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

View File

@@ -0,0 +1,563 @@
/**
* 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.meteoblue.internal;
import java.awt.Color;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.TimeZone;
import javax.imageio.ImageIO;
import org.openhab.binding.meteoblue.internal.json.JsonDataDay;
import org.openhab.binding.meteoblue.internal.json.JsonMetadata;
import org.openhab.binding.meteoblue.internal.json.JsonUnits;
import org.openhab.core.config.core.ConfigConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Model of forecast data.
*
* @author Chris Carman - Initial contribution
*/
public class Forecast {
private final Logger logger = LoggerFactory.getLogger(Forecast.class);
// metadata fields
private Double latitude;
private Double longitude;
private Integer height;
private String timeZoneAbbreviation;
// units fields
private String timeUnits;
private String predictabilityUnits;
private String precipitationProbabilityUnits;
private String pressureUnits;
private String relativeHumidityUnits;
private String temperatureUnits;
private String windDirectionUnits;
private String precipitationUnits;
private String windSpeedUnits;
// data_day fields
private Calendar forecastDate;
private Integer pictocode;
private Integer UVIndex;
private Double minTemperature;
private Double maxTemperature;
private Double meanTemperature;
private Double feltTemperatureMin;
private Double feltTemperatureMax;
private Integer windDirection;
private Integer precipitationProbability;
private String rainSpot;
private Integer predictabilityClass;
private Integer predictability;
private Double precipitation;
private Double snowFraction;
private Integer minSeaLevelPressure;
private Integer maxSeaLevelPressure;
private Integer meanSeaLevelPressure;
private Double minWindSpeed;
private Double maxWindSpeed;
private Double meanWindSpeed;
private Integer relativeHumidityMin;
private Integer relativeHumidityMax;
private Integer relativeHumidityMean;
private Double convectivePrecipitation;
private Double precipitationHours;
private Double humidityGreater90Hours;
// derived fields
private String cardinalWindDirection;
private String iconName;
private Image icon;
private Image rainArea;
private Double snowFall;
public Forecast(int whichDay, JsonMetadata metadata, JsonUnits units, JsonDataDay dataDay)
throws IllegalArgumentException {
if (metadata == null) {
throw new IllegalArgumentException("Received no metadata information.");
}
if (dataDay == null) {
throw new IllegalArgumentException("Received no data_day information.");
}
if (whichDay > dataDay.getTime().length - 1) {
throw new IndexOutOfBoundsException("No data received for day " + whichDay);
}
// extract the metadata fields
latitude = metadata.getLatitude();
longitude = metadata.getLongitude();
height = metadata.getHeight();
timeZoneAbbreviation = metadata.getTimeZoneAbbreviation();
logger.trace("Metadata:");
logger.trace(" Latitude: {}", latitude);
logger.trace(" Longitude: {}", longitude);
logger.trace(" Height: {}", height);
logger.trace(" TZ Abbrev: {}", timeZoneAbbreviation);
// extract the units fields
timeUnits = units.getTime();
predictabilityUnits = units.getPredictability();
precipitationProbabilityUnits = units.getPrecipitationProbability();
pressureUnits = units.getPressure();
relativeHumidityUnits = units.getRelativeHumidity();
temperatureUnits = units.getTemperature();
windDirectionUnits = units.getWindDirection();
precipitationUnits = units.getPrecipitation();
windSpeedUnits = units.getWindSpeed();
logger.trace("Units:");
logger.trace(" Time: {}", timeUnits);
logger.trace(" Predictability: {}", predictabilityUnits);
logger.trace(" Precipitation Probability: {}", precipitationProbabilityUnits);
logger.trace(" Pressure: {}", pressureUnits);
logger.trace(" Relative Humidity: {}", relativeHumidityUnits);
logger.trace(" Temperature: {}", temperatureUnits);
logger.trace(" Wind Direction: {}", windDirectionUnits);
logger.trace(" Precipitation: {}", precipitationUnits);
logger.trace(" Wind Speed: {}", windSpeedUnits);
// extract the data_day fields
String timeString = dataDay.getTime()[whichDay];
try {
Calendar c = Calendar.getInstance(TimeZone.getTimeZone(timeZoneAbbreviation));
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
c.setTime(formatter.parse(timeString));
forecastDate = c;
} catch (ParseException e) {
logger.debug("Failed to parse the value '{}' as a date.", timeString);
}
pictocode = dataDay.getPictocode()[whichDay];
iconName = getIconNameForPictocode(pictocode);
icon = loadImageIcon(iconName);
UVIndex = dataDay.getUVIndex()[whichDay];
maxTemperature = dataDay.getTemperatureMax()[whichDay];
minTemperature = dataDay.getTemperatureMin()[whichDay];
meanTemperature = dataDay.getTemperatureMean()[whichDay];
feltTemperatureMax = dataDay.getFeltTemperatureMax()[whichDay];
feltTemperatureMin = dataDay.getFeltTemperatureMin()[whichDay];
windDirection = dataDay.getWindDirection()[whichDay];
cardinalWindDirection = getCardinalDirection(windDirection);
precipitationProbability = dataDay.getPrecipitationProbability()[whichDay];
rainSpot = dataDay.getRainspot()[whichDay];
rainArea = generateRainAreaImage();
predictabilityClass = dataDay.getPredictabilityClass()[whichDay];
predictability = dataDay.getPredictability()[whichDay];
precipitation = dataDay.getPrecipitation()[whichDay];
snowFraction = dataDay.getSnowFraction()[whichDay];
maxSeaLevelPressure = dataDay.getSeaLevelPressureMax()[whichDay];
minSeaLevelPressure = dataDay.getSeaLevelPressureMin()[whichDay];
meanSeaLevelPressure = dataDay.getSeaLevelPressureMean()[whichDay];
maxWindSpeed = dataDay.getWindSpeedMax()[whichDay];
meanWindSpeed = dataDay.getWindSpeedMean()[whichDay];
minWindSpeed = dataDay.getWindSpeedMin()[whichDay];
relativeHumidityMax = dataDay.getRelativeHumidityMax()[whichDay];
relativeHumidityMin = dataDay.getRelativeHumidityMin()[whichDay];
relativeHumidityMean = dataDay.getRelativeHumidityMean()[whichDay];
convectivePrecipitation = dataDay.getConvectivePrecipitation()[whichDay];
snowFall = snowFraction * precipitation;
precipitationHours = dataDay.getPrecipitationHours()[whichDay];
humidityGreater90Hours = dataDay.getHumidityGreater90Hours()[whichDay];
logger.trace("DataDay:");
logger.trace(" Time: {}", forecastDate);
logger.trace(" Pictocode: {}", pictocode);
logger.trace(" Icon: {}", icon);
logger.trace(" UV Index: {}", UVIndex);
logger.trace(" Max Temperature: {}", maxTemperature);
logger.trace(" Min Temperature: {}", minTemperature);
logger.trace(" Mean Temperature: {}", meanTemperature);
logger.trace(" Felt Temperature Max: {}", feltTemperatureMax);
logger.trace(" Felt Temperature Min: {}", feltTemperatureMin);
logger.trace(" Wind Direction: {}", windDirection);
logger.trace(" Precipitation Probability: {}", precipitationProbability);
logger.trace(" Rainspot: {}", rainSpot);
logger.trace(" Rain Area: {}", rainArea);
logger.trace(" Predictability Class: {}", predictabilityClass);
logger.trace(" Predictability: {}", predictability);
logger.trace(" Precipitation: {}", precipitation);
logger.trace(" Snow Fraction: {}", snowFraction);
logger.trace(" Max Sea-level Pressure: {}", maxSeaLevelPressure);
logger.trace(" Min Sea-level Pressure: {}", minSeaLevelPressure);
logger.trace(" Mean Sea-level Pressure: {}", meanSeaLevelPressure);
logger.trace(" Max Wind Speed: {}", maxWindSpeed);
logger.trace(" Mean Wind Speed: {}", meanWindSpeed);
logger.trace(" Min Wind Speed: {}", minWindSpeed);
logger.trace(" Relative Humidity Max: {}", relativeHumidityMax);
logger.trace(" Relative Humidity Min: {}", relativeHumidityMin);
logger.trace(" Relative Humidity Mean: {}", relativeHumidityMean);
logger.trace(" Convective Precipitation: {}", convectivePrecipitation);
logger.trace(" Snowfall: {}", snowFall);
logger.trace(" Precipitation Hours: {}", precipitationHours);
logger.trace(" Humidity > 90 Hours: {}", humidityGreater90Hours);
}
// generic getter
public Object getDatapoint(String datapointName) {
if (datapointName.equals("condition")) {
return String.valueOf(pictocode);
}
try {
Field field = getClass().getDeclaredField(datapointName);
field.setAccessible(true);
return field.get(this);
} catch (NoSuchFieldException e) {
logger.warn("Unable to find a datapoint declared for the name '{}'", datapointName);
return null;
} catch (Exception e) {
logger.warn("An unexpected error occurred while trying to access the datapoint '{}'", datapointName, e);
return null;
}
}
// metadata getters
public Integer getHeight() {
return height;
}
// units getters
public String getTimeUnits() {
return timeUnits;
}
public String getPredictabilityUnits() {
return predictabilityUnits;
}
public String getPrecipitationProbabilityUnits() {
return precipitationProbabilityUnits;
}
public String getPressureUnits() {
return pressureUnits;
}
public String getRelativeHumidityUnits() {
return relativeHumidityUnits;
}
public String getTemperatureUnits() {
return temperatureUnits;
}
public String getWindDirectionUnits() {
return windDirectionUnits;
}
public String getPrecipitationUnits() {
return precipitationUnits;
}
public String getWindSpeedUnits() {
return windSpeedUnits;
}
// data_day getters
public Calendar getForecastDate() {
return forecastDate;
}
public Integer getPictocode() {
return pictocode;
}
public Integer getUVIndex() {
return UVIndex;
}
public Double getTemperatureMin() {
return minTemperature;
}
public Double getTemperatureMax() {
return maxTemperature;
}
public Double getTemperatureMean() {
return meanTemperature;
}
public Double getFeltTemperatureMin() {
return feltTemperatureMin;
}
public Double feltTemperatureMax() {
return feltTemperatureMax;
}
public Integer getWindDirection() {
return windDirection;
}
public String getCardinalWindDirection() {
return cardinalWindDirection;
}
public Integer getPrecipitationProbability() {
return precipitationProbability;
}
public String getRainSpot() {
return rainSpot;
}
public Image getRainArea() {
return rainArea;
}
public Integer getPredictabilityClass() {
return predictabilityClass;
}
public Integer getPredictability() {
return predictability;
}
public Double getPrecipitation() {
return precipitation;
}
public Double getSnowFraction() {
return snowFraction;
}
public Integer getSeaLevelPressureMin() {
return minSeaLevelPressure;
}
public Integer getSeaLevelPressureMax() {
return maxSeaLevelPressure;
}
public Integer getSeaLevelPressureMean() {
return meanSeaLevelPressure;
}
public Double getWindSpeedMin() {
return minWindSpeed;
}
public Double getWindSpeedMax() {
return maxWindSpeed;
}
public Double getWindSpeedMean() {
return meanWindSpeed;
}
public Integer getRelativeHumidityMin() {
return relativeHumidityMin;
}
public Integer getRelativeHumidityMax() {
return relativeHumidityMax;
}
public Integer getRelativeHumidityMean() {
return relativeHumidityMean;
}
public Double getConvectivePrecipitation() {
return convectivePrecipitation;
}
public Double getPrecipitationHours() {
return precipitationHours;
}
public Double getHumidityGreater90Hours() {
return humidityGreater90Hours;
}
// derived getters
private String getCardinalDirection(int degrees) {
/*
* 8 directions @ 45 deg. each, centered
* N = 337.5-360,0-22.5
* NE = 22.5-67.5
* E = 67.5-112.5
* SE = 112.5-157.5
* S = 157.5-202.5
* SW = 202.5-247.5
* W = 247.5-292.5
* NW = 292.5-337.5
*/
if (degrees > 337 || degrees < 23) {
return "N";
}
if (degrees > 22 && degrees < 68) {
return "NE";
}
if (degrees > 67 && degrees < 113) {
return "E";
}
if (degrees > 112 && degrees < 158) {
return "SE";
}
if (degrees > 157 && degrees < 203) {
return "S";
}
if (degrees > 202 && degrees < 248) {
return "SW";
}
if (degrees > 247 && degrees < 293) {
return "W";
}
return "NW";
}
public Double getSnowFall() {
return snowFall;
}
public Image getIcon() {
return icon;
}
private String getIconNameForPictocode(int which) {
if (which < 1 || which > 17) {
return "iday.png";
}
return "iday-" + which + ".png";
}
private Image loadImageIcon(String imageFileName) {
BufferedImage buf = null;
String configDirectory = ConfigConstants.getConfigFolder();
File dataFile = new File(new File(configDirectory, "icons/classic/"), imageFileName);
if (!dataFile.exists()) {
logger.debug("Image file '{}' does not exist. Unable to create imageIcon.", dataFile.getAbsolutePath());
return null;
}
try {
buf = ImageIO.read(dataFile);
logger.trace("Returning image data: {}", buf);
return buf;
} catch (FileNotFoundException e) {
logger.trace("Image file '{}' not found during read attempt", dataFile, e);
return null;
} catch (IOException e) {
logger.debug("Failed to load image file '{}' for weather icon.", dataFile, e);
return null;
}
}
private Image generateRainAreaImage() {
if (rainSpot == null) {
logger.debug("No rainspot data exists. Can't generate rain area image.");
return null;
}
// @formatter:off
/*
* Grid position -> <- String position
* [ 0 1 2 3 4 5 6 ] [ 42 43 44 45 46 47 48 ]
* [ 7 8 9 10 11 12 13 ] [ 35 36 37 38 39 40 41 ]
* [ 14 15 16 17 18 19 20 ] [ 28 29 30 31 32 33 34 ]
* [ 21 22 23 24 25 26 27 ] [ 21 22 23 24 25 26 27 ]
* [ 28 29 30 31 32 33 34 ] [ 14 15 16 17 18 19 20 ]
* [ 35 36 37 38 39 40 41 ] [ 7 8 9 10 11 12 13 ]
* [ 42 43 44 45 46 47 48 ] [ 0 1 2 3 4 5 6 ]
*/
// @formatter:on
String s42 = rainSpot.substring(42);
String s35 = rainSpot.substring(35, 42);
String s28 = rainSpot.substring(28, 35);
String s21 = rainSpot.substring(21, 28);
String s14 = rainSpot.substring(14, 21);
String s07 = rainSpot.substring(7, 14);
String s00 = rainSpot.substring(0, 7);
char[] values = new char[49];
s00.getChars(0, s00.length(), values, 42);
s07.getChars(0, s07.length(), values, 35);
s14.getChars(0, s14.length(), values, 28);
s21.getChars(0, s21.length(), values, 21);
s28.getChars(0, s28.length(), values, 14);
s35.getChars(0, s35.length(), values, 7);
s42.getChars(0, s42.length(), values, 0);
logger.trace("Final grid: {}", values);
BufferedImage buf = new BufferedImage(350, 350, BufferedImage.TYPE_INT_RGB);
int gridCell = 0;
Color color;
for (int y = 0; y < 7; y++) {
for (int x = 0; x < 7; x++) {
gridCell = y * 7 + x;
color = getColorForIndex(values[gridCell]);
writeGrid(gridCell, buf, color);
}
}
return buf;
}
private void writeGrid(int cell, BufferedImage img, Color color) {
int r = color.getRed();
int g = color.getGreen();
int b = color.getBlue();
int p = (r << 16) | (g << 8) | b;
int startX = cell % 7 * 50;
int startY = cell / 7 * 50;
for (int i = startY; i < startY + 50; i++) {
for (int j = startX; j < startX + 50; j++) {
img.setRGB(j, i, p);
}
}
}
private Color getColorForIndex(char c) {
switch (c) {
case '0':
return Color.WHITE;
case '1':
return Color.GREEN;
case '2':
return Color.YELLOW;
case '3':
return Color.RED;
case '9':
return Color.decode("0x00FF00");
default:
return Color.BLACK;
}
}
}

