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

View File

@@ -0,0 +1,58 @@
/**
* 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.sagercaster.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link SagerCasterBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class SagerCasterBindingConstants {
public static final String BINDING_ID = "sagercaster";
public static final String LOCAL = "local";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_SAGERCASTER = new ThingTypeUID(BINDING_ID, "sagercaster");
// Configuration elements
public static final String CONFIG_LOCATION = "location";
public static final String CONFIG_PERIOD = "observation-period";
// List of all Channel Groups Group Channel ids
public static final String GROUP_INPUT = "input";
public static final String GROUP_OUTPUT = "output";
// Output channel ids
public static final String CHANNEL_FORECAST = "forecast";
public static final String CHANNEL_VELOCITY = "velocity";
public static final String CHANNEL_VELOCITY_BEAUFORT = "velocity-beaufort";
public static final String CHANNEL_WINDFROM = "wind-from";
public static final String CHANNEL_WINDTO = "wind-to";
public static final String CHANNEL_WINDEVOLUTION = "wind-evolution";
public static final String CHANNEL_PRESSURETREND = "pressure-trend";
public static final String CHANNEL_TEMPERATURETREND = "temperature-trend";
// Input channel ids
public static final String CHANNEL_CLOUDINESS = "cloudiness";
public static final String CHANNEL_IS_RAINING = "is-raining";
public static final String CHANNEL_RAIN_QTTY = "rain-qtty";
public static final String CHANNEL_WIND_SPEED = "wind-speed-beaufort";
public static final String CHANNEL_TEMPERATURE = "temperature";
public static final String CHANNEL_PRESSURE = "pressure";
public static final String CHANNEL_WIND_ANGLE = "wind-angle";
}

View File

@@ -0,0 +1,66 @@
/**
* 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.sagercaster.internal;
import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.THING_TYPE_SAGERCASTER;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.sagercaster.internal.handler.SagerCasterHandler;
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.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link SagerCasterHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Gaël L'hopital - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.sagercaster")
@NonNullByDefault
public class SagerCasterHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_SAGERCASTER);
private final WindDirectionStateDescriptionProvider stateDescriptionProvider;
private final SagerWeatherCaster sagerWeatherCaster;
@Activate
public SagerCasterHandlerFactory(@Reference SagerWeatherCaster sagerWeatherCaster,
@Reference WindDirectionStateDescriptionProvider provider) {
this.stateDescriptionProvider = provider;
this.sagerWeatherCaster = sagerWeatherCaster;
}
@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_SAGERCASTER)) {
return new SagerCasterHandler(thing, stateDescriptionProvider, sagerWeatherCaster);
}
return null;
}
}

View File

@@ -0,0 +1,399 @@
/**
* 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
*/
/**
* eltiempo.selfip.com - Sager Weathercaster Algorhithm
*
* Copyright © 2008 Naish666 (eltiempo.selfip.com)
* October 2008 - v1.0
* Java transposition done by Gaël L'hopital - 2015
**
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***
* BT's Global Sager Weathercaster PHP Scripts For Cumulus (Weathercaster)
* by "Buford T. Justice" / "BTJustice"
* http://www.freewebs.com/btjustice/bt-forecasters.html
* 2014-02-05
*
* You may redistribute and use these PHP Scripts any way you wish as long as
* they remain FREE and money is not charged for their use directly or indirectly.
* If these PHP Scripts are used in your work or are modified in any way, please
* retain the full credit header.
* Based Upon:
* The Sager Weathercaster: A Scientific Instrument for Accurate Prediction of
* the Weather
* Copyright © 1969 by Raymond M. Sager and E. F. Sager
" The Sager Weathercaster predicts the weather quickly and accurately. It has been
* in use since 1942.
* Not a novelty, not a toy, this is a highly dependable, scientifically designed
* tool of inestimable value to travelers, farmers, hunters, sailors, yachtsmen, campers,
* fishermen, students -- in fact, to everyone who needs or wants to know what
* the weather will be."
* 378 possible forecasts determined from 4996 dial codes.
*/
package org.openhab.binding.sagercaster.internal;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Optional;
import java.util.Properties;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class is responsible for handling the SagerWeatherCaster algorithm
*
* @author Gaël L'hopital - Initial contribution
*/
@Component(service = SagerWeatherCaster.class, scope = ServiceScope.SINGLETON)
@NonNullByDefault
public class SagerWeatherCaster {
private final Properties forecaster = new Properties();
// Northern Polar Zone & Northern Tropical Zone
private final static String[] NPZDIRECTIONS = { "S", "SW", "W", "NW", "N", "NE", "E", "SE" };
// Northern Temperate Zone
private final static String[] NTZDIRECTIONS = { "N", "NE", "E", "SE", "S", "SW", "W", "NW" };
// Southern Polar Zone & Southern Tropical Zone
private final static String[] SPZDIRECTIONS = { "N", "NW", "W", "SW", "S", "SE", "E", "NE" };
// Southern Temperate Zone
private final static String[] STZDIRECTIONS = { "S", "SE", "E", "NE", "N", "NW", "W", "SW" };
private final Logger logger = LoggerFactory.getLogger(SagerWeatherCaster.class);
private Optional<Prevision> prevision = Optional.empty();
private @NonNullByDefault({}) String[] usedDirections;
private int currentBearing = -1;
private int windEvolution = -1; // Whether the wind during the last 6 hours has changed its direction by
// approximately 45 degrees or more
private int sagerPressure = -1; // currentPressure is Sea Level Adjusted (Relative) barometer in hPa or mB
private int pressureEvolution = -1; // pressureEvolution There are five points for registering the behavior of your
// barometer for a period of about 6 hours prior to the forecast.
private int nubes = -1;
private int currentBeaufort = -1;
private double cloudLevel = -1;
private boolean raining = false;
@Activate
public SagerWeatherCaster() {
InputStream input = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("/sagerForecaster.properties");
try {
forecaster.load(input);
} catch (IOException e) {
logger.warn("Error during Sager Forecaster startup", e);
}
}
public String[] getUsedDirections() {
return usedDirections;
}
public void setBearing(int newBearing, int oldBearing) {
int windEvol = sagerWindTrend(oldBearing, newBearing);
if ((windEvol != this.windEvolution) || (newBearing != currentBearing)) {
this.currentBearing = newBearing;
this.windEvolution = windEvol;
updatePrediction();
}
}
public void setPressure(double newPressure, double oldPressure) {
int newSagerPressure = sagerPressureLevel(newPressure);
int pressEvol = sagerPressureTrend(newPressure, oldPressure);
if ((pressEvol != this.pressureEvolution) || (newSagerPressure != sagerPressure)) {
this.sagerPressure = newSagerPressure;
this.pressureEvolution = pressEvol;
updatePrediction();
}
}
public void setCloudLevel(int cloudiness) {
this.cloudLevel = cloudiness;
sagerNubesUpdate();
}
public void setRaining(boolean raining) {
this.raining = raining;
sagerNubesUpdate();
}
public void setBeaufort(int beaufortIndex) {
if (currentBeaufort != beaufortIndex) {
this.currentBeaufort = beaufortIndex;
updatePrediction();
}
}
public int getBeaufort() {
return this.currentBeaufort;
}
public int getWindEvolution() {
return this.windEvolution;
}
public int getPressureEvolution() {
return this.pressureEvolution;
}
private void sagerNubesUpdate() {
int result;
if (!raining) {
if (cloudLevel > 80) {
result = 4; // overcast
} else if (cloudLevel > 50) {
result = 3; // mostly overcast
} else if (cloudLevel > 20) {
result = 2; // partly cloudy
} else {
result = 1; // clear
}
} else {
result = 5; // raining
}
if (result != nubes) {
this.nubes = result;
updatePrediction();
}
}
private static int sagerPressureLevel(double current) {
int result = 1;
if (current > 1029.46) {
result = 1;
} else if (current > 1019.3) {
result = 2;
} else if (current > 1012.53) {
result = 3;
} else if (current > 1005.76) {
result = 4;
} else if (current > 999) {
result = 5;
} else if (current > 988.8) {
result = 6;
} else if (current > 975.28) {
result = 7;
} else {
result = 8;
}
return result;
}
private static int sagerPressureTrend(double current, double historic) {
double evol = current - historic;
int result = 0;
if (evol > 1.4) {
result = 1; // Rising Rapidly
} else if (evol > 0.68) {
result = 2; // Rising Slowly
} else if (evol > -0.68) {
result = 3; // Normal
} else if (evol > -1.4) {
result = 4; // Decreasing Slowly
} else {
result = 5; // Decreasing Rapidly
}
return result;
}
private static int sagerWindTrend(double historic, double position) {
int result = 1; // Steady
double angle = 180 - Math.abs(Math.abs(position - historic) - 180);
if (angle > 45) {
int evol = (int) (historic + angle);
evol -= (evol > 360) ? 360 : 0;
result = (evol == position) ? 2 : 3; // Veering : Backing
}
return result;
}
private String getCompass() {
double step = 360.0 / NTZDIRECTIONS.length;
double b = Math.floor((this.currentBearing + (step / 2.0)) / step);
return NTZDIRECTIONS[(int) (b % NTZDIRECTIONS.length)];
}
private void updatePrediction() {
int zWind = Arrays.asList(usedDirections).indexOf(getCompass());
String d1 = "-";
switch (zWind) {
case 0:
if (windEvolution == 3) {
d1 = "A";
} else if (windEvolution == 1) {
d1 = "B";
} else if (windEvolution == 2) {
d1 = "C";
}
break;
case 1:
if (windEvolution == 3) {
d1 = "D";
} else if (windEvolution == 1) {
d1 = "E";
} else if (windEvolution == 2) {
d1 = "F";
}
break;
case 2:
if (windEvolution == 3) {
d1 = "G";
} else if (windEvolution == 1) {
d1 = "H";
} else if (windEvolution == 2) {
d1 = "J";
}
break;
case 3:
if (windEvolution == 3) {
d1 = "K";
} else if (windEvolution == 1) {
d1 = "L";
} else if (windEvolution == 2) {
d1 = "M";
}
break;
case 4:
if (windEvolution == 3) {
d1 = "N";
} else if (windEvolution == 1) {
d1 = "O";
} else if (windEvolution == 2) {
d1 = "P";
}
break;
case 5:
if (windEvolution == 3) {
d1 = "Q";
} else if (windEvolution == 1) {
d1 = "R";
} else if (windEvolution == 2) {
d1 = "S";
}
break;
case 6:
if (windEvolution == 3) {
d1 = "T";
} else if (windEvolution == 1) {
d1 = "U";
} else if (windEvolution == 2) {
d1 = "V";
}
break;
case 7:
if (windEvolution == 3) {
d1 = "W";
} else if (windEvolution == 1) {
d1 = "X";
} else if (windEvolution == 2) {
d1 = "Y";
}
break;
default:
if (currentBeaufort == 0) {
d1 = "Z";
}
}
String forecast = forecaster.getProperty(
d1 + String.valueOf(sagerPressure) + String.valueOf(pressureEvolution) + String.valueOf(nubes));
prevision = (forecast != null) ? Optional.of(new Prevision(forecast)) : Optional.empty();
}
public String getForecast() {
if (prevision.isPresent()) {
char forecast = prevision.get().zForecast;
return Character.toString(forecast);
} else {
return "-";
}
}
public String getWindVelocity() {
if (prevision.isPresent()) {
char windVelocity = prevision.get().zWindVelocity;
return Character.toString(windVelocity);
} else {
return "-";
}
}
public String getWindDirection() {
if (prevision.isPresent()) {
int direction = prevision.get().zWindDirection;
return String.valueOf(direction);
} else {
return "-";
}
}
public String getWindDirection2() {
if (prevision.isPresent()) {
int direction = prevision.get().zWindDirection2;
return String.valueOf(direction);
} else {
return "-";
}
}
public void setLatitude(double latitude) {
if (latitude >= 66.6) {
usedDirections = NPZDIRECTIONS;
} else if (latitude >= 23.5) {
usedDirections = NTZDIRECTIONS;
} else if (latitude >= 0) {
usedDirections = NPZDIRECTIONS;
} else if (latitude > -23.5) {
usedDirections = SPZDIRECTIONS;
} else if (latitude > -66.6) {
usedDirections = STZDIRECTIONS;
} else {
usedDirections = SPZDIRECTIONS;
}
}
private class Prevision {
public final char zForecast;
public final char zWindVelocity;
public final int zWindDirection;
public final int zWindDirection2;
public Prevision(String forecast) {
zForecast = forecast.charAt(0);
zWindVelocity = forecast.charAt(1);
zWindDirection = Character.getNumericValue(forecast.charAt(2));
zWindDirection2 = (forecast.length() > 3) ? Character.getNumericValue(forecast.charAt(3)) : -1;
}
}
}

View File

@@ -0,0 +1,28 @@
/**
* 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.sagercaster.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Component;
/**
* Dynamic provider of state options for WindDirections.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
@Component(service = { DynamicStateDescriptionProvider.class, WindDirectionStateDescriptionProvider.class })
public class WindDirectionStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
}

View File

@@ -0,0 +1,126 @@
/**
* 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.sagercaster.internal.discovery;
import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
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.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.i18n.LocationProvider;
import org.openhab.core.library.types.PointType;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SagerCasterDiscoveryService} creates things based on the configured location.
*
* @author Gaël L'hopital - Initial Contribution
*/
@NonNullByDefault
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.sagercaster")
public class SagerCasterDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(SagerCasterDiscoveryService.class);
private static final int DISCOVER_TIMEOUT_SECONDS = 30;
private static final int LOCATION_CHANGED_CHECK_INTERVAL = 60;
private @NonNullByDefault({}) LocationProvider locationProvider;
private @NonNullByDefault({}) ScheduledFuture<?> sagerCasterDiscoveryJob;
private @Nullable PointType previousLocation;
private static final ThingUID sagerCasterThing = new ThingUID(THING_TYPE_SAGERCASTER, LOCAL);
/**
* Creates a SagerCasterDiscoveryService with enabled autostart.
*/
public SagerCasterDiscoveryService() {
super(new HashSet<>(Arrays.asList(new ThingTypeUID(BINDING_ID, "-"))), DISCOVER_TIMEOUT_SECONDS, true);
}
@Override
protected void activate(@Nullable Map<String, @Nullable Object> configProperties) {
super.activate(configProperties);
}
@Override
protected void modified(@Nullable Map<String, @Nullable Object> configProperties) {
super.modified(configProperties);
}
@Override
protected void startScan() {
logger.debug("Starting Sager Weathercaster discovery scan");
PointType location = locationProvider.getLocation();
if (location == null) {
logger.debug("LocationProvider.getLocation() is not set -> Will not provide any discovery results");
return;
}
createResults(location);
}
@Override
protected void startBackgroundDiscovery() {
if (sagerCasterDiscoveryJob == null) {
sagerCasterDiscoveryJob = scheduler.scheduleWithFixedDelay(() -> {
PointType currentLocation = locationProvider.getLocation();
if (currentLocation != null && !Objects.equals(currentLocation, previousLocation)) {
logger.debug("Location has been changed from {} to {}: Creating new discovery results",
previousLocation, currentLocation);
createResults(currentLocation);
previousLocation = currentLocation;
}
}, 0, LOCATION_CHANGED_CHECK_INTERVAL, TimeUnit.SECONDS);
logger.debug("Scheduled SagerCaster location-changed job every {} seconds",
LOCATION_CHANGED_CHECK_INTERVAL);
}
}
@Override
protected void stopBackgroundDiscovery() {
logger.debug("Stopping Sager Weathercaster background discovery");
if (sagerCasterDiscoveryJob != null && !sagerCasterDiscoveryJob.isCancelled()) {
if (sagerCasterDiscoveryJob.cancel(true)) {
sagerCasterDiscoveryJob = null;
logger.debug("Stopped SagerCaster device background discovery");
}
}
}
public void createResults(PointType location) {
String propGeolocation;
propGeolocation = String.format("%s,%s", location.getLatitude(), location.getLongitude());
thingDiscovered(DiscoveryResultBuilder.create(sagerCasterThing).withLabel("Local Sager Weathercaster")
.withRepresentationProperty(CONFIG_LOCATION).withProperty(CONFIG_LOCATION, propGeolocation).build());
}
@Reference
protected void setLocationProvider(LocationProvider locationProvider) {
this.locationProvider = locationProvider;
}
protected void unsetLocationProvider(LocationProvider locationProvider) {
this.locationProvider = null;
}
}

