added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.gpstracker-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-gpstracker" description="GPSTracker Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.gpstracker/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.type.ChannelTypeUID;
/**
* Binding constants
*
* @author Gabor Bicskei - Initial contribution
*/
public abstract class GPSTrackerBindingConstants {
public static final String BINDING_ID = "gpstracker";
static final String CONFIG_PID = "binding." + BINDING_ID;
private static final String THING_TYPE = "tracker";
public static final ThingTypeUID THING_TYPE_TRACKER = new ThingTypeUID(BINDING_ID, THING_TYPE);
// channels
public static final String CHANNEL_REGION_TRIGGER = "regionTrigger";
public static final String CHANNEL_LAST_REPORT = "lastReport";
public static final String CHANNEL_LAST_LOCATION = "lastLocation";
public static final String CHANNEL_BATTERY_LEVEL = "batteryLevel";
private static final String CHANNEL_REGION_DISTANCE = "regionDistance";
public static final String CHANNEL_GPS_ACCURACY = "gpsAccuracy";
// system distance channel
public static final String CHANNEL_DISTANCE_SYSTEM_ID = "distanceSystem";
public static final String CHANNEL_DISTANCE_SYSTEM_NAME = "System";
public static final Integer CHANNEL_DISTANCE_SYSTEM_RADIUS = 100;
public static final ChannelTypeUID CHANNEL_TYPE_DISTANCE = new ChannelTypeUID(BINDING_ID, CHANNEL_REGION_DISTANCE);
public static final ChannelTypeUID CHANNEL_TYPE_REGION = new ChannelTypeUID(BINDING_ID, CHANNEL_REGION_TRIGGER);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream.of(THING_TYPE_TRACKER)
.collect(Collectors.toSet());
}

View File

@@ -0,0 +1,229 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal;
import static org.openhab.binding.gpstracker.internal.GPSTrackerBindingConstants.CONFIG_PID;
import java.net.URI;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gpstracker.internal.config.ConfigHelper;
import org.openhab.binding.gpstracker.internal.discovery.TrackerDiscoveryService;
import org.openhab.binding.gpstracker.internal.handler.TrackerHandler;
import org.openhab.binding.gpstracker.internal.message.NotificationBroker;
import org.openhab.binding.gpstracker.internal.provider.TrackerRegistry;
import org.openhab.binding.gpstracker.internal.provider.gpslogger.GPSLoggerCallbackServlet;
import org.openhab.binding.gpstracker.internal.provider.owntracks.OwnTracksCallbackServlet;
import org.openhab.core.config.core.ConfigOptionProvider;
import org.openhab.core.config.core.ParameterOption;
import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Main component
*
* @author Gabor Bicskei - Initial contribution
*/
@Component(configurationPid = CONFIG_PID, service = { ThingHandlerFactory.class, ConfigOptionProvider.class })
@NonNullByDefault
public class GPSTrackerHandlerFactory extends BaseThingHandlerFactory implements TrackerRegistry, ConfigOptionProvider {
/**
* Config URI
*/
private static final String URI_STR = "profile:gpstracker:trigger-geofence";
/**
* Class logger
*/
private final Logger logger = LoggerFactory.getLogger(GPSTrackerHandlerFactory.class);
/**
* Discovery service instance
*/
private final TrackerDiscoveryService discoveryService;
/**
* Unit provider
*/
private final UnitProvider unitProvider;
/**
* Location provider
*/
private final LocationProvider locationProvider;
/**
* HTTP service reference
*/
private final HttpService httpService;
/**
* Endpoint called by tracker applications
*/
private @NonNullByDefault({}) OwnTracksCallbackServlet otHTTPEndpoint;
/**
* Endpoint called by tracker applications
*/
private @NonNullByDefault({}) GPSLoggerCallbackServlet glHTTPEndpoint;
/**
* Notification broker
*/
private final NotificationBroker notificationBroker = new NotificationBroker();
/**
* Handler registry
*/
private final Map<String, TrackerHandler> trackerHandlers = new HashMap<>();
/**
* All regions.
*/
private final Set<String> regions = new HashSet<>();
@Activate
public GPSTrackerHandlerFactory(final @Reference HttpService httpService, //
final @Reference TrackerDiscoveryService discoveryService, //
final @Reference UnitProvider unitProvider, //
final @Reference LocationProvider locationProvider) {
this.httpService = httpService;
this.discoveryService = discoveryService;
this.unitProvider = unitProvider;
this.locationProvider = locationProvider;
}
/**
* Called by the framework to find out if thing type is supported by the handler factory.
*
* @param thingTypeUID Thing type UID
* @return True if supported.
*/
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return GPSTrackerBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
/**
* Creates new handler for tracker.
*
* @param thing Tracker thing
* @return Handler instance
*/
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (GPSTrackerBindingConstants.THING_TYPE_TRACKER.equals(thingTypeUID)
&& ConfigHelper.getTrackerId(thing.getConfiguration()) != null) {
TrackerHandler trackerHandler = new TrackerHandler(thing, notificationBroker, regions,
locationProvider.getLocation(), unitProvider);
discoveryService.removeTracker(trackerHandler.getTrackerId());
trackerHandlers.put(trackerHandler.getTrackerId(), trackerHandler);
return trackerHandler;
} else {
return null;
}
}
@Override
protected void removeHandler(ThingHandler thingHandler) {
String trackerId = ConfigHelper.getTrackerId(thingHandler.getThing().getConfiguration());
trackerHandlers.remove(trackerId);
}
/**
* Activate the binding. It starts the tracker discovery service and the HTTP callback endpoint.
*
* @param componentContext Component context.
*/
@Override
protected void activate(ComponentContext componentContext) {
super.activate(componentContext);
logger.debug("Initializing callback servlets");
try {
otHTTPEndpoint = new OwnTracksCallbackServlet(discoveryService, this);
this.httpService.registerServlet(otHTTPEndpoint.getPath(), otHTTPEndpoint, null,
this.httpService.createDefaultHttpContext());
logger.debug("Started GPSTracker Callback servlet on {}", otHTTPEndpoint.getPath());
glHTTPEndpoint = new GPSLoggerCallbackServlet(discoveryService, this);
this.httpService.registerServlet(glHTTPEndpoint.getPath(), glHTTPEndpoint, null,
this.httpService.createDefaultHttpContext());
logger.debug("Started GPSTracker Callback servlet on {}", glHTTPEndpoint.getPath());
} catch (NamespaceException | ServletException e) {
logger.error("Could not start GPSTracker Callback servlet: {}", e.getMessage(), e);
}
}
/**
* Deactivate the binding. It stops the HTTP callback endpoint and stops the tracker discovery service.
*
* @param componentContext Component context.
*/
@Override
protected void deactivate(ComponentContext componentContext) {
logger.debug("Deactivating GPSTracker Binding");
this.httpService.unregister(otHTTPEndpoint.getPath());
logger.debug("GPSTracker callback servlet stopped on {}", otHTTPEndpoint.getPath());
this.httpService.unregister(glHTTPEndpoint.getPath());
logger.debug("GPSTracker callback servlet stopped on {}", glHTTPEndpoint.getPath());
super.deactivate(componentContext);
}
@Override
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String param, @Nullable Locale locale) {
return getParameterOptions(uri, param, null, locale);
}
@Override
public @Nullable Collection<ParameterOption> getParameterOptions(URI uri, String param, @Nullable String context,
@Nullable Locale locale) {
if (URI_STR.equals(uri.toString()) && ConfigHelper.CONFIG_REGION_NAME.equals(param)) {
Set<ParameterOption> ret = new HashSet<>();
regions.forEach(r -> ret.add(new ParameterOption(r, r)));
return ret;
}
return null;
}
@Override
public @Nullable TrackerHandler getTrackerHandler(String trackerId) {
return trackerHandlers.get(trackerId);
}
}

View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.config;
import java.math.BigDecimal;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.library.types.PointType;
/**
* The {@link ConfigHelper} class is a configuration helper for channels and profiles.
*
* @author Gabor Bicskei - Initial contribution
*/
public class ConfigHelper {
// configuration constants
public static final String CONFIG_TRACKER_ID = "trackerId";
public static final String CONFIG_REGION_NAME = "regionName";
public static final String CONFIG_REGION_RADIUS = "regionRadius";
public static final String CONFIG_REGION_CENTER_LOCATION = "regionCenterLocation";
public static final String CONFIG_ACCURACY_THRESHOLD = "accuracyThreshold";
/**
* Constructor.
*/
private ConfigHelper() {
}
public static double getRegionRadius(Configuration config) {
return ((BigDecimal) config.get(CONFIG_REGION_RADIUS)).doubleValue();
}
public static double getAccuracyThreshold(Configuration config) {
Object value = config.get(CONFIG_ACCURACY_THRESHOLD);
return value != null ? ((BigDecimal) value).doubleValue() : 0;
}
public static String getRegionName(Configuration config) {
return (String) config.get(CONFIG_REGION_NAME);
}
public static String getTrackerId(Configuration config) {
return (String) config.get(CONFIG_TRACKER_ID);
}
public static PointType getRegionCenterLocation(Configuration config) {
String location = (String) config.get(CONFIG_REGION_CENTER_LOCATION);
return location != null ? new PointType(location) : null;
}
}

View File

@@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.discovery;
import java.util.Date;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gpstracker.internal.GPSTrackerBindingConstants;
import org.openhab.binding.gpstracker.internal.config.ConfigHelper;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Modified;
/**
* The {@link TrackerDiscoveryService} class provides discovery service for the binding to discover trackers. Discovery
* process is initiated by the tracker by sending a GPS log record. Based on the tracker id received in thin record an
* entry is created in the Inbox for the thing representing the tracker.
*
* @author Gabor Bicskei - Initial contribution
*/
@NonNullByDefault
@Component(service = { DiscoveryService.class,
TrackerDiscoveryService.class }, immediate = true, configurationPid = "discovery.gpstracker")
public class TrackerDiscoveryService extends AbstractDiscoveryService {
/**
* Discovery timeout
*/
private static final int TIMEOUT = 1;
/**
* Registry of tracker to discover next time
*/
private Set<String> trackersToDiscover = new HashSet<>();
/**
* Constructor.
*
* @throws IllegalArgumentException thrown by the super constructor
*/
public TrackerDiscoveryService() throws IllegalArgumentException {
super(GPSTrackerBindingConstants.SUPPORTED_THING_TYPES_UIDS, TIMEOUT, true);
}
/**
* Called when the source tracker is not registered as a thing. These undiscovered trackers will be registered by
* the discovery service.
*
* @param trackerId Tracker id.
*/
public void addTracker(String trackerId) {
trackersToDiscover.add(trackerId);
if (isBackgroundDiscoveryEnabled()) {
createDiscoveryResult(trackerId);
}
}
/**
* Unregister the tracker after the thing handles is created.
*
* @param trackerId Tracker id to unregister
*/
public void removeTracker(String trackerId) {
trackersToDiscover.remove(trackerId);
}
@Override
protected void startScan() {
trackersToDiscover.forEach(this::createDiscoveryResult);
}
/**
* Create discovery result form the tracker id.
*
* @param trackerId Tracker id.
*/
private void createDiscoveryResult(String trackerId) {
ThingUID id = new ThingUID(GPSTrackerBindingConstants.THING_TYPE_TRACKER, trackerId);
DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(id)
.withProperty(ConfigHelper.CONFIG_TRACKER_ID, trackerId)
.withThingType(GPSTrackerBindingConstants.THING_TYPE_TRACKER).withLabel("GPS Tracker " + trackerId)
.build();
this.thingDiscovered(discoveryResult);
}
@Override
@Activate
protected void activate(@Nullable Map<String, @Nullable Object> configProperties) {
super.activate(configProperties);
}
@Override
@Modified
protected void modified(@Nullable Map<String, @Nullable Object> configProperties) {
super.modified(configProperties);
}
@Override
@Deactivate
protected void deactivate() {
removeOlderResults(new Date().getTime());
super.deactivate();
}
@Override
protected void stopScan() {
super.stopScan();
removeOlderResults(getTimestampOfLastScan());
}
}

View File

@@ -0,0 +1,431 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.handler;
import static org.openhab.binding.gpstracker.internal.GPSTrackerBindingConstants.*;
import static org.openhab.binding.gpstracker.internal.config.ConfigHelper.CONFIG_REGION_CENTER_LOCATION;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.measure.Unit;
import javax.measure.quantity.Length;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gpstracker.internal.config.ConfigHelper;
import org.openhab.binding.gpstracker.internal.message.LocationMessage;
import org.openhab.binding.gpstracker.internal.message.NotificationBroker;
import org.openhab.binding.gpstracker.internal.message.NotificationHandler;
import org.openhab.binding.gpstracker.internal.message.TransitionMessage;
import org.openhab.core.config.core.Configuration;
import org.openhab.core.i18n.UnitProvider;
import org.openhab.core.library.types.PointType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.ImperialUnits;
import org.openhab.core.library.unit.MetricPrefix;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.ThingHandlerCallback;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link TrackerHandler} class is a tracker thing handler.
*
* @author Gabor Bicskei - Initial contribution
*/
public class TrackerHandler extends BaseThingHandler {
/**
* Trigger events
*/
private static final String EVENT_ENTER = "enter";
private static final String EVENT_LEAVE = "leave";
/**
* Class logger
*/
private final Logger logger = LoggerFactory.getLogger(TrackerHandler.class);
/**
* Notification handler
*/
private NotificationHandler notificationHandler;
/**
* Notification broker
*/
private NotificationBroker notificationBroker;
/**
* Id of the tracker represented by the thing
*/
private String trackerId;
/**
* Map of regionName/distance channels
*/
private Map<String, Channel> distanceChannelMap = new HashMap<>();
/**
* Map of last trigger events per region
*/
private Map<String, Boolean> lastTriggeredStates = new HashMap<>();
/**
* Set of all regions referenced by distance channels and extended by the received transition messages.
*/
private Set<String> regions;
/**
* System location
*/
private PointType sysLocation;
/**
* Unit provider
*/
private UnitProvider unitProvider;
/**
* Last message received from the tracker
*/
private LocationMessage lastMessage;
/**
* Constructor.
*
* @param thing Thing.
* @param notificationBroker Notification broker
* @param regions Global region set
* @param sysLocation Location of the system
* @param unitProvider Unit provider
*/
public TrackerHandler(Thing thing, NotificationBroker notificationBroker, Set<String> regions,
PointType sysLocation, UnitProvider unitProvider) {
super(thing);
this.notificationBroker = notificationBroker;
this.notificationHandler = new NotificationHandler();
this.regions = regions;
this.sysLocation = sysLocation;
this.unitProvider = unitProvider;
trackerId = ConfigHelper.getTrackerId(thing.getConfiguration());
notificationBroker.registerHandler(trackerId, notificationHandler);
logger.debug("Tracker handler created: {}", trackerId);
}
/**
* Returns tracker id configuration of the thing.
*
* @return Tracker id
*/
public String getTrackerId() {
return trackerId;
}
@Override
public void initialize() {
if (sysLocation != null) {
createBasicDistanceChannel();
} else {
logger.debug("System location is not set. Skipping system distance channel setup.");
}
mapDistanceChannels();
updateStatus(ThingStatus.ONLINE);
}
/**
* Create distance channel for measuring the distance between the tracker and the szstem.
*/
private void createBasicDistanceChannel() {
@Nullable
ThingHandlerCallback callback = getCallback();
if (callback != null) {
// find the system distance channel
ChannelUID systemDistanceChannelUID = new ChannelUID(thing.getUID(), CHANNEL_DISTANCE_SYSTEM_ID);
Channel systemDistance = thing.getChannel(CHANNEL_DISTANCE_SYSTEM_ID);
ChannelBuilder channelBuilder = null;
if (systemDistance != null) {
if (!systemDistance.getConfiguration().get(CONFIG_REGION_CENTER_LOCATION)
.equals(sysLocation.toFullString())) {
logger.trace("Existing distance channel for system. Changing system location config parameter: {}",
sysLocation.toFullString());
channelBuilder = callback.editChannel(thing, systemDistanceChannelUID);
Configuration configToUpdate = systemDistance.getConfiguration();
configToUpdate.put(CONFIG_REGION_CENTER_LOCATION, sysLocation.toFullString());
channelBuilder.withConfiguration(configToUpdate);
} else {
logger.trace("Existing distance channel for system. No change.");
}
} else {
logger.trace("Creating missing distance channel for system.");
Configuration config = new Configuration();
config.put(ConfigHelper.CONFIG_REGION_NAME, CHANNEL_DISTANCE_SYSTEM_NAME);
config.put(CONFIG_REGION_CENTER_LOCATION, sysLocation.toFullString());
config.put(ConfigHelper.CONFIG_REGION_RADIUS, CHANNEL_DISTANCE_SYSTEM_RADIUS);
config.put(ConfigHelper.CONFIG_ACCURACY_THRESHOLD, 0);
channelBuilder = callback.createChannelBuilder(systemDistanceChannelUID, CHANNEL_TYPE_DISTANCE)
.withLabel("System Distance").withConfiguration(config);
}
// update the thing with system distance channel
if (channelBuilder != null) {
List<Channel> channels = new ArrayList<>(thing.getChannels());
if (systemDistance != null) {
channels.remove(systemDistance);
}
channels.add(channelBuilder.build());
ThingBuilder thingBuilder = editThing();
thingBuilder.withChannels(channels);
updateThing(thingBuilder.build());
logger.debug("Distance channel created for system: {}", systemDistanceChannelUID);
}
}
}
/**
* Create a map of all configured distance channels to handle channel updates easily.
*/
private void mapDistanceChannels() {
distanceChannelMap = thing.getChannels().stream()
.filter(c -> CHANNEL_TYPE_DISTANCE.equals(c.getChannelTypeUID()))
.collect(Collectors.toMap(c -> ConfigHelper.getRegionName(c.getConfiguration()), Function.identity()));
// register the collected regions
regions.addAll(distanceChannelMap.keySet());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType && lastMessage != null) {
String channelId = channelUID.getId();
switch (channelId) {
case CHANNEL_LAST_REPORT:
updateBaseChannels(lastMessage, CHANNEL_LAST_REPORT);
break;
case CHANNEL_LAST_LOCATION:
updateBaseChannels(lastMessage, CHANNEL_LAST_LOCATION);
break;
case CHANNEL_BATTERY_LEVEL:
updateBaseChannels(lastMessage, CHANNEL_BATTERY_LEVEL);
break;
case CHANNEL_GPS_ACCURACY:
updateBaseChannels(lastMessage, CHANNEL_GPS_ACCURACY);
break;
default: // distance channels
@Nullable
Channel channel = thing.getChannel(channelId);
if (channel != null) {
updateDistanceChannelFromMessage(lastMessage, channel);
}
}
}
}
/**
* Handle transition messages by firing the trigger channel with regionName/event payload.
*
* @param message TransitionMessage message.
*/
private void updateTriggerChannelsWithTransition(TransitionMessage message) {
String regionName = message.getRegionName();
triggerRegionChannel(regionName, message.getEvent());
}
/**
* Fire trigger event with regionName/enter|leave payload but only if the event differs from the last event.
*
* @param regionName Region name
* @param event Occurred event
*/
private void triggerRegionChannel(@NonNull String regionName, @NonNull String event) {
Boolean lastState = lastTriggeredStates.get(regionName);
Boolean newState = EVENT_ENTER.equals(event);
if (!newState.equals(lastState) && lastState != null) {
String payload = regionName + "/" + event;
triggerChannel(CHANNEL_REGION_TRIGGER, payload);
lastTriggeredStates.put(regionName, newState);
logger.trace("Triggering {} for {}/{}", regionName, trackerId, payload);
}
lastTriggeredStates.put(regionName, newState);
}
/**
* Update state channels from location message. This includes basic channel updates and recalculations of all
* distances.
*
* @param message Message.
*/
private void updateChannelsWithLocation(LocationMessage message) {
updateBaseChannels(message, CHANNEL_BATTERY_LEVEL, CHANNEL_LAST_LOCATION, CHANNEL_LAST_REPORT,
CHANNEL_GPS_ACCURACY);
String trackerId = message.getTrackerId();
logger.debug("Updating distance channels tracker {}", trackerId);
distanceChannelMap.values().forEach(c -> updateDistanceChannelFromMessage(message, c));
}
private void updateDistanceChannelFromMessage(LocationMessage message, Channel c) {
Configuration currentConfig = c.getConfiguration();
// convert into meters which is the unit of the threshold
Double accuracyThreshold = convertToMeters(ConfigHelper.getAccuracyThreshold(currentConfig));
State messageAccuracy = message.getGpsAccuracy();
Double accuracy = messageAccuracy != UnDefType.UNDEF ? ((QuantityType<?>) messageAccuracy).doubleValue()
: accuracyThreshold;
if (accuracyThreshold >= accuracy || accuracyThreshold.intValue() == 0) {
if (accuracyThreshold > 0) {
logger.debug("Location accuracy is below required threshold: {}<={}", accuracy, accuracyThreshold);
} else {
logger.debug("Location accuracy threshold check is disabled.");
}
String regionName = ConfigHelper.getRegionName(currentConfig);
PointType center = ConfigHelper.getRegionCenterLocation(currentConfig);
State newLocation = message.getTrackerLocation();
if (center != null && newLocation != UnDefType.UNDEF) {
double newDistance = center.distanceFrom((PointType) newLocation).doubleValue();
updateState(c.getUID(), new QuantityType<>(newDistance / 1000, MetricPrefix.KILO(SIUnits.METRE)));
logger.trace("Region {} center distance from tracker location {} is {}m", regionName, newLocation,
newDistance);
// fire trigger based on distance calculation only in case of pure location message
if (!(message instanceof TransitionMessage)) {
// convert into meters which is the unit of the calculated distance
double radiusMeter = convertToMeters(ConfigHelper.getRegionRadius(c.getConfiguration()));
if (radiusMeter > newDistance) {
triggerRegionChannel(regionName, EVENT_ENTER);
} else {
triggerRegionChannel(regionName, EVENT_LEAVE);
}
}
}
} else {
logger.debug("Skip update as location accuracy is above required threshold: {}>{}", accuracy,
accuracyThreshold);
}
}
private double convertToMeters(double valueToConvert) {
if (unitProvider != null) {
@Nullable
Unit<Length> unit = unitProvider.getUnit(Length.class);
if (unit != null && !SIUnits.METRE.equals(unit)) {
double value = ImperialUnits.YARD.getConverterTo(SIUnits.METRE).convert(valueToConvert);
logger.trace("Value converted: {}yd->{}m", valueToConvert, value);
return value;
} else {
logger.trace("System uses SI measurement units. No conversion is needed.");
}
} else {
logger.trace("No unit provider. Considering region radius {} in meters.", valueToConvert);
}
return valueToConvert;
}
/**
* Update basic channels: batteryLevel, lastLocation, lastReport
*
* @param message Received message.
*/
private void updateBaseChannels(LocationMessage message, String... channels) {
logger.debug("Update base channels for tracker {} from message: {}", trackerId, message);
for (String channel : channels) {
switch (channel) {
case CHANNEL_LAST_REPORT:
State timestamp = message.getTimestamp();
updateState(CHANNEL_LAST_REPORT, timestamp);
logger.trace("{} -> {}", CHANNEL_LAST_REPORT, timestamp);
break;
case CHANNEL_LAST_LOCATION:
State newLocation = message.getTrackerLocation();
updateState(CHANNEL_LAST_LOCATION, newLocation);
logger.trace("{} -> {}", CHANNEL_LAST_LOCATION, newLocation);
break;
case CHANNEL_BATTERY_LEVEL:
State batteryLevel = message.getBatteryLevel();
updateState(CHANNEL_BATTERY_LEVEL, batteryLevel);
logger.trace("{} -> {}", CHANNEL_BATTERY_LEVEL, batteryLevel);
break;
case CHANNEL_GPS_ACCURACY:
State accuracy = message.getGpsAccuracy();
updateState(CHANNEL_GPS_ACCURACY, accuracy);
logger.trace("{} -> {}", CHANNEL_GPS_ACCURACY, accuracy);
break;
}
}
}
/**
* Location message handling.
*
* @param lm Location message
*/
public void updateLocation(LocationMessage lm) {
this.lastMessage = lm;
updateStatus(ThingStatus.ONLINE);
updateChannelsWithLocation(lm);
notificationBroker.sendNotification(lm);
}
/**
* Transition message handling
*
* @param tm Transition message
*/
public void doTransition(TransitionMessage tm) {
this.lastMessage = tm;
updateStatus(ThingStatus.ONLINE);
String regionName = tm.getRegionName();
logger.debug("ConfigHelper transition event received: {}", regionName);
regions.add(regionName);
updateChannelsWithLocation(tm);
updateTriggerChannelsWithTransition(tm);
notificationBroker.sendNotification(tm);
}
/**
* Get notification to return to the tracker (supported by OwnTracks only)
*
* @return List of notifications received from other trackers
*/
public List<LocationMessage> getNotifications() {
return notificationHandler.getNotifications();
}
}