View File

@@ -0,0 +1,40 @@
/**
* 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.meteoblue.internal;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.openhab.core.thing.ThingTypeUID;
/**
* Defines common constants.
*
* @author Chris Carman - Initial contribution
*/
public class MeteoBlueBindingConstants {
private static final String BINDING_ID = "meteoblue";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_WEATHER = new ThingTypeUID(BINDING_ID, "weather");
public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge");
public static final Set<ThingTypeUID> BRIDGE_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_BRIDGE);
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<>(Arrays.asList(THING_TYPE_WEATHER));
// Bridge configuration settings
public static final String APIKEY = "apiKey";
}

View File

@@ -0,0 +1,26 @@
/**
* 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.meteoblue.internal;
/**
* Model for the meteoblue bridge configuration.
*
* @author Chris Carman - Initial contribution
*/
public class MeteoBlueBridgeConfig {
private String apiKey;
public String getApiKey() {
return apiKey;
}
}

View File

@@ -0,0 +1,87 @@
/**
* 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.meteoblue.internal;
import org.apache.commons.lang.StringUtils;
/**
* Model for the meteoblue binding configuration.
*
* @author Chris Carman - Initial contribution
*/
public class MeteoBlueConfiguration {
// default values
public static final int DEFAULT_REFRESH = 240;
// constants
public static final String SERVICETYPE_COMM = "Commercial";
public static final String SERVICETYPE_NONCOMM = "NonCommercial";
public static final String COMM_BASE_URL = "http://my.meteoblue.com/dataApi/dispatch.pl?type=json_7day_3h_firstday&";
public static final String NONCOMM_BASE_URL = "http://my.meteoblue.com/packages/basic-day?";
public static final String URL_MINIMAL_PARAMS = "apikey=#API_KEY#&lat=#LATITUDE#&lon=#LONGITUDE#";
// required parameters
// servicetype - either Commercial or NonCommercial
public String serviceType;
// location - lat., long., and alt. in a single string
public String location;
// optional parameters
// refresh - time period in minutes between pulls
public Integer refresh;
// latitude - the latitude of this location in degrees (-90 to 90)
public Double latitude;
// longitude - the longitude of this location in degrees (-180 to 180)
public Double longitude;
// altitude - the height above sea level of the location, in meters
public Double altitude;
// timeZone - the timezone of the location (see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
public String timeZone;
// returns the URL for the specified serviceType
public static String getURL(String serviceType) {
if (SERVICETYPE_COMM.equals(serviceType)) {
return COMM_BASE_URL + URL_MINIMAL_PARAMS + "#FORMAT_PARAMS#";
} else {
return NONCOMM_BASE_URL + URL_MINIMAL_PARAMS + "#FORMAT_PARAMS#";
}
}
public void parseLocation() {
String[] split = location.split(",");
String a1 = split.length > 0 ? split[0] : null;
String a2 = split.length > 1 ? split[1] : null;
String a3 = split.length > 2 ? split[2] : null;
if (!StringUtils.isBlank(a1)) {
latitude = tryGetDouble(a1);
}
if (!StringUtils.isBlank(a2)) {
longitude = tryGetDouble(a2);
}
if (!StringUtils.isBlank(a3)) {
altitude = tryGetDouble(a3);
}
}
private Double tryGetDouble(String toParse) {
try {
return Double.parseDouble(toParse);
} catch (NumberFormatException e) {
return null;
}
}
}