View File

@@ -0,0 +1,50 @@
/**
* 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.sagercaster.internal.handler;
import java.util.Optional;
import java.util.SortedMap;
import java.util.TreeMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link ExpiringMap} is responsible for storing a list of values of class T
* Values older than eldestAge are discarded at each insert of a new one
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
class ExpiringMap<T> {
private final SortedMap<Long, T> values = new TreeMap<>();
private Optional<T> agedValue = Optional.empty();
private long eldestAge = 0;
public void setObservationPeriod(long eldestAge) {
this.eldestAge = eldestAge;
}
public void put(T newValue) {
long now = System.currentTimeMillis();
values.put(now, newValue);
Optional<Long> eldestKey = values.keySet().stream().filter(key -> key < now - eldestAge).findFirst();
if (eldestKey.isPresent()) {
agedValue = Optional.of(values.get(eldestKey.get()));
values.entrySet().removeIf(map -> map.getKey() <= eldestKey.get());
}
}
public Optional<T> getAgedValue() {
return agedValue;
}
}

View File

@@ -0,0 +1,286 @@
/**
* 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.sagercaster.internal.handler;
import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.*;
import static org.openhab.core.library.unit.MetricPrefix.HECTO;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Angle;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.Pressure;
import javax.measure.quantity.Temperature;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.sagercaster.internal.SagerWeatherCaster;
import org.openhab.binding.sagercaster.internal.WindDirectionStateDescriptionProvider;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.library.unit.SIUnits;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SagerCasterHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Gaël L'hopital - Initial contribution
*/
@NonNullByDefault
public class SagerCasterHandler extends BaseThingHandler {
private final static String FORECAST_PENDING = "0";
private final static Set<String> SHOWERS = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList("G", "K", "L", "R", "S", "T", "U", "W")));
private final Logger logger = LoggerFactory.getLogger(SagerCasterHandler.class);
private final SagerWeatherCaster sagerWeatherCaster;
private final WindDirectionStateDescriptionProvider stateDescriptionProvider;
private int currentTemp = 0;
private final ExpiringMap<QuantityType<Pressure>> pressureCache = new ExpiringMap<>();
private final ExpiringMap<QuantityType<Temperature>> temperatureCache = new ExpiringMap<>();
private final ExpiringMap<QuantityType<Angle>> bearingCache = new ExpiringMap<>();
public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider stateDescriptionProvider,
SagerWeatherCaster sagerWeatherCaster) {
super(thing);
this.stateDescriptionProvider = stateDescriptionProvider;
this.sagerWeatherCaster = sagerWeatherCaster;
}
@Override
public void initialize() {
String location = (String) getConfig().get(CONFIG_LOCATION);
int observationPeriod = ((BigDecimal) getConfig().get(CONFIG_PERIOD)).intValue();
String latitude = location.split(",")[0];
sagerWeatherCaster.setLatitude(Double.parseDouble(latitude));
long period = TimeUnit.SECONDS.toMillis(observationPeriod);
pressureCache.setObservationPeriod(period);
bearingCache.setObservationPeriod(period);
temperatureCache.setObservationPeriod(period);
defineWindDirectionStateDescriptions();
updateStatus(ThingStatus.ONLINE);
}
private void defineWindDirectionStateDescriptions() {
List<StateOption> options = new ArrayList<>();
String[] directions = sagerWeatherCaster.getUsedDirections();
for (int i = 0; i < directions.length; i++) {
int secondDirection = i < directions.length - 1 ? i + 1 : 0;
String windDescription = directions[i] + " or " + directions[secondDirection] + " winds";
options.add(new StateOption(String.valueOf(i + 1), windDescription));
}
options.add(new StateOption("9", "Shifting / Variable winds"));
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDFROM),
options);
stateDescriptionProvider.setStateOptions(new ChannelUID(getThing().getUID(), GROUP_OUTPUT, CHANNEL_WINDTO),
options);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
postNewForecast();
} else {
String id = channelUID.getIdWithoutGroup();
switch (id) {
case CHANNEL_CLOUDINESS:
logger.debug("Octa cloud level changed, updating forecast");
if (command instanceof QuantityType) {
@SuppressWarnings("unchecked")
QuantityType<Dimensionless> cloudiness = (QuantityType<Dimensionless>) command;
scheduler.submit(() -> {
sagerWeatherCaster.setCloudLevel(cloudiness.intValue());
postNewForecast();
});
break;
}
case CHANNEL_IS_RAINING:
logger.debug("Rain status updated, updating forecast");
if (command instanceof OnOffType) {
OnOffType isRaining = ((OnOffType) command);
scheduler.submit(() -> {
sagerWeatherCaster.setRaining(isRaining == OnOffType.ON);
postNewForecast();
});
} else {
logger.debug("Channel '{}' can only accept Switch type commands.", channelUID);
}
break;
case CHANNEL_RAIN_QTTY:
logger.debug("Rain status updated, updating forecast");
if (command instanceof QuantityType) {
QuantityType<?> newQtty = ((QuantityType<?>) command);
scheduler.submit(() -> {
sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
postNewForecast();
});
} else if (command instanceof DecimalType) {
DecimalType newQtty = ((DecimalType) command);
scheduler.submit(() -> {
sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
postNewForecast();
});
} else {
logger.debug("Channel '{}' can accept Number, Number:Speed, Number:Length type commands.",
channelUID);
}
break;
case CHANNEL_WIND_SPEED:
logger.debug("Updated wind speed, updating forecast");
if (command instanceof DecimalType) {
DecimalType newValue = (DecimalType) command;
scheduler.submit(() -> {
sagerWeatherCaster.setBeaufort(newValue.intValue());
postNewForecast();
});
} else {
logger.debug("Channel '{}' only accepts DecimalType commands.", channelUID);
}
break;
case CHANNEL_PRESSURE:
logger.debug("Sea-level pressure updated, updating forecast");
if (command instanceof QuantityType) {
@SuppressWarnings("unchecked")
QuantityType<Pressure> newPressure = ((QuantityType<Pressure>) command)
.toUnit(HECTO(SIUnits.PASCAL));
if (newPressure != null) {
pressureCache.put(newPressure);
Optional<QuantityType<Pressure>> agedPressure = pressureCache.getAgedValue();
if (agedPressure.isPresent()) {
scheduler.submit(() -> {
sagerWeatherCaster.setPressure(newPressure.doubleValue(),
agedPressure.get().doubleValue());
updateChannelString(GROUP_OUTPUT, CHANNEL_PRESSURETREND,
String.valueOf(sagerWeatherCaster.getPressureEvolution()));
postNewForecast();
});
} else {
updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, FORECAST_PENDING);
}
}
}
break;
case CHANNEL_TEMPERATURE:
logger.debug("Temperature updated");
if (command instanceof QuantityType) {
@SuppressWarnings("unchecked")
QuantityType<Temperature> newTemperature = ((QuantityType<Temperature>) command)
.toUnit(SIUnits.CELSIUS);
if (newTemperature != null) {
temperatureCache.put(newTemperature);
currentTemp = newTemperature.intValue();
Optional<QuantityType<Temperature>> agedTemperature = temperatureCache.getAgedValue();
if (agedTemperature.isPresent()) {
double delta = newTemperature.doubleValue() - agedTemperature.get().doubleValue();
String trend = (delta > 3) ? "1"
: (delta > 0.3) ? "2" : (delta > -0.3) ? "3" : (delta > -3) ? "4" : "5";
updateChannelString(GROUP_OUTPUT, CHANNEL_TEMPERATURETREND, trend);
}
}
}
break;
case CHANNEL_WIND_ANGLE:
logger.debug("Updated wind direction, updating forecast");
if (command instanceof QuantityType) {
@SuppressWarnings("unchecked")
QuantityType<Angle> newAngle = (QuantityType<Angle>) command;
bearingCache.put(newAngle);
Optional<QuantityType<Angle>> agedAngle = bearingCache.getAgedValue();
if (agedAngle.isPresent()) {
scheduler.submit(() -> {
sagerWeatherCaster.setBearing(newAngle.intValue(), agedAngle.get().intValue());
updateChannelString(GROUP_OUTPUT, CHANNEL_WINDEVOLUTION,
String.valueOf(sagerWeatherCaster.getWindEvolution()));
postNewForecast();
});
}
}
break;
default:
logger.debug("The binding can not handle command: {} on channel: {}", command, channelUID);
}
}
}
private void postNewForecast() {
String forecast = sagerWeatherCaster.getForecast();
// Sharpens forecast if current temp is below 2 degrees, likely to be flurries rather than shower
forecast += SHOWERS.contains(forecast) ? (currentTemp > 2) ? "1" : "2" : "";
updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, forecast);
updateChannelString(GROUP_OUTPUT, CHANNEL_WINDFROM, sagerWeatherCaster.getWindDirection());
updateChannelString(GROUP_OUTPUT, CHANNEL_WINDTO, sagerWeatherCaster.getWindDirection2());
String velocity = sagerWeatherCaster.getWindVelocity();
updateChannelString(GROUP_OUTPUT, CHANNEL_VELOCITY, velocity);
int predictedBeaufort = sagerWeatherCaster.getBeaufort();
switch (velocity) {
case "N":
predictedBeaufort += 1;
break;
case "F":
predictedBeaufort = 4;
break;
case "S":
predictedBeaufort = 6;
break;
case "G":
predictedBeaufort = 8;
break;
case "W":
predictedBeaufort = 10;
break;
case "H":
predictedBeaufort = 12;
break;
case "D":
predictedBeaufort -= 1;
break;
}
updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, predictedBeaufort);
}
protected void updateChannelString(String group, String channelId, String value) {
ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
if (isLinked(id)) {
updateState(id, new StringType(value));
}
}
protected void updateChannelDecimal(String group, String channelId, int value) {
ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
if (isLinked(id)) {
updateState(id, new DecimalType(value));
}
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="sagercaster" 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>SagerCaster Binding</name>
<description>The Sager Weathercaster is a scientific instrument for accurate prediction of the weather.</description>
<author>Gaël L'hopital</author>
</binding:binding>

View File

@@ -0,0 +1,63 @@
# binding
binding.sagercaster.name = SagerCaster Binding
binding.sagercaster.description = Die SagerCaster-Erweiterung wird zur Erstellung von Wettervorhersagen verwendet.
# channel types
channel-type.sagercaster.forecast.state.option.0 = Warten Sie etwas länger auf eine Vorhersage
channel-type.sagercaster.forecast.state.option.A = Gutes Wetter
channel-type.sagercaster.forecast.state.option.B = Gutes Wetter und Erwärmung
channel-type.sagercaster.forecast.state.option.C = Gutes Wetter und Abkühlung
channel-type.sagercaster.forecast.state.option.D = Instabil
channel-type.sagercaster.forecast.state.option.E = Instabil und Erwärmung
channel-type.sagercaster.forecast.state.option.F = Instabil und Abkühlung
channel-type.sagercaster.forecast.state.option.G = Zunehmende Bewölkung oder sehr bewölkt, gefolgt von Niederschlag oder Schauern / Schnee
channel-type.sagercaster.forecast.state.option.G1 = Zunehmende oder sehr trübe Bewölkung, gefolgt von Niederschlag oder Schauern
channel-type.sagercaster.forecast.state.option.G2 = Zunehmende Bewölkung oder sehr bewölkt, gefolgt von Niederschlag oder Schnee
channel-type.sagercaster.forecast.state.option.H = Zunehmende Bewölkung oder sehr bewölkt, gefolgt von Niederschlag oder Schauern und Erwärmung
channel-type.sagercaster.forecast.state.option.J = Regengüsse
channel-type.sagercaster.forecast.state.option.K = Regengüsse / Schnee und Erwärmung
channel-type.sagercaster.forecast.state.option.K1 = Regengüsse und Erwärmung
channel-type.sagercaster.forecast.state.option.K2 = Schnee und Erwärmung
channel-type.sagercaster.forecast.state.option.L = Regengüsse / Schnee und Abkühlung
channel-type.sagercaster.forecast.state.option.L1 = Regengüsse und Abkühlung
channel-type.sagercaster.forecast.state.option.L2 = Regengüsse und Abkühlung
channel-type.sagercaster.forecast.state.option.M = Niederschlag
channel-type.sagercaster.forecast.state.option.N = Niederschlag und Erwärmung
channel-type.sagercaster.forecast.state.option.P = Niederschlag und Abkühlung dann wahrscheinliche Besserung innerhalb von 24 Stunden
channel-type.sagercaster.forecast.state.option.R = Niederschlag oder Schauer / Schnee und Besserung innerhalb von 12 Stunden
channel-type.sagercaster.forecast.state.option.R1 = Niederschlag oder Schauer und Besserung innerhalb von 12 Stunden
channel-type.sagercaster.forecast.state.option.R2 = Niederschlag oder Schnee und Besserung innerhalb von 12 Stunden
channel-type.sagercaster.forecast.state.option.S = Niederschlag oder Schauer / Schnee und Verbesserung innerhalb von 12 Stunden und Abkühlung
channel-type.sagercaster.forecast.state.option.S1 = Niederschlag oder Regengüsse und Besserung innerhalb von 12 Stunden und Erfrischung
channel-type.sagercaster.forecast.state.option.S2 = Niederschlag oder Schnee und Verbesserung innerhalb von 12 Stunden und Abkühlung
channel-type.sagercaster.forecast.state.option.T = Niederschlag oder Schauer / Schnee und schnelle Besserung innerhalb von 6 Stunden
channel-type.sagercaster.forecast.state.option.T1 = Niederschlag oder Schauer und schnelle Besserung innerhalb von 6 Stunden
channel-type.sagercaster.forecast.state.option.T2 = Niederschlag oder Schnee und schnelle Besserung innerhalb von 6 Stunden
channel-type.sagercaster.forecast.state.option.U = Niederschlag oder Schauer / Schnee und schnelle Besserung innerhalb von 6 Stunden, dann Abkühlung
channel-type.sagercaster.forecast.state.option.U1 = Niederschlag oder Schauer und schnelle Besserung innerhalb von 6 Stunden, dann Abkühlung
channel-type.sagercaster.forecast.state.option.U2 = Niederschlag oder Schnee und schnelle Besserung innerhalb von 6 Stunden, dann Abkühlung
channel-type.sagercaster.forecast.state.option.W = Niederschlag oder Schauer / Schnee, gefolgt von gutem Wetter innerhalb von 6 Stunden und Erfrischung
channel-type.sagercaster.forecast.state.option.W1 = Niederschlag oder Schauer, gefolgt von gutem Wetter innerhalb von 6 Stunden und Erfrischung
channel-type.sagercaster.forecast.state.option.W2 = Niederschlag oder Schnee, gefolgt von gutem Wetter innerhalb von 6 Stunden und Abkühlung
channel-type.sagercaster.forecast.state.option.X = Instabil, gefolgt von gutem Wetter
channel-type.sagercaster.forecast.state.option.Y = Instabil, gefolgt von gutem Wetter innerhalb von 6 Stunden und Erfrischung
channel-type.sagercaster.velocity.state.option.N = Wahrscheinlich steigend
channel-type.sagercaster.velocity.state.option.F = Mäßig bis frisch
channel-type.sagercaster.velocity.state.option.S = Starke Winde können dem Sturm im offenen Raum vorausgehen
channel-type.sagercaster.velocity.state.option.G = Sturm
channel-type.sagercaster.velocity.state.option.W = Gefährlicher Sturm
channel-type.sagercaster.velocity.state.option.H = Orkan
channel-type.sagercaster.velocity.state.option.D = Abkühlend oder moderat, wenn die aktuellen Winde kühl oder stark sind
channel-type.sagercaster.velocity.state.option.U = Keine wesentliche Änderung. Tendenz zur Zunahme während des Tages, Abnahme am Abend.
channel-type.sagercaster.wind-evolution.state.option.1 = Stabil
channel-type.sagercaster.wind-evolution.state.option.2 = Stätig
channel-type.sagercaster.wind-evolution.state.option.3 = Variabel
channel-type.sagercaster.trend.state.option.1 = Schneller Anstieg
channel-type.sagercaster.trend.state.option.2 = Langsamer Anstieg
channel-type.sagercaster.trend.state.option.3 = Stabil
channel-type.sagercaster.trend.state.option.4 = Mäßiger Rückgang
channel-type.sagercaster.trend.state.option.5 = Schneller Rückgang

View File

@@ -0,0 +1,64 @@
# binding
binding.sagercaster.name = Extension SagerCaster
binding.sagercaster.description = L'extension SagerCaster permet d'établir des prévisions météo.
# channel types
channel-type.sagercaster.forecast.state.option.0 = Patientez encore un peu pour une prédiction
channel-type.sagercaster.forecast.state.option.A = Beau-temps
channel-type.sagercaster.forecast.state.option.B = Beau-temps et réchauffement
channel-type.sagercaster.forecast.state.option.C = Beau-temps et rafraichissement
channel-type.sagercaster.forecast.state.option.D = Instable
channel-type.sagercaster.forecast.state.option.E = Instable et réchauffement
channel-type.sagercaster.forecast.state.option.F = Instable et rafraichissement
channel-type.sagercaster.forecast.state.option.G = Nébulosité croissante ou très nuageux suivi de précititations ou averses / neige
channel-type.sagercaster.forecast.state.option.G1 = Nébulosité croissante ou très nuageux suivi de précititations ou averses
channel-type.sagercaster.forecast.state.option.G2 = Nébulosité croissante ou très nuageux suivi de précititations ou neige
channel-type.sagercaster.forecast.state.option.H = Nébulosité croissante ou très nuageux suivi de précititations ou averses et réchauffement
channel-type.sagercaster.forecast.state.option.J = Averses
channel-type.sagercaster.forecast.state.option.K = Averses / neige et réchauffement
channel-type.sagercaster.forecast.state.option.K1 = Averses et réchauffement
channel-type.sagercaster.forecast.state.option.K2 = Neige et réchauffement
channel-type.sagercaster.forecast.state.option.L = Averses / neige et rafraichissement
channel-type.sagercaster.forecast.state.option.L1 = Averses et rafraichissement
channel-type.sagercaster.forecast.state.option.L2 = Neige et rafraichissement
channel-type.sagercaster.forecast.state.option.M = Précipitations
channel-type.sagercaster.forecast.state.option.N = Précipitations et réchauffement
channel-type.sagercaster.forecast.state.option.P = Précipitations et rafraichissement puis amélioration probable dans les 24 heures
channel-type.sagercaster.forecast.state.option.R = Précipitations ou averses / neige et amélioration dans les 12 heures
channel-type.sagercaster.forecast.state.option.R1 = Précipitations ou averses et amélioration dans les 12 heures
channel-type.sagercaster.forecast.state.option.R2 = Précipitations ou neige et amélioration dans les 12 heures
channel-type.sagercaster.forecast.state.option.S = Précipitations ou averses / neige et amélioration dans les 12 heures et rafraichissement
channel-type.sagercaster.forecast.state.option.S1 = Précipitations ou averses et amélioration dans les 12 heures et rafraichissement
channel-type.sagercaster.forecast.state.option.S2 = Précipitations ou neige et amélioration dans les 12 heures et rafraichissement
channel-type.sagercaster.forecast.state.option.T = Précipitations ou averses / neige et amélioration rapide dans les 6 heures
channel-type.sagercaster.forecast.state.option.T1 = Précipitations ou averses et amélioration rapide dans les 6 heures
channel-type.sagercaster.forecast.state.option.T2 = Précipitations ou neige et amélioration rapide dans les 6 heures
channel-type.sagercaster.forecast.state.option.U = Précipitations ou averses / neige et amélioration rapide dans les 6 heures puis rafraichissement
channel-type.sagercaster.forecast.state.option.U1 = Précipitations ou averses et amélioration rapide dans les 6 heures puis rafraichissement
channel-type.sagercaster.forecast.state.option.U2 = Précipitations ou neige et amélioration rapide dans les 6 heures puis rafraichissement
channel-type.sagercaster.forecast.state.option.W = Précipitations ou averses / neige suivi de beau temps rapide dans les 6 heures et rafraichissement
channel-type.sagercaster.forecast.state.option.W1 = Précipitations ou averses suivi de beau temps rapide dans les 6 heures et rafraichissement
channel-type.sagercaster.forecast.state.option.W2 = Précipitations ou neige suivi de beau temps rapide dans les 6 heures et rafraichissement
channel-type.sagercaster.forecast.state.option.X = Instable suivi de beau temps
channel-type.sagercaster.forecast.state.option.Y = Instable suivi de beau temps rapide dans les 6 heures et rafraichissement
channel-type.sagercaster.velocity.state.option.N = Probablement en augmentation
channel-type.sagercaster.velocity.state.option.F = Modéré à frais
channel-type.sagercaster.velocity.state.option.S = Vents forts pouvant précéder tempête en espace ouvert
channel-type.sagercaster.velocity.state.option.G = Tempête
channel-type.sagercaster.velocity.state.option.W = Tempête dangereuse
channel-type.sagercaster.velocity.state.option.H = Ouragan
channel-type.sagercaster.velocity.state.option.D = Décroissant ou en modération si les vents actuels sont frais ou forts
channel-type.sagercaster.velocity.state.option.U = Pas de changement notable. Tendance pour augmentation progressive dans la journée, diminution dans la soirée.
channel-type.sagercaster.wind-evolution.state.option.1 = Stable
channel-type.sagercaster.wind-evolution.state.option.2 = Horaire
channel-type.sagercaster.wind-evolution.state.option.3 = Anti-horaire
channel-type.sagercaster.trend.state.option.1 = Augmentation rapide
channel-type.sagercaster.trend.state.option.2 = Augmentation lente
channel-type.sagercaster.trend.state.option.3 = Stable
channel-type.sagercaster.trend.state.option.4 = Baisse modérée
channel-type.sagercaster.trend.state.option.5 = Baisse rapide

View File

@@ -0,0 +1,244 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="sagercaster"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="sagercaster">
<label>SagerCaster Thing</label>
<description>This thing represents a forecast for a given location</description>
<channel-groups>
<channel-group id="input" typeId="input"/>
<channel-group id="output" typeId="output"/>
</channel-groups>
<representation-property>location</representation-property>
<config-description>
<parameter name="location" type="text" required="true"
pattern="^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)[,]\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$">
<label>Location</label>
<context>location</context>
<description>Your geo coordinates separated with comma (e.g. "37.8,-122.4").</description>
</parameter>
<parameter name="observation-period" type="integer" min="0" max="6" required="true">
<label>Observation Period</label>
<description>SagerWeatherCaster needs a minimum representative period of time to produce meaningfull results.
Defaults to 6 hours</description>
<default>6</default>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="input">
<label>Inputs</label>
<description>The channels used to build the forecast results</description>
<channels>
<channel id="cloudiness" typeId="cloudiness"/>
<channel id="is-raining" typeId="is-raining"/>
<channel id="rain-qtty" typeId="rain-qtty"/>
<channel id="wind-speed-beaufort" typeId="wind-speed-beaufort"/>
<channel id="pressure" typeId="pressure"/>
<channel id="temperature" typeId="temperature"/>
<channel id="wind-angle" typeId="wind-angle-rw"/>
</channels>
</channel-group-type>
<channel-group-type id="output">
<label>Results</label>
<description>Results of the Sager Weathercaster algorithm</description>
<channels>
<channel id="forecast" typeId="forecast"/>
<channel id="velocity" typeId="velocity"/>
<channel id="velocity-beaufort" typeId="wind-speed-beaufort">
<label>Wind Velocity</label>
</channel>
<channel id="wind-from" typeId="wind-direction">
<label>Wind From</label>
</channel>
<channel id="wind-to" typeId="wind-direction">
<label>Wind To</label>
</channel>
<channel id="wind-evolution" typeId="wind-evolution"/>
<channel id="pressure-trend" typeId="trend">
<label>Pressure Trend</label>
<description>Pressure Evolution trend over observation delay</description>
</channel>
<channel id="temperature-trend" typeId="trend">
<label>Temperature Trend</label>
<description>Temperature Evolution trend over observation delay</description>
</channel>
</channels>
</channel-group-type>
<channel-type id="forecast">
<item-type>String</item-type>
<label>Weather Forecast</label>
<state readOnly="true" pattern="%s">
<options>
<option value="0">Not enough historic data to study pressure evolution, wait a bit ...</option>
<option value="A">Fair</option>
<option value="B">Fair and warmer</option>
<option value="C">Fair and cooler</option>
<option value="D">Unsettled</option>
<option value="E">Unsettled and warmer</option>
<option value="F">Unsettled and cooler</option>
<option value="G">Increasing cloudiness or overcast followed by Precipitation or showers/Flurries</option>
<option value="G1">Increasing cloudiness or overcast followed by Precipitation or showers</option>
<option value="G2">Increasing cloudiness or overcast followed by Precipitation or Flurries</option>
<option value="H">Increasing cloudiness or overcast followed by Precipitation or showers and warmer</option>
<option value="J">Showers</option>
<option value="K">Showers/Flurries and warmer</option>
<option value="K1">Showers and warmer</option>
<option value="K2">Flurries and warmer</option>
<option value="L">Showers/Flurries and cooler</option>
<option value="L1">Showers and cooler</option>
<option value="L2">Flurries and cooler</option>
<option value="M">Precipitation</option>
<option value="N">Precipitation and warmer</option>
<option value="P">Precipitation and turning cooler; then improvement likely in 24 hours</option>
<option value="R">Precipitation or showers/Flurries followed by improvement (within 12 hours)</option>
<option value="R1">Precipitation or showers followed by improvement (within 12 hours)</option>
<option value="R2">Precipitation or flurries followed by improvement (within 12 hours)</option>
<option value="S">Precipitation or showers/Flurries followed by improvement (within 12 hours) and becoming cooler</option>
<option value="S1">Precipitation or showers followed by improvement (within 12 hours) and becoming cooler</option>
<option value="S2">Precipitation or flurries followed by improvement (within 12 hours) and becoming cooler</option>
<option value="T">Precipitation or showers/Flurries followed by improvement early in period (within 6 hours)</option>
<option value="T1">Precipitation or showers followed by improvement early in period (within 6 hours)</option>
<option value="T2">Precipitation or flurries followed by improvement early in period (within 6 hours)</option>
<option value="U">Precipitation or showers/Flurries by improvement early in period (within 6 hours) and becoming
cooler</option>
<option value="U1">Precipitation or showers by improvement early in period (within 6 hours) and becoming cooler</option>
<option value="U2">Precipitation or flurries by improvement early in period (within 6 hours) and becoming cooler</option>
<option value="W">Precipitation or showers/Flurries followed by fair early in period (within 6 hours) and
becoming cooler</option>
<option value="W1">Precipitation or showers followed by fair early in period (within 6 hours) and becoming cooler</option>
<option value="W2">Precipitation or flurries followed by fair early in period (within 6 hours) and becoming cooler</option>
<option value="X">Unsettled followed by fair</option>
<option value="Y">Unsettled followed by fair early in period (within 6 hours) and becoming cooler</option>
</options>
</state>
</channel-type>
<channel-type id="velocity">
<item-type>String</item-type>
<label>Wind Velocity</label>
<state readOnly="true" pattern="%s">
<options>
<option value="N">Probably increasing</option>
<option value="F">Moderate to fresh</option>
<option value="S">Strong winds may precede gales over open water)</option>
<option value="G">Gale</option>
<option value="W">Dangerous gale (whole gale)</option>
<option value="H">Hurricane</option>
<option value="D">Diminishing, or moderating somewhat if current winds are of fresh to strong velocity</option>
<option value="U">No important change. Some tendency for slight increase in winds during day, diminishing in
evening</option>
</options>
</state>
</channel-type>
<channel-type id="wind-direction">
<item-type>String</item-type>
<label>Wind Direction</label>
<description>Wind direction</description>
<category>Wind</category>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="wind-angle-rw">
<item-type>Number:Angle</item-type>
<label>Wind Angle</label>
<description>Wind Angle</description>
<category>Wind</category>
<state readOnly="false" pattern="%.0f %unit%"/>
</channel-type>
<channel-type id="wind-evolution" advanced="true">
<item-type>String</item-type>
<label>Wind Evolution</label>
<description>Wind bearing evolution trend over observation delay</description>
<state readOnly="true" pattern="%s">
<options>
<option value="1">Steady</option>
<option value="2">Veering</option>
<option value="3">Backing</option>
</options>
</state>
</channel-type>
<channel-type id="trend" advanced="true">
<item-type>String</item-type>
<label>Measure Trend</label>
<description>Measure evolution trend over observation delay</description>
<state readOnly="true" pattern="%s">
<options>
<option value="1">Rising Rapidly</option>
<option value="2">Rising Slowly</option>
<option value="3">Normal</option>
<option value="4">Decreasing Slowly</option>
<option value="5">Decreasing Rapidly</option>
</options>
</state>
</channel-type>
<channel-type id="timestamp" advanced="true">
<item-type>DateTime</item-type>
<label>Calculation Time</label>
<description>Weather forecast calculation date and time</description>
<category>Observation time</category>
<state readOnly="true"></state>
</channel-type>
<channel-type id="cloudiness">
<item-type>Number:Dimensionless</item-type>
<label>Cloudiness</label>
<description>Current cloudiness.</description>
<category>Clouds</category>
<state min="0" max="100" pattern="%d %%"/>
</channel-type>
<channel-type id="rain-qtty" advanced="true">
<item-type>Number</item-type>
<label>Rain Quantity</label>
<description>Current rain quantity</description>
<category>Rain</category>
<state pattern="%.2f %unit%"/>
</channel-type>
<channel-type id="is-raining">
<item-type>Switch</item-type>
<label>Raining</label>
<description>Is it currently raining ?</description>
<category>Rain</category>
</channel-type>
<channel-type id="wind-speed-beaufort">
<item-type>Number</item-type>
<label>Beaufort</label>
<description>Wind speed using Beaufort Scale</description>
<category>Wind</category>
<state min="0" max="12" pattern="%d"/>
</channel-type>
<channel-type id="pressure">
<item-type>Number:Pressure</item-type>
<label>Sea Level Pressure</label>
<description>Sea Level Pressure</description>
<category>Pressure</category>
<state pattern="%.3f %unit%"/>
</channel-type>
<channel-type id="temperature">
<item-type>Number:Temperature</item-type>
<label>Temperature</label>
<description>Current temperature</description>
<category>Temperature</category>
<state pattern="%.1f %unit%"/>
</channel-type>
</thing:thing-descriptions>