Introduced MQTT detectors to autodetect MQTT devices
This commit is contained in:
parent
e4707c2493
commit
b7ee6b16dc
21 changed files with 714 additions and 108 deletions
|
|
@ -1,3 +1,5 @@
|
|||
dependencies {
|
||||
implementation project(':hal-core')
|
||||
|
||||
testImplementation project(path: ':hal-core', configuration: 'testClasses')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,10 +24,11 @@
|
|||
|
||||
package se.hal.plugin.mqtt;
|
||||
|
||||
import se.hal.HalServer;
|
||||
import se.hal.daemon.HalMulticastDnsDaemon;
|
||||
import se.hal.intf.*;
|
||||
import se.hal.plugin.mqtt.detector.HalMqttDetector;
|
||||
import se.hal.plugin.mqtt.device.HalMqttDeviceConfig;
|
||||
import se.hal.plugin.mqtt.device.HalMqttUnknownDeviceConfig;
|
||||
import zutil.InetUtil;
|
||||
import zutil.ObjectUtil;
|
||||
import zutil.log.LogUtil;
|
||||
|
|
@ -37,10 +38,7 @@ import zutil.net.mqtt.MqttSubscriptionListener;
|
|||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
|
@ -50,6 +48,7 @@ public class HalMqttController implements HalAutostartController, MqttSubscripti
|
|||
|
||||
private MqttBroker mqttBroker;
|
||||
|
||||
private List<HalMqttDetector> detectors = Collections.emptyList();
|
||||
private HashMap<String, List<HalMqttDeviceConfig>> topics = new HashMap<>();
|
||||
private List<HalDeviceReportListener> deviceListeners = new CopyOnWriteArrayList<>();
|
||||
|
||||
|
|
@ -73,6 +72,8 @@ public class HalMqttController implements HalAutostartController, MqttSubscripti
|
|||
mqttBroker.addGlobalSubscriber(this);
|
||||
mqttBroker.start();
|
||||
|
||||
detectors = HalServer.getPluginManager().getEnabledPluginSingletons(HalMqttDetector.class);
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.log(Level.SEVERE, "Unable to initialize MQTT plugin.", e);
|
||||
|
||||
|
|
@ -80,11 +81,6 @@ public class HalMqttController implements HalAutostartController, MqttSubscripti
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close(){
|
||||
if (mqttBroker != null) {
|
||||
|
|
@ -102,16 +98,40 @@ public class HalMqttController implements HalAutostartController, MqttSubscripti
|
|||
public void dataPublished(String topic, byte[] data) {
|
||||
logger.finest("MQTT data published(topic: " + topic + "): " + new String(data, StandardCharsets.UTF_8));
|
||||
|
||||
List<HalMqttDeviceConfig> devices = topics.get(topic);
|
||||
if (ObjectUtil.isEmpty(devices))
|
||||
devices = Arrays.asList(new HalMqttUnknownDeviceConfig(topic, data));
|
||||
List<HalMqttDeviceConfig> registeredDevices = topics.get(topic);
|
||||
|
||||
for (HalMqttDeviceConfig deviceConfig : devices) {
|
||||
HalDeviceData deviceData = deviceConfig.getDeviceData(data);
|
||||
// Handle existing devices
|
||||
|
||||
if (deviceListeners != null) {
|
||||
for (HalDeviceReportListener deviceListener : deviceListeners) {
|
||||
deviceListener.reportReceived(deviceConfig, deviceData);
|
||||
if (!ObjectUtil.isEmpty(registeredDevices)) {
|
||||
for (HalMqttDeviceConfig deviceConfig : registeredDevices) {
|
||||
HalDeviceData deviceData = deviceConfig.getDeviceData(data);
|
||||
|
||||
if (deviceListeners != null) {
|
||||
for (HalDeviceReportListener deviceListener : deviceListeners) {
|
||||
deviceListener.reportReceived(deviceConfig, deviceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle new devices
|
||||
|
||||
for (HalMqttDetector detector : detectors) {
|
||||
List<HalMqttDeviceConfig> detectedDevices = detector.parseTopic(topic, data);
|
||||
|
||||
// Check if we already know the device
|
||||
if (!ObjectUtil.isEmpty(detectedDevices)) {
|
||||
for (HalMqttDeviceConfig detectedDeviceConfig : detectedDevices) {
|
||||
// Only handle unknown devices
|
||||
if (!registeredDevices.contains(detectedDeviceConfig)) {
|
||||
HalDeviceData deviceData = detectedDeviceConfig.getDeviceData(data);
|
||||
|
||||
if (deviceListeners != null) {
|
||||
for (HalDeviceReportListener deviceListener : deviceListeners) {
|
||||
deviceListener.reportReceived(detectedDeviceConfig, deviceData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -153,7 +173,8 @@ public class HalMqttController implements HalAutostartController, MqttSubscripti
|
|||
if (eventConfig instanceof HalMqttDeviceConfig) {
|
||||
HalMqttDeviceConfig mqttEvent = (HalMqttDeviceConfig) eventConfig;
|
||||
mqttBroker.publish(mqttEvent.getTopic(), Double.toString(eventData.getData()).getBytes());
|
||||
} else throw new IllegalArgumentException(
|
||||
} else
|
||||
throw new IllegalArgumentException(
|
||||
"Device config is not an instance of " + HalMqttDeviceConfig.class + ": " + eventConfig.getClass());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,78 @@
|
|||
package se.hal.plugin.mqtt.detector;
|
||||
|
||||
import se.hal.plugin.mqtt.device.HalMqttDeviceConfig;
|
||||
import se.hal.plugin.mqtt.device.HalMqttHumidityDeviceConfig;
|
||||
import se.hal.plugin.mqtt.device.HalMqttParticularMatterDeviceConfig;
|
||||
import se.hal.plugin.mqtt.device.HalMqttTemperatureDeviceConfig;
|
||||
import zutil.ObjectUtil;
|
||||
import zutil.parser.DataNode;
|
||||
import zutil.parser.json.JSONParser;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This detector will handle any MQTT topics and messages that have a generic structure.
|
||||
* Supported formats:
|
||||
* <u>
|
||||
* <li>Topics ending with: temperature, humidity, ppm</li>
|
||||
* <li>Topics where the payload is a JSON object containing the fields: temperature, humidity, ppm</li>
|
||||
* </u>
|
||||
*/
|
||||
public class GenericMqttDetector implements HalMqttDetector {
|
||||
|
||||
@Override
|
||||
public List<HalMqttDeviceConfig> parseTopic(String topic, byte[] data) {
|
||||
if (ObjectUtil.isEmpty(topic))
|
||||
return Collections.emptyList();
|
||||
|
||||
List<HalMqttDeviceConfig> detectedDeviceConfigs = new ArrayList<>();
|
||||
String[] topicSections = topic.split("/");
|
||||
|
||||
// Check if the topic ends with specific keywords
|
||||
|
||||
if (topicSections.length > 1) {
|
||||
String keyword = topicSections[topicSections.length - 1];
|
||||
HalMqttDeviceConfig config = null;
|
||||
|
||||
switch (keyword) {
|
||||
case "temperature":
|
||||
config = new HalMqttTemperatureDeviceConfig(topic);
|
||||
break;
|
||||
case "humidity":
|
||||
config = new HalMqttHumidityDeviceConfig(topic);
|
||||
break;
|
||||
case "pm25":
|
||||
config = new HalMqttParticularMatterDeviceConfig(topic);
|
||||
break;
|
||||
}
|
||||
|
||||
if (config != null) {
|
||||
detectedDeviceConfigs.add(config);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the payload is in JSON format
|
||||
|
||||
DataNode jsonPayload = JSONParser.read(new String(data, StandardCharsets.UTF_8));
|
||||
if (jsonPayload != null) {
|
||||
if (jsonPayload.get("temperature") != null) {
|
||||
HalMqttDeviceConfig config = new HalMqttTemperatureDeviceConfig(topic, "$.temperature");
|
||||
detectedDeviceConfigs.add(config);
|
||||
}
|
||||
if (jsonPayload.get("humidity") != null) {
|
||||
HalMqttDeviceConfig config = new HalMqttHumidityDeviceConfig(topic, "$.humidity");
|
||||
detectedDeviceConfigs.add(config);
|
||||
}
|
||||
if (jsonPayload.get("pm25") != null) {
|
||||
HalMqttDeviceConfig config = new HalMqttParticularMatterDeviceConfig(topic, "$.pm25");
|
||||
detectedDeviceConfigs.add(config);
|
||||
}
|
||||
}
|
||||
|
||||
return detectedDeviceConfigs;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package se.hal.plugin.mqtt.detector;
|
||||
|
||||
import se.hal.plugin.mqtt.device.HalMqttDeviceConfig;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This interface defines the interface required by a MQTT Device detector.
|
||||
* The purpose of the implementing classes is to simplify creation of devices based on
|
||||
* MQTT by automatically detecting and parsing devices from reported topics.
|
||||
*/
|
||||
public interface HalMqttDetector {
|
||||
|
||||
List<HalMqttDeviceConfig> parseTopic(String topic, byte[] data);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package se.hal.plugin.mqtt.detector;
|
||||
|
||||
import se.hal.intf.HalDeviceConfig;
|
||||
import se.hal.intf.HalDeviceData;
|
||||
import se.hal.intf.HalDeviceReportListener;
|
||||
import se.hal.plugin.mqtt.device.HalMqttDeviceConfig;
|
||||
import zutil.ObjectUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class TasmotaMqttDetector implements HalMqttDetector {
|
||||
|
||||
|
||||
@Override
|
||||
public List<HalMqttDeviceConfig> parseTopic(String topic, byte[] data) {
|
||||
if (ObjectUtil.isEmpty(topic) ||
|
||||
! topic.startsWith("zigbee2mqtt/") ||
|
||||
topic.startsWith("zigbee2mqtt/bridge/"))
|
||||
return Collections.emptyList();
|
||||
|
||||
List<HalMqttDeviceConfig> detectedDeviceConfigs = new ArrayList<>();
|
||||
return detectedDeviceConfigs;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package se.hal.plugin.mqtt.detector;
|
||||
|
||||
import se.hal.intf.HalDeviceConfig;
|
||||
import se.hal.intf.HalDeviceData;
|
||||
import se.hal.intf.HalDeviceReportListener;
|
||||
import se.hal.plugin.mqtt.device.HalMqttDeviceConfig;
|
||||
import zutil.ObjectUtil;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
public class Zigbee2mqttDetector implements HalMqttDetector {
|
||||
|
||||
@Override
|
||||
public List<HalMqttDeviceConfig> parseTopic(String topic, byte[] data) {
|
||||
if (ObjectUtil.isEmpty(topic) ||
|
||||
! topic.startsWith("zigbee2mqtt/") ||
|
||||
topic.startsWith("zigbee2mqtt/bridge/"))
|
||||
return Collections.emptyList();
|
||||
|
||||
List<HalMqttDeviceConfig> detectedDeviceConfigs = new ArrayList<>();
|
||||
return detectedDeviceConfigs;
|
||||
}
|
||||
}
|
||||
|
|
@ -57,6 +57,7 @@ import zutil.ui.conf.Configurator;
|
|||
import java.util.Objects;
|
||||
|
||||
public abstract class HalMqttDeviceConfig implements HalSensorConfig {
|
||||
|
||||
@Configurator.Configurable(value = "MQTT Topic")
|
||||
private String topic;
|
||||
@Configurator.Configurable(value = "JSON Path", description = "If the value of the topic is a JSON then this parameter can be used to specify the path to the e.g. temperature value." +
|
||||
|
|
@ -64,6 +65,25 @@ public abstract class HalMqttDeviceConfig implements HalSensorConfig {
|
|||
private String jsonPath;
|
||||
|
||||
|
||||
public HalMqttDeviceConfig() {}
|
||||
|
||||
/**
|
||||
* @param topic is the topic associated to this device
|
||||
*/
|
||||
public HalMqttDeviceConfig(String topic) {
|
||||
this.topic = topic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param topic is the topic associated to this device.
|
||||
* @param jsonPath indicates that the payload is of JSON format and the data should be extracted from this path.
|
||||
*/
|
||||
public HalMqttDeviceConfig(String topic, String jsonPath) {
|
||||
this.topic = topic;
|
||||
this.jsonPath = jsonPath;
|
||||
}
|
||||
|
||||
|
||||
public String getTopic() {
|
||||
return topic;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2020 Ziver Koc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2020 Ziver Koc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package se.hal.plugin.mqtt.device;
|
||||
|
||||
import se.hal.intf.HalDeviceData;
|
||||
import se.hal.intf.HalEventController;
|
||||
import se.hal.plugin.mqtt.HalMqttController;
|
||||
import se.hal.struct.devicedata.HumiditySensorData;
|
||||
import zutil.ObjectUtil;
|
||||
import zutil.converter.Converter;
|
||||
import zutil.parser.DataNode;
|
||||
import zutil.parser.DataNodePath;
|
||||
import zutil.parser.json.JSONParser;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class HalMqttHumidityDeviceConfig extends HalMqttDeviceConfig {
|
||||
|
||||
public HalMqttHumidityDeviceConfig() {}
|
||||
public HalMqttHumidityDeviceConfig(String topic) {
|
||||
super(topic);
|
||||
}
|
||||
public HalMqttHumidityDeviceConfig(String topic, String jsonPath) {
|
||||
super(topic, jsonPath);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Hal Methods
|
||||
// --------------------------
|
||||
|
||||
@Override
|
||||
public Class<? extends HalEventController> getDeviceControllerClass() {
|
||||
return HalMqttController.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends HalDeviceData> getDeviceDataClass() {
|
||||
return HumiditySensorData.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HumiditySensorData getDeviceData(byte[] data) {
|
||||
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)
|
||||
return new HumiditySensorData(deviceDataValue.getDouble(), System.currentTimeMillis());
|
||||
else
|
||||
return null;
|
||||
}
|
||||
return new HumiditySensorData(Converter.toInt(data), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregationMethod getAggregationMethod() {
|
||||
return AggregationMethod.AVERAGE;
|
||||
}
|
||||
}
|
||||
|
|
@ -62,6 +62,14 @@ import java.nio.charset.StandardCharsets;
|
|||
|
||||
public class HalMqttParticularMatterDeviceConfig extends HalMqttDeviceConfig {
|
||||
|
||||
public HalMqttParticularMatterDeviceConfig() {}
|
||||
public HalMqttParticularMatterDeviceConfig(String topic) {
|
||||
super(topic);
|
||||
}
|
||||
public HalMqttParticularMatterDeviceConfig(String topic, String jsonPath) {
|
||||
super(topic, jsonPath);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Hal Methods
|
||||
// --------------------------
|
||||
|
|
@ -77,7 +85,7 @@ public class HalMqttParticularMatterDeviceConfig extends HalMqttDeviceConfig {
|
|||
}
|
||||
|
||||
@Override
|
||||
public HalDeviceData getDeviceData(byte[] data) {
|
||||
public ParticulateMatterSensorData getDeviceData(byte[] data) {
|
||||
if (!ObjectUtil.isEmpty(getJsonPath())) {
|
||||
String dataStr = new String(data, StandardCharsets.UTF_8);
|
||||
DataNode json = JSONParser.read(dataStr);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2020 Ziver Koc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2020 Ziver Koc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package se.hal.plugin.mqtt.device;
|
||||
|
||||
import se.hal.intf.HalDeviceData;
|
||||
import se.hal.intf.HalEventController;
|
||||
import se.hal.plugin.mqtt.HalMqttController;
|
||||
import se.hal.struct.devicedata.TemperatureSensorData;
|
||||
import zutil.ObjectUtil;
|
||||
import zutil.converter.Converter;
|
||||
import zutil.parser.DataNode;
|
||||
import zutil.parser.DataNodePath;
|
||||
import zutil.parser.json.JSONParser;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class HalMqttTemperatureDeviceConfig extends HalMqttDeviceConfig {
|
||||
|
||||
public HalMqttTemperatureDeviceConfig() {}
|
||||
public HalMqttTemperatureDeviceConfig(String topic) {
|
||||
super(topic);
|
||||
}
|
||||
public HalMqttTemperatureDeviceConfig(String topic, String jsonPath) {
|
||||
super(topic, jsonPath);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Hal Methods
|
||||
// --------------------------
|
||||
|
||||
@Override
|
||||
public Class<? extends HalEventController> getDeviceControllerClass() {
|
||||
return HalMqttController.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends HalDeviceData> getDeviceDataClass() {
|
||||
return TemperatureSensorData.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TemperatureSensorData getDeviceData(byte[] data) {
|
||||
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)
|
||||
return new TemperatureSensorData(deviceDataValue.getDouble(), System.currentTimeMillis());
|
||||
else
|
||||
return null;
|
||||
}
|
||||
return new TemperatureSensorData(Converter.toInt(data), System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregationMethod getAggregationMethod() {
|
||||
return AggregationMethod.AVERAGE;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +0,0 @@
|
|||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2023 Ziver Koc
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package se.hal.plugin.mqtt.device;
|
||||
|
||||
import se.hal.intf.HalDeviceData;
|
||||
import se.hal.intf.HalEventController;
|
||||
import se.hal.plugin.mqtt.HalMqttController;
|
||||
import se.hal.struct.devicedata.TemperatureSensorData;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* Represents an unknown device data type
|
||||
*/
|
||||
public class HalMqttUnknownDeviceConfig extends HalMqttDeviceConfig {
|
||||
|
||||
/** Save data so it can be provided to user for analysis, the data will not be used in any other way */
|
||||
transient String data;
|
||||
|
||||
|
||||
public HalMqttUnknownDeviceConfig(String topic, byte[] data) {
|
||||
setTopic(topic);
|
||||
this.data = new String(data, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// --------------------------
|
||||
// Hal Methods
|
||||
// --------------------------
|
||||
|
||||
@Override
|
||||
public Class<? extends HalEventController> getDeviceControllerClass() {
|
||||
return HalMqttController.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<? extends HalDeviceData> getDeviceDataClass() {
|
||||
return TemperatureSensorData.class; // Just return any data class just so we do not get so many error logs
|
||||
}
|
||||
|
||||
@Override
|
||||
public HalDeviceData getDeviceData(byte[] data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AggregationMethod getAggregationMethod() {
|
||||
return AggregationMethod.AVERAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Topic: " + getTopic() + ", Data: " + data;
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,13 @@
|
|||
"interfaces": [
|
||||
{"se.hal.intf.HalAutostartController": "se.hal.plugin.mqtt.HalMqttController"},
|
||||
|
||||
{"se.hal.plugin.mqtt.detector.HalMqttDetector": "se.hal.plugin.mqtt.detector.GenericMqttDetector"},
|
||||
{"se.hal.plugin.mqtt.detector.HalMqttDetector": "se.hal.plugin.mqtt.detector.TasmotaMqttDetector"},
|
||||
{"se.hal.plugin.mqtt.detector.HalMqttDetector": "se.hal.plugin.mqtt.detector.Zigbee2mqttDetector"},
|
||||
|
||||
{"se.hal.intf.HalSensorConfig": "se.hal.plugin.mqtt.device.HalMqttHumidityDeviceConfig"},
|
||||
{"se.hal.intf.HalSensorConfig": "se.hal.plugin.mqtt.device.HalMqttParticularMatterDeviceConfig"},
|
||||
{"se.hal.intf.HalSensorConfig": "se.hal.plugin.mqtt.device.HalMqttTemperatureDeviceConfig"},
|
||||
|
||||
{"se.hal.intf.HalWebPage": "se.hal.plugin.mqtt.page.MqttOverviewPage"}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,141 @@
|
|||
package se.hal.plugin.mqtt.detector;
|
||||
|
||||
import org.junit.Test;
|
||||
import se.hal.plugin.mqtt.device.HalMqttHumidityDeviceConfig;
|
||||
import se.hal.plugin.mqtt.device.HalMqttParticularMatterDeviceConfig;
|
||||
import se.hal.plugin.mqtt.device.HalMqttTemperatureDeviceConfig;
|
||||
import se.hal.struct.devicedata.HumiditySensorData;
|
||||
import se.hal.struct.devicedata.ParticulateMatterSensorData;
|
||||
import se.hal.struct.devicedata.TemperatureSensorData;
|
||||
import se.hal.test.MockHalDeviceReportListener;
|
||||
import zutil.converter.Converter;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
public class GenericMqttDetectorTest {
|
||||
|
||||
@Test
|
||||
public void ignoredTopics() {
|
||||
MockHalDeviceReportListener listener = new MockHalDeviceReportListener();
|
||||
GenericMqttDetector detector = new GenericMqttDetector();
|
||||
detector.addListener(listener);
|
||||
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic("", new byte[]{});
|
||||
assertEquals(0, listener.getNumberOfReports());
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic("invalid/topic", new byte[]{});
|
||||
assertEquals(0, listener.getNumberOfReports());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseTemperature() {
|
||||
MockHalDeviceReportListener listener = new MockHalDeviceReportListener();
|
||||
GenericMqttDetector detector = new GenericMqttDetector();
|
||||
detector.addListener(listener);
|
||||
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic(
|
||||
"zigbee2mqtt/Kitchen air quality/temperature",
|
||||
Converter.toBytes(26));
|
||||
assertEquals(1, listener.getNumberOfReports());
|
||||
assertEquals(
|
||||
new HalMqttTemperatureDeviceConfig("zigbee2mqtt/Kitchen air quality/temperature"),
|
||||
listener.getReport(0).config);
|
||||
listener.getReport(0).data.setTimestamp(0);
|
||||
assertEquals(
|
||||
new TemperatureSensorData(26, 0),
|
||||
listener.getReport(0).data);
|
||||
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic(
|
||||
"zigbee2mqtt/Kitchen air quality",
|
||||
"{\"temperature\": 26}".getBytes(StandardCharsets.UTF_8));
|
||||
assertEquals(1, listener.getNumberOfReports());
|
||||
assertEquals(
|
||||
new HalMqttTemperatureDeviceConfig("zigbee2mqtt/Kitchen air quality", "$.temperature"),
|
||||
listener.getReport(0).config);
|
||||
listener.getReport(0).data.setTimestamp(0);
|
||||
assertEquals(
|
||||
new TemperatureSensorData(26, 0),
|
||||
listener.getReport(0).data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseHumidity() {
|
||||
MockHalDeviceReportListener listener = new MockHalDeviceReportListener();
|
||||
GenericMqttDetector detector = new GenericMqttDetector();
|
||||
detector.addListener(listener);
|
||||
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic(
|
||||
"zigbee2mqtt/Kitchen air quality/humidity",
|
||||
Converter.toBytes(51));
|
||||
assertEquals(1, listener.getNumberOfReports());
|
||||
assertEquals(
|
||||
new HalMqttHumidityDeviceConfig("zigbee2mqtt/Kitchen air quality/humidity"),
|
||||
listener.getReport(0).config);
|
||||
listener.getReport(0).data.setTimestamp(0);
|
||||
assertEquals(
|
||||
new HumiditySensorData(51, 0),
|
||||
listener.getReport(0).data);
|
||||
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic(
|
||||
"zigbee2mqtt/Kitchen air quality",
|
||||
"{\"humidity\": 51}".getBytes(StandardCharsets.UTF_8));
|
||||
assertEquals(1, listener.getNumberOfReports());
|
||||
assertEquals(
|
||||
new HalMqttHumidityDeviceConfig("zigbee2mqtt/Kitchen air quality", "$.humidity"),
|
||||
listener.getReport(0).config);
|
||||
listener.getReport(0).data.setTimestamp(0);
|
||||
assertEquals(
|
||||
new HumiditySensorData(51, 0),
|
||||
listener.getReport(0).data);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseParticularMatter() {
|
||||
MockHalDeviceReportListener listener = new MockHalDeviceReportListener();
|
||||
GenericMqttDetector detector = new GenericMqttDetector();
|
||||
detector.addListener(listener);
|
||||
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic(
|
||||
"zigbee2mqtt/Kitchen air quality/pm25",
|
||||
Converter.toBytes(1));
|
||||
assertEquals(1, listener.getNumberOfReports());
|
||||
assertEquals(
|
||||
new HalMqttParticularMatterDeviceConfig("zigbee2mqtt/Kitchen air quality/pm25"),
|
||||
listener.getReport(0).config);
|
||||
listener.getReport(0).data.setTimestamp(0);
|
||||
assertEquals(
|
||||
new ParticulateMatterSensorData(1, 0),
|
||||
listener.getReport(0).data);
|
||||
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic(
|
||||
"zigbee2mqtt/Kitchen air quality",
|
||||
"{\"pm25\": 1}".getBytes(StandardCharsets.UTF_8));
|
||||
assertEquals(1, listener.getNumberOfReports());
|
||||
assertEquals(
|
||||
new HalMqttParticularMatterDeviceConfig("zigbee2mqtt/Kitchen air quality", "$.pm25"),
|
||||
listener.getReport(0).config);
|
||||
listener.getReport(0).data.setTimestamp(0);
|
||||
assertEquals(
|
||||
new ParticulateMatterSensorData(1, 0),
|
||||
listener.getReport(0).data);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package se.hal.plugin.mqtt.detector;
|
||||
|
||||
import org.junit.Test;
|
||||
import se.hal.plugin.mqtt.device.HalMqttParticularMatterDeviceConfig;
|
||||
import se.hal.test.MockHalDeviceReportListener;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
public class Zigbee2mqttDetectorTest {
|
||||
|
||||
@Test
|
||||
public void ignoredTopics() {
|
||||
MockHalDeviceReportListener listener = new MockHalDeviceReportListener();
|
||||
Zigbee2mqttDetector detector = new Zigbee2mqttDetector();
|
||||
detector.addListener(listener);
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic("", new byte[]{});
|
||||
assertEquals(0, listener.getNumberOfReports());
|
||||
|
||||
listener.reset();
|
||||
detector.parseTopic("invalid/topic", new byte[]{});
|
||||
assertEquals(0, listener.getNumberOfReports());
|
||||
}
|
||||
/*
|
||||
@Test
|
||||
public void parseTemperature() {
|
||||
Zigbee2mqttDetector detector = new Zigbee2mqttDetector();
|
||||
|
||||
assertEquals(
|
||||
Collections.emptyList(),
|
||||
detector.parseTopic(
|
||||
"zigbee2mqtt/Kitchen air quality",
|
||||
"{\"temperature\":26}".getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseHumidity() {
|
||||
Zigbee2mqttDetector detector = new Zigbee2mqttDetector();
|
||||
|
||||
assertEquals(
|
||||
Collections.emptyList(),
|
||||
detector.parseTopic(
|
||||
"zigbee2mqtt/Kitchen air quality",
|
||||
"{\"humidity\":51}".getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseParticularMatter() {
|
||||
Zigbee2mqttDetector detector = new Zigbee2mqttDetector();
|
||||
|
||||
assertEquals(
|
||||
Collections.list(new HalMqttParticularMatterDeviceConfig()),
|
||||
detector.parseTopic(
|
||||
"zigbee2mqtt/Kitchen air quality",
|
||||
"{\"pm25\":1}".getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
*/
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue