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.sensebox-${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-sensebox" description="Sensebox Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.sensebox/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,158 @@
/**
* 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.sensebox.internal;
import static org.openhab.binding.sensebox.internal.SenseBoxBindingConstants.*;
import java.io.IOException;
import java.util.List;
import java.util.Properties;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.sensebox.internal.dto.SenseBoxData;
import org.openhab.binding.sensebox.internal.dto.SenseBoxDescriptor;
import org.openhab.binding.sensebox.internal.dto.SenseBoxLoc;
import org.openhab.binding.sensebox.internal.dto.SenseBoxLocation;
import org.openhab.binding.sensebox.internal.dto.SenseBoxSensor;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.thing.ThingStatus;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link SenseBoxAPIConnection} is responsible for fetching data from the senseBox API server.
*
* @author Hakan Tandogan - Initial contribution
*/
@NonNullByDefault
public class SenseBoxAPIConnection {
private final Logger logger = LoggerFactory.getLogger(SenseBoxAPIConnection.class);
private final Gson gson = new Gson();
private static final Properties HEADERS = new Properties();
private static final String METHOD = "GET";
private static final int TIMEOUT = 30 * 1000; // 30 seconds
public SenseBoxAPIConnection() {
Version version = FrameworkUtil.getBundle(this.getClass()).getVersion();
HEADERS.put("User-Agent", "openHAB / senseBox binding " + version.toString());
logger.trace("Headers: {}", HEADERS);
}
public SenseBoxData reallyFetchDataFromServer(String senseBoxId) {
String query = SENSEMAP_API_URL_BASE + "/boxes/" + senseBoxId;
// the caching layer does not like null values
SenseBoxData result = new SenseBoxData();
try {
String body = HttpUtil.executeUrl(METHOD, query, HEADERS, null, null, TIMEOUT);
logger.trace("Fetched Data: {}", body);
SenseBoxData parsedData = gson.fromJson(body, SenseBoxData.class);
// Assume all is well at first
parsedData.setStatus(ThingStatus.ONLINE);
// Could perhaps be simplified via triply-nested arrays
// http://stackoverflow.com/questions/36946875/how-can-i-parse-geojson-with-gson
for (SenseBoxLoc loc : parsedData.getLocs()) {
if (loc.getGeometry() != null) {
List<Double> locationData = loc.getGeometry().getData();
if (locationData != null) {
SenseBoxLocation location = new SenseBoxLocation();
if (locationData.size() > 0) {
location.setLongitude(locationData.get(0));
}
if (locationData.size() > 1) {
location.setLatitude(locationData.get(1));
}
if (locationData.size() > 2) {
location.setHeight(locationData.get(2));
}
parsedData.setLocation(location);
}
}
}
for (SenseBoxSensor sensor : parsedData.getSensors()) {
if ("VEML6070".equals(sensor.getSensorType())) {
// "unit" is not nicely comparable, so use sensor type for now
parsedData.setUvIntensity(sensor);
} else if ("SDS 011".equals(sensor.getSensorType())) {
// "unit" is not nicely comparable, neither is type, so use sensor title for now
if ("PM2.5".equals(sensor.getTitle())) {
parsedData.setParticulateMatter2dot5(sensor);
} else if ("PM10".equals(sensor.getTitle())) {
parsedData.setParticulateMatter10(sensor);
} else {
logger.debug("SDS 011 sensor title is {}", sensor.getTitle());
}
} else if ("lx".equals(sensor.getUnit())) {
if (sensor.getLastMeasurement() != null) {
if (!(INVALID_BRIGHTNESS.equals(sensor.getLastMeasurement().getValue()))) {
parsedData.setLuminance(sensor);
}
}
} else if ("Pa".equals(sensor.getUnit()) || "hPa".equals(sensor.getUnit())) {
parsedData.setPressure(sensor);
} else if ("%".equals(sensor.getUnit())) {
parsedData.setHumidity(sensor);
} else if ("°C".equals(sensor.getUnit())) {
parsedData.setTemperature(sensor);
} else {
if (logger.isDebugEnabled()) {
logger.debug(" Sensor: {}", sensor);
logger.debug(" Sensor unit: {}", sensor.getUnit());
logger.debug(" Sensor type: {}", sensor.getSensorType());
logger.debug(" Sensor LM: {}", sensor.getLastMeasurement());
if (sensor.getLastMeasurement() != null) {
logger.debug(" Sensor LM value: {}", sensor.getLastMeasurement().getValue());
logger.debug(" Sensor LM date: '{}'", sensor.getLastMeasurement().getCreatedAt());
}
}
}
}
SenseBoxDescriptor descriptor = new SenseBoxDescriptor();
descriptor.setApiUrl(query);
String image = parsedData.getImage();
if (image != null && !image.isEmpty()) {
descriptor.setImageUrl(SENSEMAP_IMAGE_URL_BASE + "/" + image);
}
descriptor.setMapUrl(SENSEMAP_MAP_URL_BASE + "/explore/" + senseBoxId);
parsedData.setDescriptor(descriptor);
logger.trace("=================================");
result = parsedData;
} catch (IOException e) {
logger.debug("IO problems while fetching data: {} / {}", query, e.getMessage());
result.setStatus(ThingStatus.OFFLINE);
}
return result;
}
}

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.sensebox.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link SenseBoxBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Hakan Tandogan - Initial contribution
*/
@NonNullByDefault
public class SenseBoxBindingConstants {
public static final String BINDING_ID = "sensebox";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_BOX = new ThingTypeUID(BINDING_ID, "box");
// List of all Channel ids
public static final String CHANNEL_LOCATION = "descriptors#location";
public static final String CHANNEL_UV_INTENSITY = "measurements#uvIntensity";
public static final String CHANNEL_ILLUMINANCE = "measurements#illuminance";
public static final String CHANNEL_PRESSURE = "measurements#pressure";
public static final String CHANNEL_HUMIDITY = "measurements#humidity";
public static final String CHANNEL_TEMPERATURE = "measurements#temperature";
public static final String CHANNEL_PARTICULATE_MATTER_2_5 = "measurements#particulateMatter2dot5";
public static final String CHANNEL_PARTICULATE_MATTER_10 = "measurements#particulateMatter10";
public static final String CHANNEL_UV_INTENSITY_LR = "lastReported#uvIntensityLastReported";
public static final String CHANNEL_ILLUMINANCE_LR = "lastReported#illuminanceLastReported";
public static final String CHANNEL_PRESSURE_LR = "lastReported#pressureLastReported";
public static final String CHANNEL_HUMIDITY_LR = "lastReported#humidityLastReported";
public static final String CHANNEL_TEMPERATURE_LR = "lastReported#temperatureLastReported";
public static final String CHANNEL_PARTICULATE_MATTER_2_5_LR = "lastReported#particulateMatter2dot5LastReported";
public static final String CHANNEL_PARTICULATE_MATTER_10_LR = "lastReported#particulateMatter10LastReported";
// List of all Property names
public static final String PROPERTY_NAME = "Name";
public static final String PROPERTY_EXPOSURE = "Exposure";
public static final String PROPERTY_IMAGE_URL = "ImageUrl";
public static final String PROPERTY_MAP_URL = "MapUrl";
// Base URL of the API server
public static final String SENSEMAP_API_URL_BASE = "https://api.opensensemap.org";
public static final String SENSEMAP_IMAGE_URL_BASE = "https://opensensemap.org/userimages";
public static final String SENSEMAP_MAP_URL_BASE = "https://opensensemap.org";
// Minimum timeslice between API requests in seconds
public static final int MINIMUM_UPDATE_INTERVAL = 5 * 60;
// How long do we want each cache entry to be available?
public static final int CACHE_EXPIRY = 10 * 1000; // 10s
// 67108860 is an invalid reading which the API sends us nevertheless
public static final String INVALID_BRIGHTNESS = "67108860";
}

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.sensebox.internal;
import static org.openhab.binding.sensebox.internal.SenseBoxBindingConstants.THING_TYPE_BOX;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sensebox.internal.handler.SenseBoxHandler;
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.annotations.Component;
/**
* The {@link SenseBoxHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Hakan Tandogan - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.sensebox")
@NonNullByDefault
public class SenseBoxHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_BOX);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_BOX)) {
return new SenseBoxHandler(thing);
}
return null;
}
}

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.sensebox.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link SenseBoxConfiguration} is the base class for configuration
* information held by devices and modules
*
* @author Hakan Tandogan - Initial contribution
*/
@NonNullByDefault
public class SenseBoxConfiguration {
private @Nullable String senseBoxId;
private long refreshInterval = 300;
public long getRefreshInterval() {
return refreshInterval;
}
public void setRefreshInterval(long refreshInterval) {
this.refreshInterval = refreshInterval;
}
public @Nullable String getSenseBoxId() {
return senseBoxId;
}
public void setSenseBoxId(String senseBoxId) {
this.senseBoxId = senseBoxId;
}
}

View File

@@ -0,0 +1,205 @@
/**
* 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.sensebox.internal.dto;
import java.util.List;
import org.openhab.core.thing.ThingStatus;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SenseBoxData} holds a de-serialized representation
* of the API response and the data therein...
*
* @author Hakan Tandogan - Initial contribution
*/
public class SenseBoxData {
@SerializedName("_id")
private String id;
@SerializedName("name")
private String name;
@SerializedName("exposure")
private String exposure;
@SerializedName("image")
private String image;
@SerializedName("loc")
private List<SenseBoxLoc> locs;
@SerializedName("sensors")
private List<SenseBoxSensor> sensors;
@Expose(deserialize = false)
private ThingStatus status;
@Expose(deserialize = false)
private SenseBoxDescriptor descriptor;
@Expose(deserialize = false)
private SenseBoxLocation location;
@Expose(deserialize = false)
private SenseBoxSensor uvIntensity;
@Expose(deserialize = false)
private SenseBoxSensor luminance;
@Expose(deserialize = false)
private SenseBoxSensor pressure;
@Expose(deserialize = false)
private SenseBoxSensor humidity;
@Expose(deserialize = false)
private SenseBoxSensor temperature;
@Expose(deserialize = false)
private SenseBoxSensor particulateMatter2dot5;
@Expose(deserialize = false)
private SenseBoxSensor particulateMatter10;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getExposure() {
return exposure;
}
public void setExposure(String exposure) {
this.exposure = exposure;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
public SenseBoxDescriptor getDescriptor() {
return descriptor;
}
public void setDescriptor(SenseBoxDescriptor descriptor) {
this.descriptor = descriptor;
}
public SenseBoxLocation getLocation() {
return location;
}
public void setLocation(SenseBoxLocation location) {
this.location = location;
}
public List<SenseBoxLoc> getLocs() {
return locs;
}
public void setLocs(List<SenseBoxLoc> locs) {
this.locs = locs;
}
public List<SenseBoxSensor> getSensors() {
return sensors;
}
public void setSensors(List<SenseBoxSensor> sensors) {
this.sensors = sensors;
}
public ThingStatus getStatus() {
return status;
}
public void setStatus(ThingStatus status) {
this.status = status;
}
public SenseBoxSensor getUvIntensity() {
return uvIntensity;
}
public void setUvIntensity(SenseBoxSensor uvIntensity) {
this.uvIntensity = uvIntensity;
}
public SenseBoxSensor getLuminance() {
return luminance;
}
public void setLuminance(SenseBoxSensor luminance) {
this.luminance = luminance;
}
public SenseBoxSensor getPressure() {
return pressure;
}
public void setPressure(SenseBoxSensor pressure) {
this.pressure = pressure;
}
public SenseBoxSensor getHumidity() {
return humidity;
}
public void setHumidity(SenseBoxSensor humidity) {
this.humidity = humidity;
}
public SenseBoxSensor getTemperature() {
return temperature;
}
public void setTemperature(SenseBoxSensor temperature) {
this.temperature = temperature;
}
public SenseBoxSensor getParticulateMatter2dot5() {
return particulateMatter2dot5;
}
public void setParticulateMatter2dot5(SenseBoxSensor particulateMatter2dot5) {
this.particulateMatter2dot5 = particulateMatter2dot5;
}
public SenseBoxSensor getParticulateMatter10() {
return particulateMatter10;
}
public void setParticulateMatter10(SenseBoxSensor particulateMatter10) {
this.particulateMatter10 = particulateMatter10;
}
}

View File

@@ -0,0 +1,52 @@
/**
* 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.sensebox.internal.dto;
/**
* The {@link SenseBoxDescriptor} holds a de-serialized representation
* of the API response and the data therein...
*
* @author Hakan Tandogan - Initial contribution
*/
public class SenseBoxDescriptor {
private String apiUrl;
private String imageUrl;
private String mapUrl;
public String getApiUrl() {
return apiUrl;
}
public void setApiUrl(String apiUrl) {
this.apiUrl = apiUrl;
}
public String getImageUrl() {
return imageUrl;
}
public void setImageUrl(String imageUrl) {
this.imageUrl = imageUrl;
}
public String getMapUrl() {
return mapUrl;
}
public void setMapUrl(String mapUrl) {
this.mapUrl = mapUrl;
}
}

View File

@@ -0,0 +1,37 @@
/**
* 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.sensebox.internal.dto;
import java.util.List;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SenseBoxGeometry} holds a de-serialized representation
* of the API response and the data therein...
*
* @author Hakan Tandogan - Initial contribution
*/
public class SenseBoxGeometry {
@SerializedName("coordinates")
private List<Double> data;
public List<Double> getData() {
return data;
}
public void setData(List<Double> data) {
this.data = data;
}
}

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.sensebox.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SenseBoxLoc} holds a de-serialized representation
* of the API response and the data therein...
*
* @author Hakan Tandogan - Initial contribution
*/
public class SenseBoxLoc {
@SerializedName("geometry")
private SenseBoxGeometry geometry;
public SenseBoxGeometry getGeometry() {
return geometry;
}
public void setGeometry(SenseBoxGeometry geometry) {
this.geometry = geometry;
}
}

View File

@@ -0,0 +1,52 @@
/**
* 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.sensebox.internal.dto;
/**
* The {@link SenseBoxLocation} holds a de-serialized representation
* of the API response and the data therein...
*
* @author Hakan Tandogan - Initial contribution
*/
public class SenseBoxLocation {
private double latitude;
private double longitude;
private double height;
public double getLatitude() {
return latitude;
}
public void setLatitude(double latitude) {
this.latitude = latitude;
}
public double getLongitude() {
return longitude;
}
public void setLongitude(double longitude) {
this.longitude = longitude;
}
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
}

View File

@@ -0,0 +1,46 @@
/**
* 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.sensebox.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SenseBoxMeasurement} holds a de-serialized representation
* of the API response and the data therein...
*
* @author Hakan Tandogan - Initial contribution
*/
public class SenseBoxMeasurement {
@SerializedName("value")
private String value;
@SerializedName("createdAt")
private String createdAt;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getCreatedAt() {
return createdAt;
}
public void setCreatedAt(String createdAt) {
this.createdAt = createdAt;
}
}

View File

@@ -0,0 +1,92 @@
/**
* 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.sensebox.internal.dto;
import com.google.gson.annotations.SerializedName;
/**
* The {@link SenseBoxSensor} holds a de-serialized representation
* of the API response and the data therein...
*
* @author Hakan Tandogan - Initial contribution
*/
public class SenseBoxSensor {
@SerializedName("_id")
private String id;
@SerializedName("title")
private String title;
@SerializedName("unit")
private String unit;
@SerializedName("icon")
private String icon;
@SerializedName("sensorType")
private String sensorType;
@SerializedName("lastMeasurement")
private SenseBoxMeasurement lastMeasurement;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getUnit() {
// the uom library uses the 'MICRO SIGN', so if we encounter the GREEK SMALL LETTER MU,
// replace it with the proper representation.
return unit != null ? unit.replaceAll("\u03bc", "\u00b5") : "";
}
public void setUnit(String unit) {
this.unit = unit;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getSensorType() {
return sensorType;
}
public void setSensorType(String sensorType) {
this.sensorType = sensorType;
}
public SenseBoxMeasurement getLastMeasurement() {
return lastMeasurement;
}
public void setLastMeasurement(SenseBoxMeasurement lastMeasurement) {
this.lastMeasurement = lastMeasurement;
}
}

View File

@@ -0,0 +1,301 @@
/**
* 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.sensebox.internal.handler;
import static org.openhab.binding.sensebox.internal.SenseBoxBindingConstants.*;
import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sensebox.internal.SenseBoxAPIConnection;
import org.openhab.binding.sensebox.internal.config.SenseBoxConfiguration;
import org.openhab.binding.sensebox.internal.dto.SenseBoxData;
import org.openhab.binding.sensebox.internal.dto.SenseBoxLocation;
import org.openhab.binding.sensebox.internal.dto.SenseBoxSensor;
import org.openhab.core.cache.ExpiringCacheMap;
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.MetricPrefix;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
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 SenseBoxHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Hakan Tandogan - Initial contribution
* @author Hakan Tandogan - Ignore incorrect data for brightness readings
* @author Hakan Tandogan - Changed use of caching utils to ESH ExpiringCacheMap
* @author Hakan Tandogan - Unit of Measurement support
*/
@NonNullByDefault
public class SenseBoxHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(SenseBoxHandler.class);
protected @NonNullByDefault({}) SenseBoxConfiguration thingConfiguration;
private @Nullable SenseBoxData data;
private @Nullable ScheduledFuture<?> refreshJob;
private static final BigDecimal ONEHUNDRED = BigDecimal.valueOf(100l);
private static final String CACHE_KEY_DATA = "DATA";
private final ExpiringCacheMap<String, SenseBoxData> cache = new ExpiringCacheMap<>(CACHE_EXPIRY);
private final SenseBoxAPIConnection connection = new SenseBoxAPIConnection();
public SenseBoxHandler(Thing thing) {
super(thing);
}
@Override
public void initialize() {
logger.debug("Start initializing!");
thingConfiguration = getConfigAs(SenseBoxConfiguration.class);
String senseBoxId = thingConfiguration.getSenseBoxId();
logger.debug("Thing Configuration {} initialized {}", getThing().getUID(), senseBoxId);
String offlineReason = "";
boolean validConfig = true;
if (senseBoxId == null || senseBoxId.trim().isEmpty()) {
offlineReason = "senseBox ID is mandatory and must be configured";
validConfig = false;
}
if (thingConfiguration.getRefreshInterval() < MINIMUM_UPDATE_INTERVAL) {
logger.warn("Refresh interval is much too small, setting to default of {} seconds",
MINIMUM_UPDATE_INTERVAL);
thingConfiguration.setRefreshInterval(MINIMUM_UPDATE_INTERVAL);
}
if (senseBoxId != null && validConfig) {
cache.put(CACHE_KEY_DATA, () -> {
return connection.reallyFetchDataFromServer(senseBoxId);
});
updateStatus(ThingStatus.UNKNOWN);
startAutomaticRefresh();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, offlineReason);
}
logger.debug("Thing {} initialized {}", getThing().getUID(), getThing().getStatus());
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
data = fetchData();
if (data != null && ThingStatus.ONLINE == data.getStatus()) {
publishDataForChannel(channelUID);
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
} else {
logger.debug("Unsupported command {}! Supported commands: REFRESH", command);
}
}
@Override
public void dispose() {
stopAutomaticRefresh();
}
private void stopAutomaticRefresh() {
if (refreshJob != null) {
refreshJob.cancel(true);
}
}
private void publishData() {
logger.debug("Refreshing data for box {}, scheduled after {} seconds...", thingConfiguration.getSenseBoxId(),
thingConfiguration.getRefreshInterval());
data = fetchData();
if (data != null && ThingStatus.ONLINE == data.getStatus()) {
publishProperties();
publishChannels();
updateStatus(ThingStatus.ONLINE);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
}
};
private void startAutomaticRefresh() {
stopAutomaticRefresh();
refreshJob = scheduler.scheduleWithFixedDelay(this::publishData, 0, thingConfiguration.getRefreshInterval(),
TimeUnit.SECONDS);
}
private @Nullable SenseBoxData fetchData() {
return cache.get(CACHE_KEY_DATA);
}
private void publishProperties() {
SenseBoxData localData = data;
if (localData != null) {
Map<String, String> properties = editProperties();
properties.put(PROPERTY_NAME, localData.getName());
properties.put(PROPERTY_EXPOSURE, localData.getExposure());
properties.put(PROPERTY_IMAGE_URL, localData.getDescriptor().getImageUrl());
properties.put(PROPERTY_MAP_URL, localData.getDescriptor().getMapUrl());
updateProperties(properties);
}
}
private void publishChannels() {
thing.getChannels().forEach(channel -> publishDataForChannel(channel.getUID()));
}
private void publishDataForChannel(ChannelUID channelUID) {
SenseBoxData localData = data;
if (localData != null && isLinked(channelUID)) {
switch (channelUID.getId()) {
case CHANNEL_LOCATION:
updateState(channelUID, locationFromData(localData.getLocation()));
break;
case CHANNEL_UV_INTENSITY:
updateState(channelUID, decimalFromSensor(localData.getUvIntensity()));
break;
case CHANNEL_ILLUMINANCE:
updateState(channelUID, decimalFromSensor(localData.getLuminance()));
break;
case CHANNEL_PRESSURE:
updateState(channelUID, decimalFromSensor(localData.getPressure()));
break;
case CHANNEL_HUMIDITY:
updateState(channelUID, decimalFromSensor(localData.getHumidity()));
break;
case CHANNEL_TEMPERATURE:
updateState(channelUID, decimalFromSensor(localData.getTemperature()));
break;
case CHANNEL_PARTICULATE_MATTER_2_5:
updateState(channelUID, decimalFromSensor(localData.getParticulateMatter2dot5()));
break;
case CHANNEL_PARTICULATE_MATTER_10:
updateState(channelUID, decimalFromSensor(localData.getParticulateMatter10()));
break;
case CHANNEL_UV_INTENSITY_LR:
updateState(channelUID, dateTimeFromSensor(localData.getUvIntensity()));
break;
case CHANNEL_ILLUMINANCE_LR:
updateState(channelUID, dateTimeFromSensor(localData.getLuminance()));
break;
case CHANNEL_PRESSURE_LR:
updateState(channelUID, dateTimeFromSensor(localData.getPressure()));
break;
case CHANNEL_HUMIDITY_LR:
updateState(channelUID, dateTimeFromSensor(localData.getHumidity()));
break;
case CHANNEL_TEMPERATURE_LR:
updateState(channelUID, dateTimeFromSensor(localData.getTemperature()));
break;
case CHANNEL_PARTICULATE_MATTER_2_5_LR:
updateState(channelUID, dateTimeFromSensor(localData.getParticulateMatter2dot5()));
break;
case CHANNEL_PARTICULATE_MATTER_10_LR:
updateState(channelUID, dateTimeFromSensor(localData.getParticulateMatter10()));
break;
default:
logger.debug("Command received for an unknown channel: {}", channelUID.getId());
break;
}
}
}
private State dateTimeFromSensor(@Nullable SenseBoxSensor sensorData) {
State result = UnDefType.UNDEF;
if (sensorData != null && sensorData.getLastMeasurement() != null
&& sensorData.getLastMeasurement().getCreatedAt() != null
&& !sensorData.getLastMeasurement().getCreatedAt().isEmpty()) {
result = new DateTimeType(sensorData.getLastMeasurement().getCreatedAt());
}
return result;
}
private State decimalFromSensor(@Nullable SenseBoxSensor sensorData) {
State result = UnDefType.UNDEF;
if (sensorData != null && sensorData.getLastMeasurement() != null
&& sensorData.getLastMeasurement().getValue() != null
&& !sensorData.getLastMeasurement().getValue().isEmpty()) {
logger.debug("About to determine quantity for {} / {}", sensorData.getLastMeasurement().getValue(),
sensorData.getUnit());
BigDecimal bd = new BigDecimal(sensorData.getLastMeasurement().getValue());
switch (sensorData.getUnit()) {
case "%":
result = new QuantityType<>(bd, SmartHomeUnits.PERCENT);
break;
case "°C":
result = new QuantityType<>(bd, SIUnits.CELSIUS);
break;
case "Pa":
result = new QuantityType<>(bd, SIUnits.PASCAL);
break;
case "hPa":
if (BigDecimal.valueOf(10000l).compareTo(bd) < 0) {
// Some stations report measurements in Pascal, but send 'hPa' as units...
bd = bd.divide(ONEHUNDRED);
}
result = new QuantityType<>(bd, MetricPrefix.HECTO(SIUnits.PASCAL));
break;
case "lx":
result = new QuantityType<>(bd, SmartHomeUnits.LUX);
break;
case "\u00b5g/m³":
result = new QuantityType<>(bd, SmartHomeUnits.MICROGRAM_PER_CUBICMETRE);
break;
case "\u00b5W/cm²":
result = new QuantityType<>(bd, SmartHomeUnits.MICROWATT_PER_SQUARE_CENTIMETRE);
break;
default:
// The data provider might have configured some unknown unit, accept at least the
// measurement
logger.debug("Could not determine unit for '{}', using default", sensorData.getUnit());
result = new QuantityType<>(bd, SmartHomeUnits.ONE);
}
logger.debug("State: '{}'", result);
}
return result;
}
private State locationFromData(@Nullable SenseBoxLocation locationData) {
State result = UnDefType.UNDEF;
if (locationData != null) {
result = new PointType(new DecimalType(locationData.getLatitude()),
new DecimalType(locationData.getLongitude()), new DecimalType(locationData.getHeight()));
}
return result;
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="sensebox" 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>senseBox Binding</name>
<description>The senseBox binding imports data from the senseBox Citizen Science project.</description>
<author>Hakan Tandoğan</author>
</binding:binding>

View File

@@ -0,0 +1,20 @@
<?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:sensebox:config">
<parameter name="senseBoxId" type="text" required="true">
<label>Box ID</label>
<description>Id of the box, can be found on the map details.</description>
</parameter>
<parameter name="refreshInterval" type="integer" min="300" max="86400" unit="s">
<label>Refresh Interval</label>
<description>Refresh interval for senseBox data.</description>
<default>300</default>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,51 @@
# binding
binding.sensebox.description = Das senseBox Binding importiert Daten aus dem senseBox Citizen Science-Projekt.
# thing types
thing-type.sensebox.box.label = senseBox
thing-type.sensebox.box.description = Ein senseBox Sensor.
# thing type config description
thing-type.config.sensebox.sensor.senseBoxId.label = senseBox ID
thing-type.config.sensebox.sensor.senseBoxId.description = Die ID des senseBox Sensors.
thing-type.config.sensebox.sensor.refreshInterval.label = Abfrageintervall
thing-type.config.sensebox.sensor.refreshInterval.description = Intervall zur Abfrage der senseBox Daten.
# channel group types
channel-group-type.sensebox.descriptors.label = senseBox Beschreibung
channel-group-type.sensebox.descriptors.description = Fasst Daten der senseBox zusammen.
channel-group-type.sensebox.measurements.label = Messwerte
channel-group-type.sensebox.measurements.description = Fasst Messwerte zusammen.
channel-group-type.sensebox.lastReported.label = Letzte Messung
channel-group-type.sensebox.lastReported.description = Fasst Zeitpunkt der Messwerte zusammen.
# channel types
channel-type.sensebox.uvIntensity.label = UV-Intensität
channel-type.sensebox.uvIntensity.description = Aktuelle UV-Intensität.
channel-type.sensebox.uvIntensityLastReported.label = Letzte Messung UV-Intensität
channel-type.sensebox.uvIntensityLastReported.description = Zeigt den Zeitpunkt der letzten Messung der UV-Intensität an.
channel-type.sensebox.uvIntensityLastReported.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
channel-type.sensebox.illuminance.label = Beleuchtungsstärke
channel-type.sensebox.illuminance.description = Aktuelle Beleuchtungsstärke.
channel-type.sensebox.illuminanceLastReported.label = Letzte Messung Beleuchtungsstärke
channel-type.sensebox.illuminanceLastReported.description = Zeigt den Zeitpunkt der letzten Messung der Beleuchtungsstärke an.
channel-type.sensebox.illuminanceLastReported.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
channel-type.sensebox.pressureLastReported.label = Letzte Messung Luftdrucks
channel-type.sensebox.pressureLastReported.description = Zeigt den Zeitpunkt der letzten Messung des Luftdrucks an.
channel-type.sensebox.pressureLastReported.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
channel-type.sensebox.humidityLastReported.label = Letzte Messung Luftfeuchtigkeit
channel-type.sensebox.humidityLastReported.description = Zeigt den Zeitpunkt der letzten Messung der Luftfeuchtigkeit an.
channel-type.sensebox.humidityLastReported.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
channel-type.sensebox.temperatureLastReported.label = Letzte Messung Außentemperatur
channel-type.sensebox.temperatureLastReported.description = Zeigt den Zeitpunkt der letzten Messung der Außentemperatur an.
channel-type.sensebox.temperatureLastReported.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
channel-type.sensebox.particulateMatter2dot5.label = Feinstaub - PM2.5
channel-type.sensebox.particulateMatter2dot5.description = Aktuelle Dichte von Teilchen mit Partikelgröße unter 2,5 µm.
channel-type.sensebox.particulateMatter2dot5LastReported.label = Letzte Messung PM2.5
channel-type.sensebox.particulateMatter2dot5LastReported.description = Zeigt den Zeitpunkt der letzten Messung von PM2.5 an.
channel-type.sensebox.particulateMatter2dot5LastReported.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS
channel-type.sensebox.particulateMatter10.label = Feinstaub - PM10
channel-type.sensebox.particulateMatter10.description = Aktuelle Dichte von Teilchen mit Partikelgröße unter 10 µm.
channel-type.sensebox.particulateMatter10LastReported.label = Letzte Messung PM10
channel-type.sensebox.particulateMatter10LastReported.description = Zeigt den Zeitpunkt der letzten Messung von PM10 an.
channel-type.sensebox.particulateMatter10LastReported.state.pattern = %1$td.%1$tm.%1$tY %1$tH:%1$tM:%1$tS

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="sensebox"
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">
<!-- This thing represents the Arduino box that sends data to api.opensensebox.org . -->
<thing-type id="box">
<label>senseBox</label>
<description>This is a senseBox sensor.</description>
<channel-groups>
<channel-group typeId="descriptors" id="descriptors"/>
<channel-group typeId="measurements" id="measurements"/>
<channel-group typeId="lastReported" id="lastReported"/>
</channel-groups>
<representation-property>senseBoxId</representation-property>
<config-description-ref uri="thing-type:sensebox:config"/>
</thing-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="sensebox"
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">
<!-- Groups to better manage link display -->
<channel-group-type id="descriptors">
<label>Descriptors</label>
<description>Box descriptors like Location, description, etc.</description>
<channels>
<channel id="location" typeId="system.location"/>
</channels>
</channel-group-type>
<channel-group-type id="measurements">
<label>Measurements</label>
<description>Measurements as fetched from the API.</description>
<channels>
<channel id="uvIntensity" typeId="uvIntensity"/>
<channel id="illuminance" typeId="illuminance"/>
<channel id="pressure" typeId="system.barometric-pressure"/>
<channel id="humidity" typeId="system.atmospheric-humidity"/>
<channel id="temperature" typeId="system.outdoor-temperature"/>
<channel id="particulateMatter2dot5" typeId="particulateMatter2dot5"/>
<channel id="particulateMatter10" typeId="particulateMatter10"/>
</channels>
</channel-group-type>
<channel-group-type id="lastReported">
<label>Last Reported</label>
<description>Timestamps when a measurement was last reported.</description>
<channels>
<channel id="uvIntensityLastReported" typeId="uvIntensityLastReported"/>
<channel id="illuminanceLastReported" typeId="illuminanceLastReported"/>
<channel id="pressureLastReported" typeId="pressureLastReported"/>
<channel id="humidityLastReported" typeId="humidityLastReported"/>
<channel id="temperatureLastReported" typeId="temperatureLastReported"/>
<channel id="particulateMatter2dot5LastReported" typeId="particulateMatter2dot5LastReported"/>
<channel id="particulateMatter10LastReported" typeId="particulateMatter10LastReported"/>
</channels>
</channel-group-type>
<!-- The channels themselves -->
<!--
"title": "UV-Intensität",
"unit": "µW/cm²",
"sensorType": "VEML6070",
-->
<channel-type id="uvIntensity">
<item-type>Number:Intensity</item-type>
<label>UV Intensity</label>
<description>Current UV intensity.</description>
<state readOnly="true" pattern="%.2f µW/cm²"></state>
</channel-type>
<channel-type id="uvIntensityLastReported" advanced="true">
<item-type>DateTime</item-type>
<label>UV Intensity Measurement Time</label>
<description>Timestamp when data was measured.</description>
<state readOnly="true"/>
</channel-type>
<!--
"title": "Beleuchtungsstärke",
"unit": "lx",
"sensorType": "TSL45315",
-->
<channel-type id="illuminance">
<item-type>Number:Illuminance</item-type>
<label>Illuminance</label>
<description>Current illuminance.</description>
<state readOnly="true" pattern="%.2f lx"></state>
</channel-type>
<channel-type id="illuminanceLastReported" advanced="true">
<item-type>DateTime</item-type>
<label>Illuminance Measurement Time</label>
<description>Timestamp when data was measured.</description>
<state readOnly="true"/>
</channel-type>
<!--
"title": "Luftdruck",
"unit": "hPa",
"sensorType": "BMP280",
-->
<channel-type id="pressureLastReported" advanced="true">
<item-type>DateTime</item-type>
<label>Pressure Measurement Time</label>
<description>Timestamp when data was measured.</description>
<state readOnly="true"/>
</channel-type>
<!--
"title": "rel. Luftfeuchte",
"unit": "%",
"sensorType": "HDC1008",
-->
<channel-type id="humidityLastReported" advanced="true">
<item-type>DateTime</item-type>
<label>Humidity Measurement Time</label>
<description>Timestamp when data was measured.</description>
<state readOnly="true"/>
</channel-type>
<!--
"title": "Temperatur",
"unit": "°C",
"sensorType": "HDC1008",
-->
<channel-type id="temperatureLastReported" advanced="true">
<item-type>DateTime</item-type>
<label>Temperature Measurement Time</label>
<description>Timestamp when data was measured.</description>
<state readOnly="true"/>
</channel-type>
<!--
"title": "PM2.5",
"unit": "µg/m³",
"sensorType": "SDS 011",
-->
<channel-type id="particulateMatter2dot5">
<item-type>Number:Density</item-type>
<label>Particulate Matter - PM2.5</label>
<description>Current density of particles less than 2.5 µm in diameter.</description>
<state readOnly="true" pattern="%.2f µg/m³">
</state>
</channel-type>
<channel-type id="particulateMatter2dot5LastReported" advanced="true">
<item-type>DateTime</item-type>
<label>Particulate Matter 2.5 Measurement Time</label>
<description>Timestamp when data was measured.</description>
<state readOnly="true"/>
</channel-type>
<!--
"title": "PM10",
"unit": "µg/m³",
"sensorType": "SDS 011",
-->
<channel-type id="particulateMatter10">
<item-type>Number:Density</item-type>
<label>Particulate Matter - PM10</label>
<description>Current density of particles less than 10 µm in diameter.</description>
<state readOnly="true" pattern="%.2f µg/m³">
</state>
</channel-type>
<channel-type id="particulateMatter10LastReported" advanced="true">
<item-type>DateTime</item-type>
<label>Particulate Matter 10 Measurement Time</label>
<description>Timestamp when data was measured.</description>
<state readOnly="true"/>
</channel-type>
</thing:thing-descriptions>