View File

@@ -0,0 +1,63 @@
/**
* 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.meteoblue.internal;
import static org.openhab.binding.meteoblue.internal.MeteoBlueBindingConstants.*;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.meteoblue.internal.handler.MeteoBlueBridgeHandler;
import org.openhab.binding.meteoblue.internal.handler.MeteoBlueHandler;
import org.openhab.core.thing.Bridge;
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 MeteoBlueHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Chris Carman - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.meteoblue")
public class MeteoBlueHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
.concat(BRIDGE_THING_TYPES_UIDS.stream(), MeteoBlueBindingConstants.SUPPORTED_THING_TYPES_UIDS.stream())
.collect(Collectors.toSet());
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_WEATHER)) {
return new MeteoBlueHandler(thing);
}
if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
return new MeteoBlueBridgeHandler((Bridge) thing);
}
return null;
}
}

View File

@@ -0,0 +1,91 @@
/**
* 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.meteoblue.internal.handler;
import static org.openhab.binding.meteoblue.internal.MeteoBlueBindingConstants.THING_TYPE_BRIDGE;
import java.util.Collections;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.meteoblue.internal.MeteoBlueBridgeConfig;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.types.Command;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link MeteoBlueBridgeHandler} is responsible for handling the
* bridge things created to use the meteoblue weather service.
*
* @author Chris Carman - Initial contribution
*/
public class MeteoBlueBridgeHandler extends BaseBridgeHandler {
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_BRIDGE);
private final Logger logger = LoggerFactory.getLogger(MeteoBlueBridgeHandler.class);
private String apiKey;
public MeteoBlueBridgeHandler(Bridge bridge) {
super(bridge);
}
/**
* Initialize the bridge.
*/
@Override
public void initialize() {
logger.debug("Initializing meteoblue bridge");
MeteoBlueBridgeConfig config = getConfigAs(MeteoBlueBridgeConfig.class);
String apiKeyTemp = config.getApiKey();
if (StringUtils.isBlank(apiKeyTemp)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Cannot initialize meteoblue bridge. No apiKey provided.");
return;
}
apiKey = apiKeyTemp;
healthCheck();
}
/**
* No commands are supported here.
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
public String getApiKey() {
return new String(apiKey);
}
private void healthCheck() {
String url = "http://my.meteoblue.com/packages/";
try {
HttpUtil.executeUrl("GET", url, 30 * 1000);
logger.trace("HealthCheck succeeded.");
updateStatus(ThingStatus.ONLINE);
} catch (Exception e) {
logger.trace("HealthCheck failed", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "HealthCheck failed");
}
}
}

View File

@@ -0,0 +1,424 @@
/**
* 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.meteoblue.internal.handler;
import static org.openhab.core.library.unit.MetricPrefix.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.imageio.ImageIO;
import org.apache.commons.lang.StringUtils;
import org.openhab.binding.meteoblue.internal.Forecast;
import org.openhab.binding.meteoblue.internal.MeteoBlueConfiguration;
import org.openhab.binding.meteoblue.internal.json.JsonData;
import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.items.ImageItem;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.RawType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.library.unit.SmartHomeUnits;
import org.openhab.core.thing.Bridge;
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.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The {@link MeteoBlueHandler} is responsible for handling commands
* sent to one of the channels.
*
* @author Chris Carman - Initial contribution
*/
public class MeteoBlueHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(MeteoBlueHandler.class);
private Bridge bridge;
private Forecast[] forecasts;
private Gson gson;
private JsonData weatherData;
private ScheduledFuture<?> refreshJob;
private boolean properlyConfigured;
public MeteoBlueHandler(Thing thing) {
super(thing);
gson = new Gson();
forecasts = new Forecast[7];
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (properlyConfigured) {
logger.debug("Received command '{}' for channel '{}'", command, channelUID);
updateChannel(channelUID.getId());
}
}
@Override
public void initialize() {
logger.debug("Initializing the meteoblue handler...");
bridge = getBridge();
if (bridge == null) {
logger.warn("Unable to initialize meteoblue. No bridge was configured.");
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge not configured.");
return;
}
MeteoBlueConfiguration config = getConfigAs(MeteoBlueConfiguration.class);
if (StringUtils.isBlank(config.serviceType)) {
config.serviceType = MeteoBlueConfiguration.SERVICETYPE_NONCOMM;
logger.debug("Using default service type ({}).", config.serviceType);
return;
}
if (StringUtils.isBlank(config.location)) {
flagBadConfig("The location was not configured.");
return;
}
config.parseLocation();
if (config.latitude == null) {
flagBadConfig(String.format("Could not determine latitude from the defined location setting (%s).",
config.location));
return;
}
if (config.latitude > 90.0 || config.latitude < -90.0) {
flagBadConfig(String.format("Specified latitude value (%d) is not valid.", config.latitude));
return;
}
if (config.longitude == null) {
flagBadConfig(String.format("Could not determine longitude from the defined location setting (%s).",
config.location));
return;
}
if (config.longitude > 180.0 || config.longitude < -180.0) {
flagBadConfig(String.format("Specified longitude value (%d) is not valid.", config.longitude));
return;
}
updateStatus(ThingStatus.UNKNOWN);
startAutomaticRefresh(config);
properlyConfigured = true;
}
/**
* Marks the configuration as invalid.
*/
private void flagBadConfig(String message) {
properlyConfigured = false;
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message);
}
/**
* Schedule a job to periodically refresh the weather data.
*/
private void startAutomaticRefresh(MeteoBlueConfiguration config) {
if (refreshJob != null && !refreshJob.isCancelled()) {
logger.trace("Refresh job already exists.");
return;
}
Runnable runnable = () -> {
boolean updateSuccessful = false;
try {
// Request new weather data
updateSuccessful = updateWeatherData();
if (updateSuccessful) {
// build forecasts from the data
for (int i = 0; i < 7; i++) {
forecasts[i] = new Forecast(i, weatherData.getMetadata(), weatherData.getUnits(),
weatherData.getDataDay());
}
// Update all channels from the updated weather data
for (Channel channel : getThing().getChannels()) {
updateChannel(channel.getUID().getId());
}
}
} catch (Exception e) {
logger.warn("Exception occurred during weather update: {}", e.getMessage(), e);
}
};
int period = config.refresh != null ? config.refresh : MeteoBlueConfiguration.DEFAULT_REFRESH;
refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, period, TimeUnit.MINUTES);
}
@Override
public void dispose() {
logger.debug("Disposing meteoblue handler.");
if (refreshJob != null && !refreshJob.isCancelled()) {
refreshJob.cancel(true);
refreshJob = null;
}
}
/**
* Update the channel from the last weather data retrieved.
*
* @param channelId the id of the channel to be updated
*/
private void updateChannel(String channelId) {
Channel channel = getThing().getChannel(channelId);
if (channel == null || !isLinked(channelId)) {
logger.trace("Channel '{}' was null or not linked! Not updated.", channelId);
return;
}
// get the set of channel parameters.
// the first will be the forecast day (eg. forecastToday),
// and the second will be the datapoint (eg. snowFraction)
String[] channelParts = channelId.split("#");
String forecastDay = channelParts[0];
String datapointName = channelParts[1];
if (channelParts.length != 2) {
logger.debug("Skipped invalid channelId '{}'", channelId);
return;
}
logger.debug("Updating channel '{}'", channelId);
Forecast forecast = getForecast(forecastDay);
if (forecast == null) {
logger.debug("No forecast found for '{}'. Not updating.", forecastDay);
return;
}
Object datapoint = forecast.getDatapoint(datapointName);
logger.debug("Value for datapoint '{}' is '{}'", datapointName, datapoint);
if (datapoint == null) {
logger.debug("Couldn't get datapoint '{}' for '{}'. Not updating.", datapointName, forecastDay);
return;
}
// Build a State from this value
State state = null;
if (datapoint instanceof Calendar) {
state = new DateTimeType(
ZonedDateTime.ofInstant(((Calendar) datapoint).toInstant(), ZoneId.systemDefault()));
} else if (datapoint instanceof Integer) {
state = getStateForType(channel.getAcceptedItemType(), (Integer) datapoint);
} else if (datapoint instanceof Number) {
BigDecimal decimalValue = new BigDecimal(datapoint.toString()).setScale(2, RoundingMode.HALF_UP);
state = getStateForType(channel.getAcceptedItemType(), decimalValue);
} else if (datapoint instanceof String) {
state = new StringType(datapoint.toString());
} else if (datapoint instanceof BufferedImage) {
ImageItem item = new ImageItem("rain area");
state = new RawType(renderImage((BufferedImage) datapoint), "image/png");
item.setState(state);
} else {
logger.debug("Unsupported value type {}", datapoint.getClass().getSimpleName());
}
// Update the channel
if (state != null) {
logger.trace("Updating channel with state value {}. (object type {})", state,
datapoint.getClass().getSimpleName());
updateState(channelId, state);
}
}
private State getStateForType(String type, Integer value) {
return getStateForType(type, new BigDecimal(value));
}
private State getStateForType(String type, BigDecimal value) {
State state = new DecimalType(value);
if (type.equals("Number:Temperature")) {
state = new QuantityType<>(value, SIUnits.CELSIUS);
} else if (type.equals("Number:Length")) {
state = new QuantityType<>(value, MILLI(SIUnits.METRE));
} else if (type.equals("Number:Pressure")) {
state = new QuantityType<>(value, HECTO(SIUnits.PASCAL));
} else if (type.equals("Number:Speed")) {
state = new QuantityType<>(value, SmartHomeUnits.METRE_PER_SECOND);
}
return state;
}
// Request new weather data from the service
private boolean updateWeatherData() {
if (bridge == null) {
logger.debug("Unable to update weather data. Bridge missing.");
return false;
}
MeteoBlueBridgeHandler handler = (MeteoBlueBridgeHandler) bridge.getHandler();
if (handler == null) {
logger.debug("Unable to update weather data. Handler missing.");
return false;
}
String apiKey = handler.getApiKey();
logger.debug("Updating weather data...");
MeteoBlueConfiguration config = getConfigAs(MeteoBlueConfiguration.class);
config.parseLocation();
String serviceType = config.serviceType;
if (serviceType.equals(MeteoBlueConfiguration.SERVICETYPE_COMM)) {
logger.debug("Fetching weather data using Commercial API.");
} else {
logger.debug("Fetching weather data using NonCommercial API.");
}
// get the base url for the HTTP query
String url = MeteoBlueConfiguration.getURL(serviceType);
url = url.replace("#API_KEY#", apiKey);
url = url.replace("#LATITUDE#", String.valueOf(config.latitude)).replace("#LONGITUDE#",
String.valueOf(config.longitude));
// fill in any optional parameters for the HTTP query
StringBuilder builder = new StringBuilder();
if (config.altitude != null) {
builder.append("&asl=" + config.altitude);
}
if (StringUtils.isNotBlank(config.timeZone)) {
builder.append("&tz=" + config.timeZone);
}
url = url.replace("#FORMAT_PARAMS#", builder.toString());
logger.trace("Using URL '{}'", url);
// Run the HTTP request and get the JSON response
String httpResponse = getWeatherData(url);
if (httpResponse == null) {
return false;
}
JsonData jsonResult = translateJson(httpResponse, serviceType);
logger.trace("json object: {}", jsonResult);
if (jsonResult == null) {
logger.warn("No data was received from the weather service");
return false;
}
String errorMessage = jsonResult.getErrorMessage();
if (errorMessage != null) {
if (errorMessage.equals("MB_REQUEST::DISPATCH: Invalid api key")) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid API Key");
} else if (errorMessage.equals("MB_REQUEST::DISPATCH: This datafeed is not authorized for your api key")) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"API Key not authorized for this datafeed");
} else {
logger.warn("Failed to retrieve weather data due to unexpected error. Error message: {}", errorMessage);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, errorMessage);
}
return false;
}
weatherData = jsonResult;
updateStatus(ThingStatus.ONLINE);
return true;
}
// Run the HTTP request and get the JSON response
private String getWeatherData(String url) {
try {
String httpResponse = HttpUtil.executeUrl("GET", url, 30 * 1000);
logger.trace("http response: {}", httpResponse);
return httpResponse;
} catch (IOException e) {
logger.debug("I/O Exception occurred while retrieving weather data.", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"I/O Exception occurred while retrieving weather data.");
return null;
}
}
// Convert a json string response into a json data object
private JsonData translateJson(String stringData, String serviceType) {
// JsonData weatherData = null;
// For now, no distinction is made between commercial and non-commercial data;
// This may need to be changed later based on user feedback.
/*
* if (serviceType.equals(MeteoBlueConfiguration.SERVICETYPE_COMM)) {
* weatherData = gson.fromJson(httpResponse, JsonCommercialData.class);
* }
* else {
* weatherData = gson.fromJson(httpResponse, JsonNonCommercialData.class);
* }
*/
return gson.fromJson(stringData, JsonData.class);
}
private Forecast getForecast(String which) {
switch (which) {
case "forecastToday":
return forecasts[0];
case "forecastTomorrow":
return forecasts[1];
case "forecastDay2":
return forecasts[2];
case "forecastDay3":
return forecasts[3];
case "forecastDay4":
return forecasts[4];
case "forecastDay5":
return forecasts[5];
case "forecastDay6":
return forecasts[6];
default:
return null;
}
}
private byte[] renderImage(BufferedImage image) {
byte[] data = null;
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ImageIO.write(image, "png", out);
out.flush();
data = out.toByteArray();
out.close();
} catch (IOException ioe) {
logger.debug("I/O exception occurred converting image data", ioe);
}
return data;
}
}