View File

@@ -0,0 +1,138 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.message;
import java.math.BigDecimal;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.PointType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.types.State;
import org.openhab.core.types.UnDefType;
import com.google.gson.annotations.SerializedName;
/**
* The {@link LocationMessage} is a POJO for location messages sent bz trackers.
*
* @author Gabor Bicskei - Initial contribution
*/
@NonNullByDefault
public class LocationMessage {
/**
* Message type
*/
@SerializedName("_type")
private String type = "";
/**
* Tracker ID used to display the initials of a user (iOS,Android/string/optional) required for http mode
*/
@SerializedName("tid")
private String trackerId = "";
/**
* Latitude (iOS, Android/float/meters/required)
*/
@SerializedName("lat")
private BigDecimal latitude = BigDecimal.ZERO;
/**
* Longitude (iOS,Android/float/meters/required)
*/
@SerializedName("lon")
private BigDecimal longitude = BigDecimal.ZERO;
/**
* GPS accuracy
*/
@SerializedName("acc")
private @Nullable BigDecimal gpsAccuracy;
/**
* Battery level (iOS,Android/integer/percent/optional)
*/
@SerializedName("batt")
private Integer batteryLevel = Integer.MIN_VALUE;
/**
* Timestamp at which the event occurred (iOS,Android/integer/epoch/required)
*/
@SerializedName("tst")
private Long timestampMillis = Long.MIN_VALUE;
public String getTrackerId() {
return trackerId.replaceAll("[^a-zA-Z0-9_]", "");
}
/**
* Converts event timestamp onto DateTimeType
*
* @return Conversion result
*/
public State getTimestamp() {
if (timestampMillis != Long.MIN_VALUE) {
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(new Date(timestampMillis * 1000).toInstant(),
ZoneId.systemDefault());
return new DateTimeType(zonedDateTime);
}
return UnDefType.UNDEF;
}
/**
* Converts tracker coordinates into PointType
*
* @return Conversion result
*/
public State getTrackerLocation() {
if (latitude != BigDecimal.ZERO && longitude != BigDecimal.ZERO) {
return new PointType(new DecimalType(latitude), new DecimalType(longitude));
}
return UnDefType.UNDEF;
}
/**
* Converts battery level into DecimalType
*
* @return Conversion result
*/
public State getBatteryLevel() {
if (batteryLevel != Integer.MIN_VALUE) {
return new DecimalType(batteryLevel);
}
return UnDefType.UNDEF;
}
public State getGpsAccuracy() {
if (gpsAccuracy != null) {
return new QuantityType<>(gpsAccuracy.intValue(), SIUnits.METRE);
}
return UnDefType.UNDEF;
}
@Override
public String toString() {
return "LocationMessage [" + ("type=" + type + ", ") + ("trackerId=" + trackerId + ", ")
+ ("latitude=" + latitude + ", ") + ("longitude=" + longitude + ", ")
+ (gpsAccuracy != null ? "gpsAccuracy=" + gpsAccuracy + ", " : "")
+ ("batteryLevel=" + batteryLevel + ", ") + ("timestampMillis=" + timestampMillis) + "]";
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.message;
import java.util.HashMap;
import java.util.Map;
import com.google.gson.Gson;
/**
* Message handling utility
*
* @author Gabor Bicskei - Initial contribution
*/
public class MessageUtil {
/**
* Patterns to identify incoming JSON payload.
*/
private static final String[] PATTERNS = new String[] { ".*\"_type\"\\s*:\\s*\"transition\".*", // transition
".*\"_type\"\\s*:\\s*\"location\".*", // location
};
/**
* Supported message types
*/
private static final Map<String, Class<? extends LocationMessage>> MESSAGE_TYPES = new HashMap<>();
static {
MESSAGE_TYPES.put(PATTERNS[0], TransitionMessage.class);
MESSAGE_TYPES.put(PATTERNS[1], LocationMessage.class);
}
private final Gson gson = new Gson();
/**
* Parses JSON message into an object with type determined by message pattern.
*
* @param json JSON string.
* @return Parsed message POJO or null without pattern match
*/
public LocationMessage fromJson(String json) {
for (String pattern : PATTERNS) {
Class<? extends LocationMessage> c = MESSAGE_TYPES.get(pattern);
if (json.matches(pattern)) {
return gson.fromJson(json, c);
}
}
return null;
}
/**
* Converts object to JSON sting.
*
* @param o Object to convert
* @return JSON string
*/
public String toJson(Object o) {
return gson.toJson(o);
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.message;
import java.util.HashMap;
import java.util.Map;
/**
* Notification broker.
*
* @author Gabor Bicskei - Initial contribution
*/
public class NotificationBroker {
/**
* Handlers
*/
private Map<String, NotificationHandler> handlers = new HashMap<>();
/**
* Register new handler.
*
* @param trackerId Tracker id
* @param handler Notification handler
*/
public void registerHandler(String trackerId, NotificationHandler handler) {
handlers.put(trackerId, handler);
}
public void sendNotification(LocationMessage msg) {
String trackerId = msg.getTrackerId();
handlers.entrySet().stream().filter(e -> !e.getKey().equals(trackerId))
.forEach(e -> e.getValue().handleNotification(msg));
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.message;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Handler for notification messages between trackers.
*
* @author Gabor Bicskei - Initial contribution
*/
public class NotificationHandler {
/**
* Location notifications need to be sent to the own tracker. Only the last location is saved for each tracker
* in the group.
*/
private Map<String, LocationMessage> locationNotifications = new HashMap<>();
/**
* TransitionMessage notification to send out to the own tracker. Notifications are saved in order they were
* received.
*/
private Map<String, List<TransitionMessage>> transitionNotifications = new HashMap<>();
/**
* Handling notification sent by other trackers.
*
* @param msg Notification message.
*/
public void handleNotification(LocationMessage msg) {
synchronized (this) {
String trackerId = msg.getTrackerId();
if (msg instanceof TransitionMessage) {
List<TransitionMessage> transitionMessages = transitionNotifications.computeIfAbsent(trackerId,
k -> new ArrayList<>());
transitionMessages.add((TransitionMessage) msg);
} else {
locationNotifications.put(trackerId, msg);
}
}
}
/**
* Collect all notifications about friend trackers.
*
* @return List of notification messages from friend trackers need to sent out
*/
public List<LocationMessage> getNotifications() {
List<LocationMessage> ret;
synchronized (this) {
ret = new ArrayList<>(locationNotifications.values());
transitionNotifications.values().forEach(ret::addAll);
locationNotifications.clear();
transitionNotifications.clear();
}
return ret;
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.message;
import com.google.gson.annotations.SerializedName;
/**
* TransitionMessage message POJO
*
* @author Gabor Bicskei - Initial contribution
*/
public class TransitionMessage extends LocationMessage {
/**
* Event that triggered the transition (iOS,Android/string/required)
* enter The tracker entered the defined geographical region or BLE Beacon range (iOS)
* leave The tracker left the defined geographical region or BLE Beacon range (iOS)
*/
@SerializedName("event")
String event;
/**
* Name of the waypoint (iOS,Android/string/optional)
*/
@SerializedName("desc")
String regionName;
public String getRegionName() {
return regionName;
}
public String getEvent() {
return event;
}
}

View File

@@ -0,0 +1,89 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.profile;
import static org.openhab.binding.gpstracker.internal.GPSTrackerBindingConstants.CHANNEL_TYPE_REGION;
import java.util.Collection;
import java.util.Locale;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gpstracker.internal.GPSTrackerBindingConstants;
import org.openhab.core.library.CoreItemFactory;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.profiles.*;
import org.openhab.core.thing.type.ChannelType;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.osgi.service.component.annotations.Component;
/**
* The {@link GPSTrackerProfileFactory} class defines and provides switch profile and its type of this binding.
*
* @author Gabor Bicskei - Initial contribution
*/
@NonNullByDefault
@Component
public class GPSTrackerProfileFactory implements ProfileFactory, ProfileAdvisor, ProfileTypeProvider {
/**
* Profile UID for trigger events
*/
static final ProfileTypeUID UID_TRIGGER_SWITCH = new ProfileTypeUID(GPSTrackerBindingConstants.BINDING_ID,
"trigger-geofence");
/**
* Profile type for trigger events
*/
private static final TriggerProfileType TRIGGER_SWITCH_TYPE = ProfileTypeBuilder
.newTrigger(UID_TRIGGER_SWITCH, "Geofence").withSupportedItemTypes(CoreItemFactory.SWITCH)
.withSupportedChannelTypeUIDs(CHANNEL_TYPE_REGION).build();
@Override
public Collection<ProfileTypeUID> getSupportedProfileTypeUIDs() {
return Stream.of(UID_TRIGGER_SWITCH).collect(Collectors.toSet());
}
@Override
public Collection<ProfileType> getProfileTypes(@Nullable Locale locale) {
return Stream.of(TRIGGER_SWITCH_TYPE).collect(Collectors.toSet());
}
@Override
public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(Channel channel, @Nullable String itemType) {
return getSuggestedProfileTypeUID(channel.getChannelTypeUID(), itemType);
}
@Override
public @Nullable ProfileTypeUID getSuggestedProfileTypeUID(ChannelType channelType, @Nullable String itemType) {
return getSuggestedProfileTypeUID(channelType.getUID(), itemType);
}
private @Nullable ProfileTypeUID getSuggestedProfileTypeUID(@Nullable ChannelTypeUID channelTypeUID,
@Nullable String itemType) {
if (CoreItemFactory.SWITCH.equals(itemType) && CHANNEL_TYPE_REGION.equals(channelTypeUID)) {
return UID_TRIGGER_SWITCH;
}
return null;
}
@Override
public @Nullable Profile createProfile(ProfileTypeUID profileTypeUID, ProfileCallback callback,
ProfileContext profileContext) {
if (UID_TRIGGER_SWITCH.equals(profileTypeUID)) {
return new GPSTrackerTriggerSwitchProfile(callback, profileContext);
}
return null;
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.profile;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.gpstracker.internal.config.ConfigHelper;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.profiles.ProfileCallback;
import org.openhab.core.thing.profiles.ProfileContext;
import org.openhab.core.thing.profiles.ProfileTypeUID;
import org.openhab.core.thing.profiles.TriggerProfile;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link GPSTrackerTriggerSwitchProfile} class implements the behavior when being linked to a Switch item.
*
* @author Gabor Bicskei - Initial contribution
*/
@NonNullByDefault
public class GPSTrackerTriggerSwitchProfile implements TriggerProfile {
/**
* Class logger
*/
private final Logger logger = LoggerFactory.getLogger(GPSTrackerTriggerSwitchProfile.class);
/**
* Callback
*/
private ProfileCallback callback;
/**
* Link region name
*/
private String regionName;
/**
* Constructor.
*
* @param callback Callback
* @param context Context
*/
GPSTrackerTriggerSwitchProfile(ProfileCallback callback, ProfileContext context) {
this.callback = callback;
this.regionName = ConfigHelper.getRegionName(context.getConfiguration());
logger.debug("Trigger switch profile created for region {}", regionName);
}
@Override
public ProfileTypeUID getProfileTypeUID() {
return GPSTrackerProfileFactory.UID_TRIGGER_SWITCH;
}
@Override
public void onStateUpdateFromItem(State state) {
}
@Override
public void onTriggerFromHandler(String payload) {
if (payload.startsWith(regionName)) {
OnOffType state = payload.endsWith("enter") ? OnOffType.ON : OnOffType.OFF;
callback.sendCommand(state);
logger.debug("Transition trigger {} handled for region {} by profile: {}", payload, regionName, state);
}
}
}

View File

@@ -0,0 +1,156 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.provider;
import java.io.BufferedReader;
import java.util.Collections;
import java.util.List;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.openhab.binding.gpstracker.internal.discovery.TrackerDiscoveryService;
import org.openhab.binding.gpstracker.internal.handler.TrackerHandler;
import org.openhab.binding.gpstracker.internal.message.LocationMessage;
import org.openhab.binding.gpstracker.internal.message.MessageUtil;
import org.openhab.binding.gpstracker.internal.message.TransitionMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Abstract callback servlet used by the trackers.
*
* @author Gabor Bicskei - Initial contribution
*/
public abstract class AbstractCallbackServlet extends HttpServlet {
private static final long serialVersionUID = -2725161358635927815L;
/**
* Class logger
*/
private final Logger logger = LoggerFactory.getLogger(AbstractCallbackServlet.class);
/**
* Discovery service to handle new trackers
*/
private TrackerDiscoveryService discoveryService;
/**
* Utility to process messages
*/
private MessageUtil messageUtil = new MessageUtil();
/**
* Tracker registry
*/
private TrackerRegistry trackerRegistry;
/**
* Constructor called at binding startup.
*
* @param discoveryService Discovery service for new trackers.
* @param trackerRegistry Tracker handler registry
*/
protected AbstractCallbackServlet(TrackerDiscoveryService discoveryService, TrackerRegistry trackerRegistry) {
this.discoveryService = discoveryService;
this.trackerRegistry = trackerRegistry;
}
protected abstract String getPath();
/**
* Process the HTTP requests from tracker applications
*
* @param req HTTP request
* @param resp HTTP response
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
try {
StringBuilder jb = new StringBuilder();
BufferedReader reader = req.getReader();
String line;
while ((line = reader.readLine()) != null) {
jb.append(line);
}
// clear the whitespaces from the message
String json = jb.toString().replaceAll("\\p{Z}", "");
logger.debug("Post message received from {} tracker: {}", getProvider(), json);
LocationMessage message = messageUtil.fromJson(json);
if (message != null) {
List<? extends LocationMessage> response = processMessage(message);
if (response != null) {
resp.getWriter().append(messageUtil.toJson(response)).flush();
}
}
resp.setStatus(HttpServletResponse.SC_OK);
} catch (Exception e) {
logger.error("Error processing location report:", e);
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
/**
* Process the message received by the servlet. If the tracker is unknown the discovery service is notified
* so that the next search will pop up the new tracker as result.
*
* @param message The message
* @return Response message.
*/
private List<? extends LocationMessage> processMessage(LocationMessage message) {
String trackerId = message.getTrackerId();
if (!trackerId.isEmpty()) {
TrackerHandler recorder = getHandlerById(trackerId);
if (recorder != null) {
if (message instanceof TransitionMessage) {
TransitionMessage tm = (TransitionMessage) message;
recorder.doTransition(tm);
} else {
recorder.updateLocation(message);
}
return recorder.getNotifications();
} else {
logger.debug("There is no handler for tracker {}. Check the inbox for the new tracker.", trackerId);
}
} else {
logger.debug("Message without tracker id. Dropping message. {}", messageUtil.toJson(message));
}
return Collections.emptyList();
}
/**
* Find handler for tracker. If the handler does not exist it is registered with discovery service.
*
* @param trackerId Tracker id.
* @return Handler for tracker.
*/
private TrackerHandler getHandlerById(String trackerId) {
if (trackerId != null) {
TrackerHandler handler = trackerRegistry.getTrackerHandler(trackerId);
if (handler == null) {
// handler was not found - adding the tracker to discovery service.
discoveryService.addTracker(trackerId);
} else {
return handler;
}
}
return null;
}
protected abstract String getProvider();
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.provider;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gpstracker.internal.handler.TrackerHandler;
/**
* Functional interface for checking tracker registration.
*
* @author Gabor Bicskei - Initial contribution
*/
@NonNullByDefault
public interface TrackerRegistry {
/**
* Returns a handler for a given id
*
* @param trackerId the id of the tracker
* @return the handler
*/
@Nullable
TrackerHandler getTrackerHandler(String trackerId);
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.provider.gpslogger;
import org.openhab.binding.gpstracker.internal.discovery.TrackerDiscoveryService;
import org.openhab.binding.gpstracker.internal.provider.AbstractCallbackServlet;
import org.openhab.binding.gpstracker.internal.provider.TrackerRegistry;
/**
* Callback servlet for GPSLogger
*
* @author Gabor Bicskei - Initial contribution
*/
public class GPSLoggerCallbackServlet extends AbstractCallbackServlet {
private static final long serialVersionUID = -6992472786850682196L;
/**
* Servlet path
*/
private static final String CALLBACK_PATH = "/gpstracker/gpslogger";
/**
* Provider name
*/
private static final String PROVIDER = "GPSLogger";
/**
* Constructor called at binding startup.
*
* @param discoveryService Discovery service for new trackers.
* @param trackerRegistry Tracker registry
*/
public GPSLoggerCallbackServlet(TrackerDiscoveryService discoveryService, TrackerRegistry trackerRegistry) {
super(discoveryService, trackerRegistry);
}
@Override
public String getPath() {
return CALLBACK_PATH;
}
@Override
protected String getProvider() {
return PROVIDER;
}
}

View File

@@ -0,0 +1,57 @@
/**
* Copyright (c) 2010-2020 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.gpstracker.internal.provider.owntracks;
import org.openhab.binding.gpstracker.internal.discovery.TrackerDiscoveryService;
import org.openhab.binding.gpstracker.internal.provider.AbstractCallbackServlet;
import org.openhab.binding.gpstracker.internal.provider.TrackerRegistry;
/**
* Callback servlet for OwnTracks trackers
*
* @author Gabor Bicskei - Initial contribution
*/
public class OwnTracksCallbackServlet extends AbstractCallbackServlet {
private static final long serialVersionUID = -4053305903339688036L;
/**
* Servlet path
*/
private static final String CALLBACK_PATH = "/gpstracker/owntracks";
/**
* Provider name
*/
private static final String PROVIDER = "OwnTracks";
/**
* Constructor called at binding startup.
*
* @param discoveryService Discovery service for new trackers.
* @param trackerRegistry Tracker registry
*/
public OwnTracksCallbackServlet(TrackerDiscoveryService discoveryService, TrackerRegistry trackerRegistry) {
super(discoveryService, trackerRegistry);
}
@Override
public String getPath() {
return CALLBACK_PATH;
}
@Override
protected String getProvider() {
return PROVIDER;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="gpstracker" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>GPSTracker Binding</name>
<description>GPS tracking with OwnTracks and GPSLogger support over HTTP</description>
<author>Gabor Bicskei</author>
</binding:binding>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:gpstracker:tracker">
<parameter name="trackerId" type="text" required="true">
<label>Tracker Id</label>
<description>Id configured in tracker application.</description>
<advanced>false</advanced>
</parameter>
</config-description>
<config-description uri="channel-type:gpstracker:distance">
<parameter name="regionName" type="text" required="true">
<label>Region Name</label>
<description>Region name payload for trigger channel event</description>
<advanced>false</advanced>
<limitToOptions>false</limitToOptions>
</parameter>
<parameter name="regionRadius" type="decimal" required="true" min="0">
<label>Region Radius</label>
<description>Region circle radius in m or yd</description>
<default>100</default>
<advanced>false</advanced>
</parameter>
<parameter name="regionCenterLocation" type="text" required="true">
<context>location</context>
<label>Region Center</label>
<description>Location of the region center</description>
<advanced>false</advanced>
</parameter>
<parameter name="accuracyThreshold" type="decimal" required="true" min="0">
<label>Accuracy Threshold</label>
<description>Location accuracy threshold in m or yd (0 to disable)</description>
<default>0</default>
<advanced>false</advanced>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0
https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="profile:gpstracker:trigger-geofence">
<parameter name="regionName" type="text" required="true">
<label>Region Name</label>
<description>Region name to trigger the switch</description>
<advanced>false</advanced>
<limitToOptions>false</limitToOptions>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="gpstracker"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Thing Types -->
<thing-type id="tracker" extensible="regionDistance">
<label>Tracker Device</label>
<description>Device running tracker application</description>
<channels>
<channel id="lastLocation" typeId="system.location"/>
<channel id="batteryLevel" typeId="system.battery-level"/>
<channel id="regionTrigger" typeId="regionTrigger"/>
<channel id="lastReport" typeId="lastReport"/>
<channel id="gpsAccuracy" typeId="gpsAccuracy"/>
</channels>
<config-description-ref uri="thing-type:gpstracker:tracker"/>
</thing-type>
<!-- Channel Types -->
<channel-type id="regionDistance">
<item-type>Number:Length</item-type>
<label>Distance</label>
<description>Distance from region</description>
<state pattern="%.2f %unit%" readOnly="true"/>
<config-description-ref uri="channel-type:gpstracker:distance"/>
</channel-type>
<channel-type id="gpsAccuracy">
<item-type>Number:Length</item-type>
<label>Accuracy</label>
<description>GPS accuracy</description>
<state pattern="%d %unit%" readOnly="true"/>
</channel-type>
<channel-type id="lastReport">
<item-type>DateTime</item-type>
<label>Last Seen</label>
<description>Last report timestamp</description>
<state pattern="%1$tF %1$tR" readOnly="true"/>
</channel-type>
<channel-type id="regionTrigger">
<kind>trigger</kind>
<label>Region Trigger</label>
<description>Trigger channel for entering/leaving regions. Payload is the region name with prefix &gt; for entering
and &lt; for leaving.</description>
<event/>
</channel-type>
</thing:thing-descriptions>