mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[mqtt.homeassistant] Implement Device Tracker (#17831)
* [mqtt.homeassistant] implement Device Tracker Signed-off-by: Cody Cutrer <cody@cutrer.us>
This commit is contained in:
parent
64e4bad8e6
commit
3f392ab80a
@ -72,6 +72,17 @@ Base64 encoding is not supported
|
|||||||
| state | String | RO | The current state of the cover, possibly including opening, closing, or stopped. |
|
| state | String | RO | The current state of the cover, possibly including opening, closing, or stopped. |
|
||||||
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
|
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
|
||||||
|
|
||||||
|
### [Device Tracker](https://www.home-assistant.io/integrations/device_tracker.mqtt/)
|
||||||
|
|
||||||
|
| Channel ID | Type | R/W | Description |
|
||||||
|
|-----------------|---------------|-----|------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| home | Switch | RO | If the tracker reports itself as home or not home. |
|
||||||
|
| location-name | String | RO | The arbitrary location the tracker reports itself as at (can often be "home" or "not_home"). |
|
||||||
|
| location | Location | RO | The GPS location, if the tracker can report it. |
|
||||||
|
| gps-accuracy | Number:Length | RO | The accuracy of a GPS fix. Even if a tracker can provide GPS location, it may not be able to determine and/or report its accuracy. |
|
||||||
|
| source-type | String | RO | The source of the data, if the tracker reports it. May be "gps", "router", "bluetooth", or "bluetooth_le". |
|
||||||
|
| json-attributes | String | RO | Additional attributes, as a serialized JSON string. |
|
||||||
|
|
||||||
### [Device Trigger](https://www.home-assistant.io/integrations/device_trigger.mqtt/)
|
### [Device Trigger](https://www.home-assistant.io/integrations/device_trigger.mqtt/)
|
||||||
|
|
||||||
If a device has multiple device triggers for the same subtype (the particular button), they will only show up as a single channel, and all events for that button will be delivered to that channel.
|
If a device has multiple device triggers for the same subtype (the particular button), they will only show up as a single channel, and all events for that button will be delivered to that channel.
|
||||||
|
@ -26,11 +26,13 @@ public enum ComponentChannelType {
|
|||||||
COLOR("ha-color"),
|
COLOR("ha-color"),
|
||||||
DIMMER("ha-dimmer"),
|
DIMMER("ha-dimmer"),
|
||||||
IMAGE("ha-image"),
|
IMAGE("ha-image"),
|
||||||
|
LOCATION("ha-location"),
|
||||||
NUMBER("ha-number"),
|
NUMBER("ha-number"),
|
||||||
ROLLERSHUTTER("ha-rollershutter"),
|
ROLLERSHUTTER("ha-rollershutter"),
|
||||||
STRING("ha-string"),
|
STRING("ha-string"),
|
||||||
SWITCH("ha-switch"),
|
SWITCH("ha-switch"),
|
||||||
TRIGGER("ha-trigger");
|
TRIGGER("ha-trigger"),
|
||||||
|
GPS_ACCURACY("ha-gps-accuracy");
|
||||||
|
|
||||||
final ChannelTypeUID channelTypeUID;
|
final ChannelTypeUID channelTypeUID;
|
||||||
|
|
||||||
|
@ -157,8 +157,10 @@ public abstract class AbstractComponent<C extends AbstractChannelConfiguration>
|
|||||||
|
|
||||||
protected void addJsonAttributesChannel() {
|
protected void addJsonAttributesChannel() {
|
||||||
if (channelConfiguration.getJsonAttributesTopic() != null) {
|
if (channelConfiguration.getJsonAttributesTopic() != null) {
|
||||||
|
ChannelStateUpdateListener listener = (this instanceof ChannelStateUpdateListener localThis) ? localThis
|
||||||
|
: componentConfiguration.getUpdateListener();
|
||||||
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
|
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
|
||||||
componentConfiguration.getUpdateListener())
|
listener)
|
||||||
.stateTopic(channelConfiguration.getJsonAttributesTopic(),
|
.stateTopic(channelConfiguration.getJsonAttributesTopic(),
|
||||||
channelConfiguration.getJsonAttributesTemplate())
|
channelConfiguration.getJsonAttributesTemplate())
|
||||||
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).isAdvanced(true).build();
|
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).isAdvanced(true).build();
|
||||||
|
@ -65,6 +65,8 @@ public class ComponentFactory {
|
|||||||
return new Cover(componentConfiguration, newStyleChannels);
|
return new Cover(componentConfiguration, newStyleChannels);
|
||||||
case "device_automation":
|
case "device_automation":
|
||||||
return new DeviceTrigger(componentConfiguration, newStyleChannels);
|
return new DeviceTrigger(componentConfiguration, newStyleChannels);
|
||||||
|
case "device_tracker":
|
||||||
|
return new DeviceTracker(componentConfiguration, newStyleChannels);
|
||||||
case "event":
|
case "event":
|
||||||
return new Event(componentConfiguration, newStyleChannels);
|
return new Event(componentConfiguration, newStyleChannels);
|
||||||
case "fan":
|
case "fan":
|
||||||
|
@ -0,0 +1,232 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mqtt.homeassistant.internal.component;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.mqtt.generic.ChannelStateUpdateListener;
|
||||||
|
import org.openhab.binding.mqtt.generic.values.LocationValue;
|
||||||
|
import org.openhab.binding.mqtt.generic.values.NumberValue;
|
||||||
|
import org.openhab.binding.mqtt.generic.values.OnOffValue;
|
||||||
|
import org.openhab.binding.mqtt.generic.values.TextValue;
|
||||||
|
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannel;
|
||||||
|
import org.openhab.binding.mqtt.homeassistant.internal.ComponentChannelType;
|
||||||
|
import org.openhab.binding.mqtt.homeassistant.internal.config.dto.AbstractChannelConfiguration;
|
||||||
|
import org.openhab.binding.mqtt.homeassistant.internal.exception.ConfigurationException;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.PointType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.library.unit.SIUnits;
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.type.AutoUpdatePolicy;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.State;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.gson.JsonSyntaxException;
|
||||||
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A MQTT Device Tracker, following the https://www.home-assistant.io/integrations/device_tracker.mqtt/specification.
|
||||||
|
*
|
||||||
|
* @author Cody Cutrer - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class DeviceTracker extends AbstractComponent<DeviceTracker.ChannelConfiguration>
|
||||||
|
implements ChannelStateUpdateListener {
|
||||||
|
public static final String HOME_CHANNEL_ID = "home";
|
||||||
|
public static final String LOCATION_CHANNEL_ID = "location";
|
||||||
|
public static final String GPS_ACCURACY_CHANNEL_ID = "gps-accuracy"; // Always in meters
|
||||||
|
public static final String LOCATION_NAME_CHANNEL_ID = "location-name";
|
||||||
|
public static final String SOURCE_TYPE_CHANNEL_ID = "source-type";
|
||||||
|
|
||||||
|
public static final String[] SOURCE_TYPE_OPTIONS = new String[] { "gps", "router", "bluetooth", "bluetooth_le" };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration class for MQTT component
|
||||||
|
*/
|
||||||
|
static class ChannelConfiguration extends AbstractChannelConfiguration {
|
||||||
|
ChannelConfiguration() {
|
||||||
|
super("MQTT Binary Sensor");
|
||||||
|
}
|
||||||
|
|
||||||
|
@SerializedName("source_type")
|
||||||
|
protected @Nullable String sourceType;
|
||||||
|
@SerializedName("state_topic")
|
||||||
|
protected @Nullable String stateTopic;
|
||||||
|
@SerializedName("payload_home")
|
||||||
|
protected String payloadHome = "home";
|
||||||
|
@SerializedName("payload_not_home")
|
||||||
|
protected String payloadNotHome = "not_home";
|
||||||
|
@SerializedName("payload_reset")
|
||||||
|
protected String payloadReset = "None";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DTO for JSON Attributes providing location data
|
||||||
|
*/
|
||||||
|
static class JSONAttributes {
|
||||||
|
protected @Nullable BigDecimal latitude;
|
||||||
|
protected @Nullable BigDecimal longitude;
|
||||||
|
@SerializedName("gps_accuracy")
|
||||||
|
protected @Nullable BigDecimal gpsAccuracy;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(DeviceTracker.class);
|
||||||
|
|
||||||
|
private final ChannelStateUpdateListener channelStateUpdateListener;
|
||||||
|
private final OnOffValue homeValue = new OnOffValue();
|
||||||
|
private final NumberValue accuracyValue = new NumberValue(BigDecimal.ZERO, null, null, SIUnits.METRE);
|
||||||
|
private final TextValue locationNameValue = new TextValue();
|
||||||
|
private final LocationValue locationValue = new LocationValue();
|
||||||
|
private final @Nullable ComponentChannel homeChannel, locationChannel, accuracyChannel;
|
||||||
|
|
||||||
|
public DeviceTracker(ComponentFactory.ComponentConfiguration componentConfiguration, boolean newStyleChannels) {
|
||||||
|
super(componentConfiguration, ChannelConfiguration.class, newStyleChannels);
|
||||||
|
this.channelStateUpdateListener = componentConfiguration.getUpdateListener();
|
||||||
|
|
||||||
|
if (channelConfiguration.stateTopic == null && channelConfiguration.getJsonAttributesTopic() == null) {
|
||||||
|
throw new ConfigurationException("Device trackers must define either state_topic or json_attributes_topic");
|
||||||
|
}
|
||||||
|
homeValue.update(UnDefType.NULL);
|
||||||
|
locationNameValue.update(UnDefType.NULL);
|
||||||
|
accuracyValue.update(UnDefType.NULL);
|
||||||
|
locationValue.update(UnDefType.NULL);
|
||||||
|
|
||||||
|
if (channelConfiguration.stateTopic != null) {
|
||||||
|
homeChannel = buildChannel(HOME_CHANNEL_ID, ComponentChannelType.SWITCH, homeValue, "At Home",
|
||||||
|
componentConfiguration.getUpdateListener()).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||||
|
|
||||||
|
buildChannel(LOCATION_NAME_CHANNEL_ID, ComponentChannelType.STRING, locationNameValue, "Location Name",
|
||||||
|
this).stateTopic(channelConfiguration.stateTopic, channelConfiguration.getValueTemplate())
|
||||||
|
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||||
|
} else {
|
||||||
|
homeChannel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String sourceType = channelConfiguration.sourceType;
|
||||||
|
if (sourceType != null) {
|
||||||
|
TextValue sourceTypeValue = new TextValue(SOURCE_TYPE_OPTIONS);
|
||||||
|
sourceTypeValue.update(new StringType(sourceType));
|
||||||
|
buildChannel(SOURCE_TYPE_CHANNEL_ID, ComponentChannelType.STRING, sourceTypeValue, "Source Type", this)
|
||||||
|
.isAdvanced(true).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channelConfiguration.getJsonAttributesTopic() != null) {
|
||||||
|
locationChannel = buildChannel(LOCATION_CHANNEL_ID, ComponentChannelType.LOCATION, locationValue,
|
||||||
|
"Location", this).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||||
|
|
||||||
|
accuracyChannel = buildChannel(GPS_ACCURACY_CHANNEL_ID, ComponentChannelType.GPS_ACCURACY, accuracyValue,
|
||||||
|
"GPS Accuracy", this).isAdvanced(true).withAutoUpdatePolicy(AutoUpdatePolicy.VETO).build();
|
||||||
|
} else {
|
||||||
|
locationChannel = accuracyChannel = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
finalizeChannels();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override to set ourselves as listener
|
||||||
|
protected void addJsonAttributesChannel() {
|
||||||
|
if (channelConfiguration.getJsonAttributesTopic() != null) {
|
||||||
|
buildChannel(JSON_ATTRIBUTES_CHANNEL_ID, ComponentChannelType.STRING, new TextValue(), "JSON Attributes",
|
||||||
|
this)
|
||||||
|
.stateTopic(channelConfiguration.getJsonAttributesTopic(),
|
||||||
|
channelConfiguration.getJsonAttributesTemplate())
|
||||||
|
.withAutoUpdatePolicy(AutoUpdatePolicy.VETO).isAdvanced(true).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateChannelState(ChannelUID channel, State state) {
|
||||||
|
if (channel.getIdWithoutGroup().equals(LOCATION_NAME_CHANNEL_ID)) {
|
||||||
|
String stateString = state.toString();
|
||||||
|
if (stateString.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
State homeState;
|
||||||
|
if (channelConfiguration.payloadHome.equals(stateString)) {
|
||||||
|
homeState = OnOffType.ON;
|
||||||
|
} else if (channelConfiguration.payloadNotHome.equals(stateString)) {
|
||||||
|
homeState = OnOffType.OFF;
|
||||||
|
} else {
|
||||||
|
homeState = UnDefType.UNDEF;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channelConfiguration.payloadReset.equals(stateString)) {
|
||||||
|
state = UnDefType.NULL;
|
||||||
|
locationNameValue.update(state);
|
||||||
|
homeState = UnDefType.NULL;
|
||||||
|
ComponentChannel locationChannel = this.locationChannel;
|
||||||
|
if (locationChannel != null) {
|
||||||
|
locationValue.update(UnDefType.NULL);
|
||||||
|
accuracyValue.update(UnDefType.NULL);
|
||||||
|
channelStateUpdateListener.updateChannelState(locationChannel.getChannel().getUID(),
|
||||||
|
locationValue.getChannelState());
|
||||||
|
channelStateUpdateListener.updateChannelState(
|
||||||
|
Objects.requireNonNull(accuracyChannel).getChannel().getUID(),
|
||||||
|
accuracyValue.getChannelState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channelStateUpdateListener.updateChannelState(channel, state);
|
||||||
|
homeValue.update(homeState);
|
||||||
|
channelStateUpdateListener.updateChannelState(Objects.requireNonNull(homeChannel).getChannel().getUID(),
|
||||||
|
homeState);
|
||||||
|
} else if (channel.getIdWithoutGroup().equals(JSON_ATTRIBUTES_CHANNEL_ID)) {
|
||||||
|
// First forward JSON attributes channel as-is
|
||||||
|
channelStateUpdateListener.updateChannelState(channel, state);
|
||||||
|
|
||||||
|
JSONAttributes jsonAttributes;
|
||||||
|
try {
|
||||||
|
jsonAttributes = Objects.requireNonNull(getGson().fromJson(state.toString(), JSONAttributes.class));
|
||||||
|
} catch (JsonSyntaxException e) {
|
||||||
|
logger.warn("Cannot parse JSON attributes '{}' for '{}'.", state, getHaID());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BigDecimal latitude = jsonAttributes.latitude;
|
||||||
|
BigDecimal longitude = jsonAttributes.longitude;
|
||||||
|
BigDecimal gpsAccuracy = jsonAttributes.gpsAccuracy;
|
||||||
|
if (latitude != null && longitude != null) {
|
||||||
|
locationValue.update(new PointType(new DecimalType(latitude), new DecimalType(longitude)));
|
||||||
|
} else {
|
||||||
|
locationValue.update(UnDefType.NULL);
|
||||||
|
}
|
||||||
|
if (gpsAccuracy != null) {
|
||||||
|
accuracyValue.update(new QuantityType<>(gpsAccuracy, SIUnits.METRE));
|
||||||
|
} else {
|
||||||
|
accuracyValue.update(UnDefType.NULL);
|
||||||
|
}
|
||||||
|
channelStateUpdateListener.updateChannelState(Objects.requireNonNull(locationChannel).getChannel().getUID(),
|
||||||
|
locationValue.getChannelState());
|
||||||
|
channelStateUpdateListener.updateChannelState(Objects.requireNonNull(accuracyChannel).getChannel().getUID(),
|
||||||
|
accuracyValue.getChannelState());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void postChannelCommand(ChannelUID channelUID, Command value) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void triggerChannel(ChannelUID channelUID, String eventPayload) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
}
|
@ -16,8 +16,11 @@ channel-type.mqtt.ha-color-advanced.label = Color
|
|||||||
channel-type.mqtt.ha-color.label = Color
|
channel-type.mqtt.ha-color.label = Color
|
||||||
channel-type.mqtt.ha-dimmer-advanced.label = Dimmer
|
channel-type.mqtt.ha-dimmer-advanced.label = Dimmer
|
||||||
channel-type.mqtt.ha-dimmer.label = Dimmer
|
channel-type.mqtt.ha-dimmer.label = Dimmer
|
||||||
|
channel-type.mqtt.ha-gps-accuracy.label = GPS Accuracy
|
||||||
|
channel-type.mqtt.ha-gps-accuracy.description = The accuracy of the GPS fix, in meters.
|
||||||
channel-type.mqtt.ha-image-advanced.label = Image
|
channel-type.mqtt.ha-image-advanced.label = Image
|
||||||
channel-type.mqtt.ha-image.label = Image
|
channel-type.mqtt.ha-image.label = Image
|
||||||
|
channel-type.mqtt.ha-location.label = Location
|
||||||
channel-type.mqtt.ha-number-advanced.label = Number
|
channel-type.mqtt.ha-number-advanced.label = Number
|
||||||
channel-type.mqtt.ha-number.label = Number
|
channel-type.mqtt.ha-number.label = Number
|
||||||
channel-type.mqtt.ha-rollershutter-advanced.label = Rollershutter
|
channel-type.mqtt.ha-rollershutter-advanced.label = Rollershutter
|
||||||
|
@ -22,12 +22,26 @@
|
|||||||
<config-description-ref uri="channel-type:mqtt:ha-channel"/>
|
<config-description-ref uri="channel-type:mqtt:ha-channel"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="ha-location">
|
||||||
|
<item-type>Location</item-type>
|
||||||
|
<label>Location</label>
|
||||||
|
<config-description-ref uri="channel-type:mqtt:ha-channel"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="ha-number">
|
<channel-type id="ha-number">
|
||||||
<item-type>Number</item-type>
|
<item-type>Number</item-type>
|
||||||
<label>Number</label>
|
<label>Number</label>
|
||||||
<config-description-ref uri="channel-type:mqtt:ha-channel"/>
|
<config-description-ref uri="channel-type:mqtt:ha-channel"/>
|
||||||
</channel-type>
|
</channel-type>
|
||||||
|
|
||||||
|
<channel-type id="ha-gps-accuracy" advanced="true">
|
||||||
|
<item-type>Number:Length</item-type>
|
||||||
|
<label>GPS Accuracy</label>
|
||||||
|
<description>The accuracy of the GPS fix, in meters.</description>
|
||||||
|
<autoUpdatePolicy>veto</autoUpdatePolicy>
|
||||||
|
<config-description-ref uri="channel-type:mqtt:ha-channel"/>
|
||||||
|
</channel-type>
|
||||||
|
|
||||||
<channel-type id="ha-rollershutter">
|
<channel-type id="ha-rollershutter">
|
||||||
<item-type>Rollershutter</item-type>
|
<item-type>Rollershutter</item-type>
|
||||||
<label>Rollershutter</label>
|
<label>Rollershutter</label>
|
||||||
|
@ -0,0 +1,274 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2024 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.mqtt.homeassistant.internal.component;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.binding.mqtt.generic.values.LocationValue;
|
||||||
|
import org.openhab.binding.mqtt.generic.values.NumberValue;
|
||||||
|
import org.openhab.binding.mqtt.generic.values.OnOffValue;
|
||||||
|
import org.openhab.binding.mqtt.generic.values.TextValue;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.PointType;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.library.unit.SIUnits;
|
||||||
|
import org.openhab.core.types.UnDefType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link DeviceTracker}
|
||||||
|
*
|
||||||
|
* @author Cody Cutrer - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class DeviceTrackerTests extends AbstractComponentTests {
|
||||||
|
public static final String CONFIG_TOPIC = "device_tracker/112233445566-tracker";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testIPhone() throws InterruptedException {
|
||||||
|
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
|
||||||
|
{
|
||||||
|
"stat_t": "home/TheengsGateway/BTtoMQTT/112233445566",
|
||||||
|
"name": "APPLEDEVICE-tracker",
|
||||||
|
"uniq_id": "112233445566-tracker",
|
||||||
|
"val_tpl": "{% if value_json.get('rssi') -%}home{%- else -%}not_home{%- endif %}",
|
||||||
|
"source_type": "bluetooth_le",
|
||||||
|
"device": {
|
||||||
|
"ids": ["112233445566"],
|
||||||
|
"cns": [["mac", "112233445566"]],
|
||||||
|
"mf": "Apple",
|
||||||
|
"mdl": "APPLEDEVICE",
|
||||||
|
"name": "Apple iPhone/iPad-123456",
|
||||||
|
"via_device": "TheengsGateway"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
assertThat(component.channels.size(), is(3));
|
||||||
|
assertThat(component.getName(), is("APPLEDEVICE-tracker"));
|
||||||
|
|
||||||
|
assertChannel(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, "home/TheengsGateway/BTtoMQTT/112233445566",
|
||||||
|
"", "Location Name", TextValue.class);
|
||||||
|
assertChannel(component, DeviceTracker.HOME_CHANNEL_ID, "", "", "At Home", OnOffValue.class);
|
||||||
|
assertChannel(component, DeviceTracker.SOURCE_TYPE_CHANNEL_ID, "", "", "Source Type", TextValue.class);
|
||||||
|
assertState(component, DeviceTracker.SOURCE_TYPE_CHANNEL_ID, new StringType("bluetooth_le"));
|
||||||
|
|
||||||
|
publishMessage("home/TheengsGateway/BTtoMQTT/112233445566", """
|
||||||
|
{
|
||||||
|
"id": "11:22:33:44:55:66",
|
||||||
|
"rssi": -55,
|
||||||
|
"brand": "Apple",
|
||||||
|
"model": "Apple iPhone/iPad",
|
||||||
|
"model_id": "APPLEDEVICE",
|
||||||
|
"type": "TRACK",
|
||||||
|
"unlocked": false
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("home"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, OnOffType.ON);
|
||||||
|
publishMessage("home/TheengsGateway/BTtoMQTT/112233445566", """
|
||||||
|
{"id": "11:22:33:44:55:66", "presence": "absent"}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("not_home"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, OnOffType.OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGeneric() throws InterruptedException {
|
||||||
|
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
|
||||||
|
{
|
||||||
|
"stat_t": "devices/112233445566",
|
||||||
|
"name": "tracker"
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
assertThat(component.channels.size(), is(2));
|
||||||
|
assertThat(component.getName(), is("tracker"));
|
||||||
|
|
||||||
|
assertChannel(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, "devices/112233445566", "", "Location Name",
|
||||||
|
TextValue.class);
|
||||||
|
assertChannel(component, DeviceTracker.HOME_CHANNEL_ID, "", "", "At Home", OnOffValue.class);
|
||||||
|
|
||||||
|
publishMessage("devices/112233445566", "home");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("home"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, OnOffType.ON);
|
||||||
|
publishMessage("devices/112233445566", "not_home");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("not_home"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, OnOffType.OFF);
|
||||||
|
publishMessage("devices/112233445566", "work");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("work"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, UnDefType.UNDEF);
|
||||||
|
publishMessage("devices/112233445566", "None");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGPS() throws InterruptedException {
|
||||||
|
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
|
||||||
|
{
|
||||||
|
"stat_t": "devices/112233445566",
|
||||||
|
"name": "tracker",
|
||||||
|
"json_attributes_topic": "devices/112233445566/json"
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
assertThat(component.channels.size(), is(5));
|
||||||
|
assertThat(component.getName(), is("tracker"));
|
||||||
|
|
||||||
|
assertChannel(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, "devices/112233445566", "", "Location Name",
|
||||||
|
TextValue.class);
|
||||||
|
assertChannel(component, DeviceTracker.HOME_CHANNEL_ID, "", "", "At Home", OnOffValue.class);
|
||||||
|
assertChannel(component, DeviceTracker.LOCATION_CHANNEL_ID, "", "", "Location", LocationValue.class);
|
||||||
|
assertChannel(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, "", "", "GPS Accuracy", NumberValue.class);
|
||||||
|
assertChannel(component, DeviceTracker.JSON_ATTRIBUTES_CHANNEL_ID, "devices/112233445566/json", "",
|
||||||
|
"JSON Attributes", TextValue.class);
|
||||||
|
|
||||||
|
publishMessage("devices/112233445566", "home");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("home"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, OnOffType.ON);
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566", "not_home");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("not_home"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, OnOffType.OFF);
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566", "work");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("work"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, UnDefType.UNDEF);
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566/json", "not JSON");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("work"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, UnDefType.UNDEF);
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566/json", """
|
||||||
|
{
|
||||||
|
"nothing": 1
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("work"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, UnDefType.UNDEF);
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566/json", """
|
||||||
|
{
|
||||||
|
"latitude": 45.5,
|
||||||
|
"longitude": 91.1
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("work"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, UnDefType.UNDEF);
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID,
|
||||||
|
new PointType(new DecimalType(45.5), new DecimalType(91.1)));
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566/json", """
|
||||||
|
{
|
||||||
|
"latitude": 45.6,
|
||||||
|
"longitude": 91.2,
|
||||||
|
"gps_accuracy": 5.5
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("work"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, UnDefType.UNDEF);
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID,
|
||||||
|
new PointType(new DecimalType(45.6), new DecimalType(91.2)));
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, new QuantityType<>(5.5, SIUnits.METRE));
|
||||||
|
publishMessage("devices/112233445566/json", """
|
||||||
|
{
|
||||||
|
"latitude": 45.7,
|
||||||
|
"longitude": 91.3
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, new StringType("work"));
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, UnDefType.UNDEF);
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID,
|
||||||
|
new PointType(new DecimalType(45.7), new DecimalType(91.3)));
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566", "None");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_NAME_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.HOME_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGPSOnly() throws InterruptedException {
|
||||||
|
var component = discoverComponent(configTopicToMqtt(CONFIG_TOPIC), """
|
||||||
|
{
|
||||||
|
"name": "tracker",
|
||||||
|
"json_attributes_topic": "devices/112233445566/json"
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
assertThat(component.channels.size(), is(3));
|
||||||
|
assertThat(component.getName(), is("tracker"));
|
||||||
|
|
||||||
|
assertChannel(component, DeviceTracker.LOCATION_CHANNEL_ID, "", "", "Location", LocationValue.class);
|
||||||
|
assertChannel(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, "", "", "GPS Accuracy", NumberValue.class);
|
||||||
|
assertChannel(component, DeviceTracker.JSON_ATTRIBUTES_CHANNEL_ID, "devices/112233445566/json", "",
|
||||||
|
"JSON Attributes", TextValue.class);
|
||||||
|
|
||||||
|
publishMessage("devices/112233445566/json", "not JSON");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566/json", """
|
||||||
|
{
|
||||||
|
"nothing": 1
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566/json", """
|
||||||
|
{
|
||||||
|
"latitude": 45.5,
|
||||||
|
"longitude": 91.1
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID,
|
||||||
|
new PointType(new DecimalType(45.5), new DecimalType(91.1)));
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
publishMessage("devices/112233445566/json", """
|
||||||
|
{
|
||||||
|
"latitude": 45.6,
|
||||||
|
"longitude": 91.2,
|
||||||
|
"gps_accuracy": 5.5
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID,
|
||||||
|
new PointType(new DecimalType(45.6), new DecimalType(91.2)));
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, new QuantityType<>(5.5, SIUnits.METRE));
|
||||||
|
publishMessage("devices/112233445566/json", """
|
||||||
|
{
|
||||||
|
"latitude": 45.7,
|
||||||
|
"longitude": 91.3
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
assertState(component, DeviceTracker.LOCATION_CHANNEL_ID,
|
||||||
|
new PointType(new DecimalType(45.7), new DecimalType(91.3)));
|
||||||
|
assertState(component, DeviceTracker.GPS_ACCURACY_CHANNEL_ID, UnDefType.NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Set<String> getConfigTopics() {
|
||||||
|
return Set.of(CONFIG_TOPIC);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user