View File

@@ -0,0 +1,68 @@
/**
* 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.meteoblue.internal.json;
import com.google.gson.annotations.SerializedName;
/**
* The {@link JsonData} is the Java class used to model the JSON
* response to a weather request.
*
* @author Chris Carman - Initial contribution
*/
public class JsonData {
private JsonMetadata metadata;
private JsonUnits units;
@SerializedName("data_day")
private JsonDataDay dataDay;
@SerializedName("error_message")
private String errorMessage;
public JsonData() {
}
/**
* Get the {@link JsonMetadata} object
*
* @return the JsonMetadata object
*/
public JsonMetadata getMetadata() {
return metadata;
}
/**
* Get the {@link JsonUnits} object
*
* @return the JsonUnits object
*/
public JsonUnits getUnits() {
return units;
}
/**
* Get the {@link JsonDataDay} object
*
* @return the JsonDataDay object
*/
public JsonDataDay getDataDay() {
return dataDay;
}
// get the error message
public String getErrorMessage() {
return errorMessage;
}
}

View File

@@ -0,0 +1,210 @@
/**
* 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.meteoblue.internal.json;
import com.google.gson.annotations.SerializedName;
/**
* {@link JsonDataDay} models the 'data_day' portion of the JSON
* response to a weather request.
*
* @author Chris Carman - Initial contribution
*/
public class JsonDataDay {
private String[] time;
private Integer[] pictocode;
@SerializedName("uvindex")
private Integer[] uvIndex;
@SerializedName("temperature_max")
private Double[] temperatureMax;
@SerializedName("temperature_min")
private Double[] temperatureMin;
@SerializedName("temperature_mean")
private Double[] temperatureMean;
@SerializedName("felttemperature_max")
private Double[] feltTemperatureMax;
@SerializedName("felttemperature_min")
private Double[] feltTemperatureMin;
@SerializedName("winddirection")
private Integer[] windDirection;
@SerializedName("precipitation_probability")
private Integer[] precipitationProbability;
private String[] rainspot;
@SerializedName("predictability_class")
private Integer[] predictabilityClass;
private Integer[] predictability;
private Double[] precipitation;
@SerializedName("snowfraction")
private Double[] snowFraction;
@SerializedName("sealevelpressure_max")
private Integer[] seaLevelPressureMax;
@SerializedName("sealevelpressure_min")
private Integer[] seaLevelPressureMin;
@SerializedName("sealevelpressure_mean")
private Integer[] seaLevelPressureMean;
@SerializedName("windspeed_max")
private Double[] windSpeedMax;
@SerializedName("windspeed_mean")
private Double[] windSpeedMean;
@SerializedName("windspeed_min")
private Double[] windSpeedMin;
@SerializedName("relativehumidity_max")
private Integer[] relativeHumidityMax;
@SerializedName("relativehumidity_min")
private Integer[] relativeHumidityMin;
@SerializedName("relativehumidity_mean")
private Integer[] relativeHumidityMean;
@SerializedName("convective_precipitation")
private Double[] convectivePrecipitation;
@SerializedName("precipitation_hours")
private Double[] precipitationHours;
@SerializedName("humiditygreater90_hours")
private Double[] humidityGreater90Hours;
public JsonDataDay() {
}
public String[] getTime() {
return time;
}
public Integer[] getPictocode() {
return pictocode;
}
public Integer[] getUVIndex() {
return uvIndex;
}
public Double[] getTemperatureMax() {
return temperatureMax;
}
public Double[] getTemperatureMin() {
return temperatureMin;
}
public Double[] getTemperatureMean() {
return temperatureMean;
}
public Double[] getFeltTemperatureMax() {
return feltTemperatureMax;
}
public Double[] getFeltTemperatureMin() {
return feltTemperatureMin;
}
public Integer[] getWindDirection() {
return windDirection;
}
public Integer[] getPrecipitationProbability() {
return precipitationProbability;
}
public String[] getRainspot() {
return rainspot;
}
public Integer[] getPredictabilityClass() {
return predictabilityClass;
}
public Integer[] getPredictability() {
return predictability;
}
public Double[] getPrecipitation() {
return precipitation;
}
public Double[] getSnowFraction() {
return snowFraction;
}
public Integer[] getSeaLevelPressureMax() {
return seaLevelPressureMax;
}
public Integer[] getSeaLevelPressureMin() {
return seaLevelPressureMin;
}
public Integer[] getSeaLevelPressureMean() {
return seaLevelPressureMean;
}
public Double[] getWindSpeedMax() {
return windSpeedMax;
}
public Double[] getWindSpeedMean() {
return windSpeedMean;
}
public Double[] getWindSpeedMin() {
return windSpeedMin;
}
public Integer[] getRelativeHumidityMax() {
return relativeHumidityMax;
}
public Integer[] getRelativeHumidityMin() {
return relativeHumidityMin;
}
public Integer[] getRelativeHumidityMean() {
return relativeHumidityMean;
}
public Double[] getConvectivePrecipitation() {
return convectivePrecipitation;
}
public Double[] getPrecipitationHours() {
return precipitationHours;
}
public Double[] getHumidityGreater90Hours() {
return humidityGreater90Hours;
}
}

