* To operate, this binding will need to use channel values provided by other means (e.g. Weather Binding, Netatmo, a 1-Wire personal weather station...)
-* This binding buffers readings for some hours before producing weather forecasts(wind direction and sea level pressure). SagerWeatherCaster needs an observation period of minimum 6 hours.
+* This binding buffers readings for some hours before producing weather forecasts(wind direction and sea level pressure).
+SagerWeatherCaster needs an observation period of minimum 6 hours.
For these reasons, this binding is not a binding in the usual sense.
| Name | Type | Description |
|--------------------|----------|--------------------------------------------------------------------------|
-| location | Location | Latitude and longitude of the desired weather forecast. |
+| location (*) | Location | Latitude and longitude of the desired weather forecast. |
| observation-period | int | Minimum delay (in hours) before producing forecasts. Defaulted to 6. |
+(*) Only latitude is used by the algorithm.
+
## Channels
The binding will use some input channels, that can be configured directly with profiles (sample below).
| wind-speed-beaufort | input |Number | Wind speed expressed using the Beaufort scale |
| pressure | input |Number:Pressure | Sea level pressure |
| wind-angle | input |Number:Angle | Wind direction |
+| temperature | input |Number:Temperature | Outside temperature |
+| timestamp | output |DateTime | Timestamp of the last forecast update |
| forecast | output |String | Description of the weather forecast |
| velocity | output |String | Description of the expected wind evolution |
| velocity-beaufort | output |Number | Expected wind evolution using the Beaufort scale |
public static final String CHANNEL_WINDEVOLUTION = "wind-evolution";
public static final String CHANNEL_PRESSURETREND = "pressure-trend";
public static final String CHANNEL_TEMPERATURETREND = "temperature-trend";
+ public static final String CHANNEL_TIMESTAMP = "timestamp";
+
// Input channel ids
public static final String CHANNEL_CLOUDINESS = "cloudiness";
public static final String CHANNEL_IS_RAINING = "is-raining";
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.sagercaster.internal.caster.SagerWeatherCaster;
import org.openhab.binding.sagercaster.internal.handler.SagerCasterHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
+++ /dev/null
-/**
- * Copyright (c) 2010-2021 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 {
- // 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 final Properties forecaster = new Properties();
-
- private Optional<Prevision> prevision = Optional.empty();
- private String[] usedDirections = NTZDIRECTIONS; // Defaulted to Northern Zone
-
- 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() {
- try (InputStream input = Thread.currentThread().getContextClassLoader()
- .getResourceAsStream("/sagerForecaster.properties")) {
- 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 != windEvolution) || (newBearing != currentBearing)) {
- currentBearing = newBearing;
- windEvolution = windEvol;
- updatePrediction();
- }
- }
-
- public void setPressure(double newPressure, double oldPressure) {
- int newSagerPressure = sagerPressureLevel(newPressure);
- int pressEvol = sagerPressureTrend(newPressure, oldPressure);
- if ((pressEvol != pressureEvolution) || (newSagerPressure != sagerPressure)) {
- sagerPressure = newSagerPressure;
- pressureEvolution = pressEvol;
- updatePrediction();
- }
- }
-
- public void setCloudLevel(int cloudiness) {
- cloudLevel = cloudiness;
- sagerNubesUpdate();
- }
-
- public void setRaining(boolean isRaining) {
- raining = isRaining;
- sagerNubesUpdate();
- }
-
- public void setBeaufort(int beaufortIndex) {
- if (currentBeaufort != beaufortIndex) {
- currentBeaufort = beaufortIndex;
- updatePrediction();
- }
- }
-
- public int getBeaufort() {
- return currentBeaufort;
- }
-
- public int getWindEvolution() {
- return windEvolution;
- }
-
- public int getPressureEvolution() {
- return 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) {
- nubes = result;
- updatePrediction();
- }
- }
-
- private static int sagerPressureLevel(double current) {
- if (current > 1029.46) {
- return 1;
- } else if (current > 1019.3) {
- return 2;
- } else if (current > 1012.53) {
- return 3;
- } else if (current > 1005.76) {
- return 4;
- } else if (current > 999) {
- return 5;
- } else if (current > 988.8) {
- return 6;
- } else if (current > 975.28) {
- return 7;
- }
- return 8;
- }
-
- private static int sagerPressureTrend(double current, double historic) {
- double evol = current - historic;
-
- if (evol > 1.4) {
- return 1; // Rising Rapidly
- } else if (evol > 0.68) {
- return 2; // Rising Slowly
- } else if (evol > -0.68) {
- return 3; // Normal
- } else if (evol > -1.4) {
- return 4; // Decreasing Slowly
- }
- return 5; // Decreasing Rapidly
- }
-
- 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((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);
- }
- return "-";
- }
-
- public String getWindVelocity() {
- if (prevision.isPresent()) {
- char windVelocity = prevision.get().zWindVelocity;
- return Character.toString(windVelocity);
- }
- return "-";
- }
-
- public String getWindDirection() {
- if (prevision.isPresent()) {
- int direction = prevision.get().zWindDirection;
- return String.valueOf(direction);
- }
- return "-";
- }
-
- public String getWindDirection2() {
- if (prevision.isPresent()) {
- int direction = prevision.get().zWindDirection2;
- return String.valueOf(direction);
- }
- 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;
- }
- }
-}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.caster;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This class holds the result of the SagerCaster algorithm
+ *
+ * @author Gaël L'hopital - Initial contribution
+ */
+@NonNullByDefault
+public class SagerPrediction {
+ private final String prediction;
+
+ public SagerPrediction(String sagerCode) {
+ this.prediction = sagerCode;
+ }
+
+ public String getSagerCode() {
+ return prediction;
+ }
+
+ public String getForecast() {
+ return Character.toString(prediction.charAt(0));
+ }
+
+ public String getWindVelocity() {
+ return Character.toString(prediction.charAt(1));
+ }
+
+ public String getWindDirection() {
+ return Character.toString(prediction.charAt(2));
+ }
+
+ public String getWindDirection2() {
+ return prediction.length() > 3 ? Character.toString(prediction.charAt(3)) : SagerWeatherCaster.UNDEF;
+ }
+}
--- /dev/null
+/**
+ * Copyright (c) 2010-2021 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.caster;
+
+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 {
+ public final static String UNDEF = "-";
+ // 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 final Properties forecaster = new Properties();
+
+ private Optional<SagerPrediction> prevision = Optional.empty();
+ private String[] usedDirections = NTZDIRECTIONS; // Defaulted to Northern Zone
+
+ 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() {
+ try (InputStream input = Thread.currentThread().getContextClassLoader()
+ .getResourceAsStream("/sagerForecaster.properties")) {
+ 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 != windEvolution) || (newBearing != currentBearing)) {
+ currentBearing = newBearing;
+ windEvolution = windEvol;
+ updatePrediction();
+ }
+ }
+
+ public void setPressure(double newPressure, double oldPressure) {
+ int newSagerPressure = sagerPressureLevel(newPressure);
+ int pressEvol = sagerPressureTrend(newPressure, oldPressure);
+ if ((pressEvol != pressureEvolution) || (newSagerPressure != sagerPressure)) {
+ sagerPressure = newSagerPressure;
+ pressureEvolution = pressEvol;
+ updatePrediction();
+ }
+ }
+
+ public void setCloudLevel(int cloudiness) {
+ cloudLevel = cloudiness;
+ sagerNubesUpdate();
+ }
+
+ public void setRaining(boolean isRaining) {
+ raining = isRaining;
+ sagerNubesUpdate();
+ }
+
+ public void setBeaufort(int beaufortIndex) {
+ if (currentBeaufort != beaufortIndex) {
+ currentBeaufort = beaufortIndex;
+ updatePrediction();
+ }
+ }
+
+ public int getBeaufort() {
+ return currentBeaufort;
+ }
+
+ public int getWindEvolution() {
+ return windEvolution;
+ }
+
+ public int getPressureEvolution() {
+ return 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) {
+ nubes = result;
+ updatePrediction();
+ }
+ }
+
+ private static int sagerPressureLevel(double current) {
+ if (current > 1029.46) {
+ return 1;
+ } else if (current > 1019.3) {
+ return 2;
+ } else if (current > 1012.53) {
+ return 3;
+ } else if (current > 1005.76) {
+ return 4;
+ } else if (current > 999) {
+ return 5;
+ } else if (current > 988.8) {
+ return 6;
+ } else if (current > 975.28) {
+ return 7;
+ }
+ return 8;
+ }
+
+ private static int sagerPressureTrend(double current, double historic) {
+ double evol = current - historic;
+
+ if (evol > 1.4) {
+ return 1; // Rising Rapidly
+ } else if (evol > 0.68) {
+ return 2; // Rising Slowly
+ } else if (evol > -0.68) {
+ return 3; // Normal
+ } else if (evol > -1.4) {
+ return 4; // Decreasing Slowly
+ }
+ return 5; // Decreasing Rapidly
+ }
+
+ 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((currentBearing + (step / 2.0)) / step);
+ return NTZDIRECTIONS[(int) (b % NTZDIRECTIONS.length)];
+ }
+
+ private void updatePrediction() {
+ int zWind = Arrays.asList(usedDirections).indexOf(getCompass());
+ String d1 = UNDEF;
+ 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 = Optional.ofNullable(forecast != null ? new SagerPrediction(forecast) : null);
+ }
+
+ public String getForecast() {
+ return prevision.map(p -> p.getForecast()).orElse(UNDEF);
+ }
+
+ public String getWindVelocity() {
+ return prevision.map(p -> p.getWindVelocity()).orElse(UNDEF);
+ }
+
+ public String getWindDirection() {
+ return prevision.map(p -> p.getWindDirection()).orElse(UNDEF);
+ }
+
+ public String getWindDirection2() {
+ return prevision.map(p -> p.getWindDirection2()).orElse(UNDEF);
+ }
+
+ public String getSagerCode() {
+ return prevision.map(p -> p.getSagerCode()).orElse(UNDEF);
+ }
+
+ 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;
+ }
+ }
+
+ public int getPredictedBeaufort() {
+ int result = currentBeaufort;
+ switch (getWindVelocity()) {
+ case "N":
+ result += 1;
+ break;
+ case "F":
+ result = 4;
+ break;
+ case "S":
+ result = 6;
+ break;
+ case "G":
+ result = 8;
+ break;
+ case "W":
+ result = 10;
+ break;
+ case "H":
+ result = 12;
+ break;
+ case "D":
+ result -= 1;
+ break;
+ }
+ return result;
+ }
+}
import static org.openhab.core.library.unit.MetricPrefix.HECTO;
import java.math.BigDecimal;
+import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
-import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.measure.quantity.Angle;
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.binding.sagercaster.internal.caster.SagerWeatherCaster;
+import org.openhab.core.library.types.DateTimeType;
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.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
private final WindDirectionStateDescriptionProvider stateDescriptionProvider;
- private final ExpiringMap<QuantityType<Pressure>> pressureCache = new ExpiringMap<>();
- private final ExpiringMap<QuantityType<Temperature>> temperatureCache = new ExpiringMap<>();
- private final ExpiringMap<QuantityType<Angle>> bearingCache = new ExpiringMap<>();
+ private final ExpiringMap<Double> pressureCache = new ExpiringMap<>();
+ private final ExpiringMap<Double> temperatureCache = new ExpiringMap<>();
+ private final ExpiringMap<Integer> bearingCache = new ExpiringMap<>();
- private int currentTemp = 0;
+ private double currentTemp = 0;
+ private String currentSagerCode = SagerWeatherCaster.UNDEF;
public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider stateDescriptionProvider,
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.HOURS.toMillis(observationPeriod);
pressureCache.setObservationPeriod(period);
bearingCache.setObservationPeriod(period);
temperatureCache.setObservationPeriod(period);
+
+ String location = (String) getConfig().get(CONFIG_LOCATION);
+ String latitude = location.split(",")[0];
+ sagerWeatherCaster.setLatitude(Double.parseDouble(latitude));
defineWindDirectionStateDescriptions();
+
updateStatus(ThingStatus.ONLINE);
}
}
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);
+ ThingUID thingUID = getThing().getUID();
+ stateDescriptionProvider.setStateOptions(new ChannelUID(thingUID, GROUP_OUTPUT, CHANNEL_WINDFROM), options);
+ stateDescriptionProvider.setStateOptions(new ChannelUID(thingUID, GROUP_OUTPUT, CHANNEL_WINDTO), options);
}
@Override
String id = channelUID.getIdWithoutGroup();
switch (id) {
case CHANNEL_CLOUDINESS:
- logger.debug("Octa cloud level changed, updating forecast");
+ logger.debug("Cloud level changed, updating forecast");
if (command instanceof QuantityType) {
QuantityType<?> cloudiness = (QuantityType<?>) command;
scheduler.submit(() -> {
postNewForecast();
});
} else {
- logger.debug("Channel '{}' can only accept Switch type commands.", channelUID);
+ logger.debug("Channel '{}' accepts Switch 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();
- });
+ updateRain((QuantityType<?>) command);
} else if (command instanceof DecimalType) {
- DecimalType newQtty = (DecimalType) command;
- scheduler.submit(() -> {
- sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
- postNewForecast();
- });
+ updateRain((DecimalType) command);
} else {
- logger.debug("Channel '{}' can accept Number, Number:Speed, Number:Length type commands.",
- channelUID);
+ logger.debug("Channel '{}' accepts Number, Number:(Speed|Length) commands.", channelUID);
}
break;
case CHANNEL_WIND_SPEED:
logger.debug("Sea-level pressure updated, updating forecast");
if (command instanceof QuantityType) {
@SuppressWarnings("unchecked")
- QuantityType<Pressure> newPressure = ((QuantityType<Pressure>) command)
+ QuantityType<Pressure> pressQtty = ((QuantityType<Pressure>) command)
.toUnit(HECTO(SIUnits.PASCAL));
- if (newPressure != null) {
- pressureCache.put(newPressure);
- pressureCache.getAgedValue().ifPresentOrElse(pressure -> scheduler.submit(() -> {
- sagerWeatherCaster.setPressure(newPressure.doubleValue(), pressure.doubleValue());
+ if (pressQtty != null) {
+ double newPressureValue = pressQtty.doubleValue();
+ pressureCache.put(newPressureValue);
+ pressureCache.getAgedValue().ifPresentOrElse(oldPressure -> scheduler.submit(() -> {
+ sagerWeatherCaster.setPressure(newPressureValue, oldPressure);
updateChannelString(GROUP_OUTPUT, CHANNEL_PRESSURETREND,
String.valueOf(sagerWeatherCaster.getPressureEvolution()));
postNewForecast();
logger.debug("Temperature updated");
if (command instanceof QuantityType) {
@SuppressWarnings("unchecked")
- QuantityType<Temperature> newTemperature = ((QuantityType<Temperature>) command)
+ QuantityType<Temperature> tempQtty = ((QuantityType<Temperature>) command)
.toUnit(SIUnits.CELSIUS);
- if (newTemperature != null) {
- temperatureCache.put(newTemperature);
- currentTemp = newTemperature.intValue();
- Optional<QuantityType<Temperature>> agedTemperature = temperatureCache.getAgedValue();
- agedTemperature.ifPresent(temperature -> {
- double delta = newTemperature.doubleValue() - temperature.doubleValue();
+ if (tempQtty != null) {
+ currentTemp = tempQtty.doubleValue();
+ temperatureCache.put(currentTemp);
+ temperatureCache.getAgedValue().ifPresent(oldTemperature -> {
+ double delta = currentTemp - oldTemperature;
String trend = (delta > 3) ? "1"
: (delta > 0.3) ? "2" : (delta > -0.3) ? "3" : (delta > -3) ? "4" : "5";
updateChannelString(GROUP_OUTPUT, CHANNEL_TEMPERATURETREND, trend);
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();
- agedAngle.ifPresent(angle -> {
+ QuantityType<Angle> angleQtty = (QuantityType<Angle>) command;
+ int newAngleValue = angleQtty.intValue();
+ bearingCache.put(newAngleValue);
+ bearingCache.getAgedValue().ifPresent(oldAngle -> {
scheduler.submit(() -> {
- sagerWeatherCaster.setBearing(newAngle.intValue(), angle.intValue());
+ sagerWeatherCaster.setBearing(newAngleValue, oldAngle);
updateChannelString(GROUP_OUTPUT, CHANNEL_WINDEVOLUTION,
String.valueOf(sagerWeatherCaster.getWindEvolution()));
postNewForecast();
}
}
+ private void updateRain(Number newQtty) {
+ scheduler.submit(() -> {
+ sagerWeatherCaster.setRaining(newQtty.doubleValue() > 0);
+ postNewForecast();
+ });
+ }
+
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" : "";
+ String newSagerCode = sagerWeatherCaster.getSagerCode();
+ if (!newSagerCode.equals(currentSagerCode)) {
+ logger.debug("Sager prediction changed to {}", newSagerCode);
+ currentSagerCode = newSagerCode;
+ updateChannelTimeStamp(GROUP_OUTPUT, CHANNEL_TIMESTAMP, ZonedDateTime.now());
+ 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());
+ updateChannelString(GROUP_OUTPUT, CHANNEL_FORECAST, forecast);
+ updateChannelString(GROUP_OUTPUT, CHANNEL_WINDFROM, sagerWeatherCaster.getWindDirection());
+ updateChannelString(GROUP_OUTPUT, CHANNEL_WINDTO, sagerWeatherCaster.getWindDirection2());
+ updateChannelString(GROUP_OUTPUT, CHANNEL_VELOCITY, sagerWeatherCaster.getWindVelocity());
+ updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, sagerWeatherCaster.getPredictedBeaufort());
+ }
+ }
- 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;
+ private void updateChannelTimeStamp(String group, String channelId, ZonedDateTime zonedDateTime) {
+ ChannelUID id = new ChannelUID(getThing().getUID(), group, channelId);
+ if (isLinked(id)) {
+ updateState(id, new DateTimeType(zonedDateTime));
}
- updateChannelDecimal(GROUP_OUTPUT, CHANNEL_VELOCITY_BEAUFORT, predictedBeaufort);
}
private void updateChannelString(String group, String channelId, String value) {
beaufortLabel = Beaufort
beaufortDescription = Wind speed using Beaufort Scale
pressureDescription = Barometric pressure at sea level.
+timestampChannelLabel = Timestamp
+timestampChannelDescription = Timestamp of the last weather forecast update.
# channel options
forecast0 = Not enough historic data to study pressure evolution, wait a bit ...
forecastD = Unsettled
forecastE = Unsettled and warmer
forecastF = Unsettled and cooler
-forecastG = Increasing cloudiness or overcast followed by Precipitation or showers/Flurries
-forecastG1 = Increasing cloudiness or overcast followed by Precipitation or showers
-forecastG2 = Increasing cloudiness or overcast followed by Precipitation or Flurries
-forecastH = Increasing cloudiness or overcast followed by Precipitation or showers and warmer
+forecastG = Increasing cloudiness or overcast followed by precipitation or showers/flurries
+forecastG1 = Increasing cloudiness or overcast followed by precipitation or showers
+forecastG2 = Increasing cloudiness or overcast followed by precipitation or flurries
+forecastH = Increasing cloudiness or overcast followed by precipitation or showers and warmer
forecastJ = Showers
-forecastK = Showers/Flurries and warmer
+forecastK = Showers/flurries and warmer
forecastK1 = Showers and warmer
forecastK2 = Flurries and warmer
-forecastL = Showers/Flurries and cooler
+forecastL = Showers/flurries and cooler
forecastL1 = Showers and cooler
forecastL2 = Flurries and cooler
forecastM = Precipitation
forecastN = Precipitation and warmer
forecastP = Precipitation and turning cooler; then improvement likely in 24 hours
-forecastR = Precipitation or showers/Flurries followed by improvement (within 12 hours)
+forecastR = Precipitation or showers/flurries followed by improvement (within 12 hours)
forecastR1 = Precipitation or showers followed by improvement (within 12 hours)
forecastR2 = Precipitation or flurries followed by improvement (within 12 hours)
-forecastS = Precipitation or showers/Flurries followed by improvement (within 12 hours) and becoming cooler
+forecastS = Precipitation or showers/flurries followed by improvement (within 12 hours) and becoming cooler
forecastS1 = Precipitation or showers followed by improvement (within 12 hours) and becoming cooler
forecastS2 = Precipitation or flurries followed by improvement (within 12 hours) and becoming cooler
-forecastT = Precipitation or showers/Flurries followed by improvement early in period (within 6 hours)
+forecastT = Precipitation or showers/flurries followed by improvement early in period (within 6 hours)
forecastT1 = Precipitation or showers followed by improvement early in period (within 6 hours)
forecastT2 = Precipitation or flurries followed by improvement early in period (within 6 hours)
-forecastU = Precipitation or showers/Flurries by improvement early in period (within 6 hours) and becoming cooler
+forecastU = Precipitation or showers/flurries by improvement early in period (within 6 hours) and becoming cooler
forecastU1 = Precipitation or showers by improvement early in period (within 6 hours) and becoming cooler
forecastU2 = Precipitation or flurries by improvement early in period (within 6 hours) and becoming cooler
-forecastW = Precipitation or showers/Flurries followed by fair early in period (within 6 hours) and becoming cooler
+forecastW = Precipitation or showers/flurries followed by fair early in period (within 6 hours) and becoming cooler
forecastW1 = Precipitation or showers followed by fair early in period (within 6 hours) and becoming cooler
forecastW2 = Precipitation or flurries followed by fair early in period (within 6 hours) and becoming cooler
forecastX = Unsettled followed by fair
beaufortLabel = Beaufort
beaufortDescription = Force du vent mesurée sur l'échelle Beaufort
pressureDescription = Pression barométrique au niveau de la mer.
+timestampChannelLabel = Horodatage
+timestampChannelDescription = Horodatage de la dernière mise à jour de la prévision météo.
# channel options
forecast0 = Patientez encore un peu pour une prédiction
<label>@text/tempTrendLabel</label>
<description>@text/tempTrendDescription</description>
</channel>
+ <channel id="timestamp" typeId="timestamp"/>
</channels>
</channel-group-type>
<item-type>String</item-type>
<label>@text/trendLabel</label>
<description>@text/trendDescription</description>
+ <category>Line</category>
<state readOnly="true" pattern="%s">
<options>
<option value="1">@text/trend1</option>
</state>
</channel-type>
- <channel-type id="timestamp" advanced="true">
+ <channel-type id="timestamp">
<item-type>DateTime</item-type>
- <label>@text/timestampLabel</label>
- <description>@text/timestampDescription</description>
- <category>Observation time</category>
- <state readOnly="true"></state>
+ <label>@text/timestampChannelLabel</label>
+ <description>@text/timestampChannelDescription</description>
+ <category>Time</category>
+ <tags>
+ <tag>Status</tag>
+ <tag>Timestamp</tag>
+ </tags>
+ <state readOnly="true"/>
</channel-type>
<channel-type id="cloudiness">
<item-type>Number:Dimensionless</item-type>
<label>@text/cloudinessLabel</label>
<description>@text/cloudinessDescription</description>
- <category>Clouds</category>
+ <category>Sun_Clouds</category>
<state min="0" max="100" pattern="%d %%"/>
</channel-type>