View File

@@ -0,0 +1,56 @@
/**
* 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.meteoblue.internal.json;
import com.google.gson.annotations.SerializedName;
/**
* {@link JsonUnits} models the 'metadata' portion of the JSON
* response to a weather request.
*
* @author Chris Carman - Initial contribution
*/
public class JsonMetadata {
private String name;
private Double latitude;
private Double longitude;
private Integer height;
// [sic]
@SerializedName("timezone_abbrevation")
private String timeZoneAbbreviation;
public JsonMetadata() {
}
public String getName() {
return name;
}
public Double getLatitude() {
return latitude;
}
public Double getLongitude() {
return longitude;
}
public Integer getHeight() {
return height;
}
public String getTimeZoneAbbreviation() {
return timeZoneAbbreviation;
}
}

View File

@@ -0,0 +1,83 @@
/**
* 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.meteoblue.internal.json;
import com.google.gson.annotations.SerializedName;
/**
* {@link JsonUnits} models the 'units' portion of the JSON
* response to a weather request.
*
* @author Chris Carman - Initial contribution
*/
public class JsonUnits {
private String time;
private String predictability;
@SerializedName("precipitation_probability")
private String precipitationProbability;
private String pressure;
@SerializedName("relativehumidity")
private String relativeHumidity;
private String temperature;
@SerializedName("winddirection")
private String windDirection;
private String precipitation;
@SerializedName("windspeed")
private String windSpeed;
public JsonUnits() {
}
public String getPredictability() {
return predictability;
}
public String getPrecipitationProbability() {
return precipitationProbability;
}
public String getPressure() {
return pressure;
}
public String getRelativeHumidity() {
return relativeHumidity;
}
public String getTemperature() {
return temperature;
}
public String getWindDirection() {
return windDirection;
}
public String getPrecipitation() {
return precipitation;
}
public String getWindSpeed() {
return windSpeed;
}
public String getTime() {
return time;
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="meteoblue" 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>meteoblue Binding</name>
<description>The meteoblue binding retrieves weather information from the meteoblue weather service.</description>
<author>Chris Carman</author>
</binding:binding>

View File

@@ -0,0 +1,166 @@
# binding
binding.meteoblue.name = Extension meteoblue
binding.meteoblue.description = L'extension meteoblue interroge le service meteoblue pour récupérer les prévisions météorologiqes.
# bridge types
thing-type.meteoblue.bridge.label = Compte meteoblue
thing-type.meteoblue.bridge.description = Fournit un accès à l'API du service meteoblue.
# bridge types configuration
thing-type.config.meteoblue.bridge.apiKey.label = Clé API
thing-type.config.meteoblue.bridge.apiKey.description = Clé pour accéder à l'API du service meteoblue.
# thing types
thing-type.meteoblue.weather.label = Prévisions météorologiques
thing-type.meteoblue.weather.description = Fournit les prévisions météorologiques du service méteoblue.
# thing type configuration
thing-type.config.meteoblue.weather.serviceType.label = Type de service
thing-type.config.meteoblue.weather.serviceType.description = Type d'API à utiliser.
thing-type.config.meteoblue.weather.serviceType.option.Commercial = Commercial
thing-type.config.meteoblue.weather.serviceType.option.NonCommercial = Non Commercial
thing-type.config.meteoblue.weather.location.label = Localisation
thing-type.config.meteoblue.weather.location.description = Localisation en coordonnées géographiques (latitude, longitude[, altitude]).
thing-type.config.meteoblue.weather.timeZone.label = Fuseau horaire
thing-type.config.meteoblue.weather.timeZone.description = Fuseau horaire de cet emplacement (Europe/Paris par exemple). Voir https://en.wikipedia.org/wiki/List_of_tz_database_time_zones .
thing-type.config.meteoblue.weather.refresh.label = Intervalle d'actualisation
thing-type.config.meteoblue.weather.refresh.description = Spécifie l'intervalle d'actualisation (en minutes).
# channel groups
thing-type.meteoblue.weather.group.forecastToday.label = Prévisions pour aujourd'hui
thing-type.meteoblue.weather.group.forecastToday.description = Représente les prévisions météorologiques pour aujourd'hui.
thing-type.meteoblue.weather.group.forecastTomorrow.label = Prévisions pour demain
thing-type.meteoblue.weather.group.forecastTomorrow.description = Représente les prévisions météorologiques pour demain.
thing-type.meteoblue.weather.group.forecastDay2.label = Prévisions dans 2 jours
thing-type.meteoblue.weather.group.forecastDay2.description = Représente les prévisions météorologiques dans deux jours.
thing-type.meteoblue.weather.group.forecastDay3.label = Prévisions dans 3 jours
thing-type.meteoblue.weather.group.forecastDay3.description = Représente les prévisions météorologiques dans trois jours.
thing-type.meteoblue.weather.group.forecastDay4.label = Prévisions dans 4 jours
thing-type.meteoblue.weather.group.forecastDay4.description = Représente les prévisions météorologiques dans quatre jours.
thing-type.meteoblue.weather.group.forecastDay5.label = Prévisions dans 5 jours
thing-type.meteoblue.weather.group.forecastDay5.description = Représente les prévisions météorologiques dans cinq jours.
thing-type.meteoblue.weather.group.forecastDay6.label = Prévisions dans 6 jours
thing-type.meteoblue.weather.group.forecastDay6.description = Représente les prévisions météorologiques dans six jours.
# channel types
channel-type.meteoblue.height.label = Altitude
channel-type.meteoblue.height.description = L'altitude au-dessus de la mer de l'emplacement (en mètres).
channel-type.meteoblue.forecastDate.label = Date des prévisions
channel-type.meteoblue.forecastDate.description = La date et l'heure des prévisions.
channel-type.meteoblue.UVIndex.label = Indice UV
channel-type.meteoblue.UVIndex.description = L'indice UV prévu.
channel-type.meteoblue.minTemperature.label = Température minimale
channel-type.meteoblue.minTemperature.description = La température extérieure minimale prévue.
channel-type.meteoblue.maxTemperature.label = Température maximale
channel-type.meteoblue.maxTemperature.description = La température extérieure maximale prévue.
channel-type.meteoblue.meanTemperature.label = Température moyenne
channel-type.meteoblue.meanTemperature.description = La température extérieure moyenne prévue.
channel-type.meteoblue.feltTemperatureMin.label = Température minimale ressentie
channel-type.meteoblue.feltTemperatureMin.description = La température minimale ressentie.
channel-type.meteoblue.feltTemperatureMax.label = Température maximale ressentie
channel-type.meteoblue.feltTemperatureMax.description = La température maximale ressentie.
channel-type.meteoblue.relativeHumidityMin.label = Humidité atmosphérique minimale
channel-type.meteoblue.relativeHumidityMin.description = L'humidité relative atmosphérique minimale prévue.
channel-type.meteoblue.relativeHumidityMax.label = Humidité atmosphérique maximale
channel-type.meteoblue.relativeHumidityMax.description = L'humidité relative atmosphérique maximale prévue.
channel-type.meteoblue.relativeHumidityMean.label = Humidité atmosphérique moyenne
channel-type.meteoblue.relativeHumidityMean.description = L'humidité relative atmosphérique moyenne prévue.
channel-type.meteoblue.precipitationProbability.label = Probabilité de précipitations
channel-type.meteoblue.precipitationProbability.description = La probabilité de précipitations.
channel-type.meteoblue.precipitation.label = Précipitations
channel-type.meteoblue.precipitation.description = La quantité prévue de précipitations.
channel-type.meteoblue.convectivePrecipitation.label = Précipitations convectives
channel-type.meteoblue.convectivePrecipitation.description = La quantité prévue de précipitations de type convective.
channel-type.meteoblue.rainSpot.label = Distribution des précipitations
channel-type.meteoblue.rainSpot.description = Distribution des précipitations autour de l'emplacement sous forme de matrice 7x7, codé du sud au nord et d'ouest en est.
channel-type.meteoblue.rainArea.label = Zone des précipitations
channel-type.meteoblue.rainArea.description = Image représentant la distribution des précipitations.
channel-type.meteoblue.snowFraction.label = Proportion de neige
channel-type.meteoblue.snowFraction.description = La proportion prévue de neige sur l'ensemble des précipitations.
channel-type.meteoblue.snowFall.label = Chutes de neige
channel-type.meteoblue.snowFall.description = La quantité prévue de neige.
channel-type.meteoblue.cardinalWindDirection.label = Direction cardinale du vent
channel-type.meteoblue.cardinalWindDirection.description = La direction cardinale prévue du vent.
channel-type.meteoblue.windDirection.label = Direction du vent
channel-type.meteoblue.windDirection.description = La direction prévue du vent exprimée sous forme d'angle.
channel-type.meteoblue.minWindSpeed.label = Vitesse minimale du vent
channel-type.meteoblue.minWindSpeed.description = La vitesse minimale prévue du vent.
channel-type.meteoblue.maxWindSpeed.label = Vitesse maximale du vent
channel-type.meteoblue.maxWindSpeed.description = La vitesse maximale prévue du vent.
channel-type.meteoblue.meanWindSpeed.label = Vitesse moyenne du vent
channel-type.meteoblue.meanWindSpeed.description = La vitesse moyenne prévue du vent.
channel-type.meteoblue.pressureMin.label = Pression barométrique minimale
channel-type.meteoblue.pressureMin.description = La pression barométrique minimale prévue.
channel-type.meteoblue.pressureMax.label = Pression barométrique maximale
channel-type.meteoblue.pressureMax.description = La pression barométrique maximale prévue.
channel-type.meteoblue.pressureMean.label = Pression barométrique moyenne
channel-type.meteoblue.pressureMean.description = La pression barométrique moyenne prévue.
channel-type.meteoblue.condition.label = Conditions météorologiques
channel-type.meteoblue.condition.description = Les conditions météorologiques prévues.
channel-type.meteoblue.condition.state.option.1 = Ciel ensoleillé
channel-type.meteoblue.condition.state.option.2 = Ensoleillé et quelques nuages
channel-type.meteoblue.condition.state.option.3 = Partiellement nuageux
channel-type.meteoblue.condition.state.option.4 = Couvert
channel-type.meteoblue.condition.state.option.5 = Brouillard
channel-type.meteoblue.condition.state.option.6 = Couvert avec pluie
channel-type.meteoblue.condition.state.option.7 = Variable avec averses
channel-type.meteoblue.condition.state.option.8 = Averses, orages probables
channel-type.meteoblue.condition.state.option.9 = Couvert avec neige
channel-type.meteoblue.condition.state.option.10 = Variable avec des averses de neige
channel-type.meteoblue.condition.state.option.11 = Nuageux avec un mélange de neige et de pluie
channel-type.meteoblue.condition.state.option.12 = Couvert avec faible pluie
channel-type.meteoblue.condition.state.option.13 = Couvert avec quelques chutes de neige
channel-type.meteoblue.condition.state.option.14 = Nuageux avec pluie
channel-type.meteoblue.condition.state.option.15 = Nuageux avec neige
channel-type.meteoblue.condition.state.option.16 = Nuageux avec avec faible pluie
channel-type.meteoblue.condition.state.option.17 = Nuageux avec quelques chutes de neige
channel-type.meteoblue.icon.label = Icône
channel-type.meteoblue.icon.description = L'icône représentant les conditions météorologiques.
channel-type.meteoblue.predictability.label = Confiance des prévisions
channel-type.meteoblue.predictability.description = Le niveau de confiance des prévisions (en %).
channel-type.meteoblue.predictabilityClass.label = Classe de confiance des prévisions
channel-type.meteoblue.predictabilityClass.description = La classe de confiance des prévisions (0 = très faible, 5 = très élevé).
channel-type.meteoblue.precipitationHours.label = Heures avec précipitations
channel-type.meteoblue.precipitationHours.description = Le nombre prévu d'heures avec des précipitations.
channel-type.meteoblue.humidityGreater90Hours.label = Heures avec humidité élevée
channel-type.meteoblue.humidityGreater90Hours.description = Le nombre prévu d'heures avec une humidité supérieure à 90%.

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="meteoblue"
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">
<!-- meteoblue Bridge -->
<bridge-type id="bridge">
<label>meteoblue Account</label>
<description>The meteoblue account holds settings for use with the meteoblue weather service.</description>
<config-description>
<parameter name="apiKey" type="text" required="true">
<context>password</context>
<label>API Key</label>
<description>API key to access the meteoblue service</description>
</parameter>
</config-description>
</bridge-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,390 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="meteoblue"
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">
<!-- meteoblue Binding things -->
<thing-type id="weather">
<supported-bridge-type-refs>
<bridge-type-ref id="bridge"/>
</supported-bridge-type-refs>
<label>Weather Information</label>
<description>Weather data from the meteoblue service</description>
<channel-groups>
<channel-group id="forecastToday" typeId="forecast">
<label>Weather Forecast Today</label>
<description>Weather forecast for today</description>
</channel-group>
<channel-group id="forecastTomorrow" typeId="forecast">
<label>Weather Forecast Tomorrow</label>
<description>Weather forecast for tomorrow</description>
</channel-group>
<channel-group id="forecastDay2" typeId="forecast">
<label>Weather Forecast Day 2</label>
<description>Weather forecast two days from now</description>
</channel-group>
<channel-group id="forecastDay3" typeId="forecast">
<label>Weather Forecast Day 3</label>
<description>Weather forecast three days from now</description>
</channel-group>
<channel-group id="forecastDay4" typeId="forecast">
<label>Weather Forecast Day 4</label>
<description>Weather forecast four days from now</description>
</channel-group>
<channel-group id="forecastDay5" typeId="forecast">
<label>Weather Forecast Day 5</label>
<description>Weather forecast five days from now</description>
</channel-group>
<channel-group id="forecastDay6" typeId="forecast">
<label>Weather Forecast Day 6</label>
<description>Weather forecast six days from now</description>
</channel-group>
</channel-groups>
<config-description>
<parameter name="serviceType" type="text">
<label>Service Type</label>
<description>The type of API to be used</description>
<options>
<option value="Commercial">Commercial</option>
<option value="NonCommercial">NonCommercial</option>
</options>
<limitToOptions>true</limitToOptions>
<default>NonCommercial</default>
</parameter>
<parameter name="location" type="text" required="true">
<context>location</context>
<label>Location</label>
<description>The latitude, longitude and optionally altitude, separated by commas (lat,long,[alt])</description>
</parameter>
<parameter name="timeZone" type="text">
<label>Time Zone</label>
<description>Time Zone of this location (e.g. America/Louisville). See
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones</description>
</parameter>
<parameter name="refresh" type="integer" min="5" unit="min">
<label>Refresh Interval</label>
<description>Refresh interval (in minutes)</description>
<default>240</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="forecast">
<label>Weather Forecast</label>
<description>This is the weather forecast</description>
<channels>
<channel id="height" typeId="height"/>
<channel id="forecastDate" typeId="forecastDate"/>
<channel id="UVIndex" typeId="UVIndex"/>
<channel id="minTemperature" typeId="minTemperature"/>
<channel id="maxTemperature" typeId="maxTemperature"/>
<channel id="meanTemperature" typeId="meanTemperature"/>
<channel id="feltTemperatureMin" typeId="feltTemperatureMin"/>
<channel id="feltTemperatureMax" typeId="feltTemperatureMax"/>
<channel id="relativeHumidityMin" typeId="relativeHumidityMin"/>
<channel id="relativeHumidityMax" typeId="relativeHumidityMax"/>
<channel id="relativeHumidityMean" typeId="relativeHumidityMean"/>
<channel id="precipitationProbability" typeId="precipitationProbability"/>
<channel id="precipitation" typeId="precipitation"/>
<channel id="convectivePrecipitation" typeId="convectivePrecipitation"/>
<channel id="rainSpot" typeId="rainSpot"/>
<channel id="rainArea" typeId="rainArea"/>
<channel id="snowFraction" typeId="snowFraction"/>
<channel id="snowFall" typeId="snowFall"/>
<channel id="cardinalWindDirection" typeId="cardinalWindDirection"/>
<channel id="windDirection" typeId="windDirection"/>
<channel id="minWindSpeed" typeId="minWindSpeed"/>
<channel id="maxWindSpeed" typeId="maxWindSpeed"/>
<channel id="meanWindSpeed" typeId="meanWindSpeed"/>
<channel id="minSeaLevelPressure" typeId="pressureMin"/>
<channel id="maxSeaLevelPressure" typeId="pressureMax"/>
<channel id="meanSeaLevelPressure" typeId="pressureMean"/>
<channel id="condition" typeId="condition"/>
<channel id="icon" typeId="icon"/>
<channel id="predictability" typeId="predictability"/>
<channel id="predictabilityClass" typeId="predictabilityClass"/>
<channel id="precipitationHours" typeId="precipitationHours"/>
<channel id="humidityGreater90Hours" typeId="humidityGreater90Hours"/>
</channels>
</channel-group-type>
<channel-type id="height" advanced="true">
<item-type>Number</item-type>
<label>Altitude</label>
<description>Altitude above sea-level of the location (in meters)</description>
<state readOnly="true" pattern="%d"></state>
</channel-type>
<channel-type id="forecastDate">
<item-type>DateTime</item-type>
<label>Forecast Date</label>
<description>Forecast date</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="minTemperature">
<item-type>Number:Temperature</item-type>
<label>Minimum Temperature</label>
<description>The low temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="maxTemperature">
<item-type>Number:Temperature</item-type>
<label>Maximum Temperature</label>
<description>The high temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="meanTemperature">
<item-type>Number:Temperature</item-type>
<label>Mean Temperature</label>
<description>The average temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="feltTemperatureMin" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Minimum Felt Temperature</label>
<description>The low "feels like" temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="feltTemperatureMax" advanced="true">
<item-type>Number:Temperature</item-type>
<label>Maximum Felt Temperature</label>
<description>The high "feels like" temperature</description>
<category>Temperature</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="relativeHumidityMin">
<item-type>Number</item-type>
<label>Minimum Relative Humidity</label>
<description>The minimum relative humidity in %</description>
<category>Humidity</category>
<state readOnly="true" min="0" max="100" pattern="%d %%">
</state>
</channel-type>
<channel-type id="relativeHumidityMax">
<item-type>Number</item-type>
<label>Maximum Relative Humidity</label>
<description>The maximum relative humidity in %</description>
<category>Humidity</category>
<state readOnly="true" min="0" max="100" pattern="%d %%">
</state>
</channel-type>
<channel-type id="relativeHumidityMean">
<item-type>Number</item-type>
<label>Mean Relative Humidity</label>
<description>The average relative humidity in %</description>
<category>Humidity</category>
<state readOnly="true" min="0" max="100" pattern="%d %%">
</state>
</channel-type>
<channel-type id="cardinalWindDirection">
<item-type>String</item-type>
<label>Cardinal Wind Direction</label>
<state readOnly="true" pattern="%s">
</state>
</channel-type>
<channel-type id="windDirection" advanced="true">
<item-type>Number</item-type>
<label>Wind Direction</label>
<description>Wind direction (in degrees)</description>
<category>Wind</category>
<state readOnly="true" min="0" max="360" pattern="%.0f °">
</state>
</channel-type>
<channel-type id="maxWindSpeed">
<item-type>Number:Speed</item-type>
<label>Maximum Wind Speed</label>
<description>Maximum wind speed</description>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="minWindSpeed">
<item-type>Number:Speed</item-type>
<label>Minimum Wind Speed</label>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="meanWindSpeed">
<item-type>Number:Speed</item-type>
<label>Mean Wind Speed</label>
<category>Wind</category>
<state readOnly="true" pattern="%.1f %unit%"/>
</channel-type>
<channel-type id="pressureMin">
<item-type>Number:Pressure</item-type>
<label>Low Pressure</label>
<description>Low Pressure</description>
<category>Pressure</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="pressureMax">
<item-type>Number:Pressure</item-type>
<label>High Pressure</label>
<description>High Pressure</description>
<category>Pressure</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="pressureMean">
<item-type>Number:Pressure</item-type>
<label>Mean Pressure</label>
<description>Mean Pressure</description>
<category>Pressure</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="UVIndex" advanced="true">
<item-type>Number</item-type>
<label>UV Index</label>
<description>UV Index</description>
<state readOnly="true" pattern="%.1f">
</state>
</channel-type>
<channel-type id="precipitation">
<item-type>Number:Length</item-type>
<label>Precipitation</label>
<description>Prediction of total precipitation</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="convectivePrecipitation">
<item-type>Number:Length</item-type>
<label>Convective Precipitation</label>
<description>Prediction of convective precipitation (rainfall)</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="rainSpot">
<item-type>String</item-type>
<label>Rain Spot</label>
<description>Precipitation distribution around the location as a 7x7 array,
encoded from south to north and west to
east
(e.g. 2112211093222219212110112229922133311912239022192).</description>
<category>Rain</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="rainArea">
<item-type>Image</item-type>
<label>Rain Area</label>
<description>Image representing the Rain Spot precipitation distribution</description>
<category>Rain</category>
<state readOnly="true"/>
</channel-type>
<channel-type id="snowFraction">
<item-type>Number</item-type>
<label>Snow Fraction</label>
<description>Prediction of the fraction of total precipitation that will fall as snow</description>
<category>Rain</category>
<state readOnly="true" min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="snowFall">
<item-type>Number:Length</item-type>
<label>Snowfall</label>
<description>Prediction of snowfall</description>
<category>Rain</category>
<state readOnly="true" pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="precipitationProbability">
<item-type>Number</item-type>
<label>Precipitation Probability</label>
<description>Probability of precipitation (as %)</description>
<state readOnly="true" min="0" max="100" pattern="%d %%">
</state>
</channel-type>
<channel-type id="predictability">
<item-type>Number</item-type>
<label>Forecast Predictability</label>
<description>The predictability of the forecast (as %)</description>
<state readOnly="true" min="0" max="100" pattern="%d %%">
</state>
</channel-type>
<channel-type id="precipitationHours">
<item-type>Number</item-type>
<label>Precipitation Hours</label>
<description>The predicted number of hours having precipitation</description>
<state readOnly="true" min="0" max="24" pattern="%d">
</state>
</channel-type>
<channel-type id="humidityGreater90Hours">
<item-type>Number</item-type>
<label>Humidity Greater Than 90 Hours</label>
<description>The predicted number of hours having humidity greater than 90%</description>
<state readOnly="true" min="0" max="24" pattern="%d">
</state>
</channel-type>
<channel-type id="predictabilityClass">
<item-type>Number</item-type>
<label>Forecast Predictability Class</label>
<description>The estimated certainty of the forecast (0 = very low, 5 = very high)</description>
<state readOnly="true" min="0" max="5" pattern="%d"/>
</channel-type>
<channel-type id="condition">
<item-type>String</item-type>
<label>Condition</label>
<description>A brief description of the forecast weather condition (e.g. 'Overcast')</description>
<state readOnly="true" min="1" max="17" pattern="%s">
<options>
<!-- Source: https://content.meteoblue.com/nl/service-specifications/standards/symbols-and-pictograms#eztoc14635_1_6 -->
<option value="1">Sunny, cloudless sky</option>
<option value="2">Sunny and few clouds</option>
<option value="3">Partly cloudy</option>
<option value="4">Overcast</option>
<option value="5">Fog</option>
<option value="6">Overcast with rain</option>
<option value="7">Mixed with showers</option>
<option value="8">Showers, thunderstorms likely</option>
<option value="9">Overcast with snow</option>
<option value="10">Mixed with snow showers</option>
<option value="11">Mostly cloudy with a mixture of snow and rain</option>
<option value="12">Overcast with light rain</option>
<option value="13">Overcast with light snow</option>
<option value="14">Mostly cloudy with rain</option>
<option value="15">Mostly cloudy with snow</option>
<option value="16">Mostly cloudy with light rain</option>
<option value="17">Mostly cloudy with light snow</option>
</options>
</state>
</channel-type>
<channel-type id="icon" advanced="true">
<item-type>Image</item-type>
<label>Weather Icon</label>
<description>Icon representing the weather conditions</description>
</channel-type>
</thing:thing-descriptions>