From: Gaël L'hopital Date: Sat, 4 Dec 2021 15:55:48 +0000 (+0100) Subject: [Sagercaster] Reintroducing timestamp channel (#11665) X-Git-Url: https://git.basschouten.com/?a=commitdiff_plain;h=4605edeb291cd2fcb92c3128a10b21d749956be1;p=openhab-addons.git [Sagercaster] Reintroducing timestamp channel (#11665) [Sagercaster] Reintroducing timestamp channel Signed-off-by: clinique --- diff --git a/bundles/org.openhab.binding.sagercaster/README.md b/bundles/org.openhab.binding.sagercaster/README.md index 485b769182..c9eadf8a16 100644 --- a/bundles/org.openhab.binding.sagercaster/README.md +++ b/bundles/org.openhab.binding.sagercaster/README.md @@ -6,7 +6,8 @@ The Sager Weathercaster is a scientific instrument for accurate prediction of th * 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. @@ -24,9 +25,11 @@ The binding itself does not require any configuration. | 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). @@ -41,6 +44,8 @@ The binding will use some input channels, that can be configured directly with p | 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 | diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java index f4865f6121..7b1eef6214 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java @@ -48,6 +48,8 @@ public class SagerCasterBindingConstants { 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"; diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterHandlerFactory.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterHandlerFactory.java index aa8b7ff799..ad9f8c6448 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterHandlerFactory.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterHandlerFactory.java @@ -19,6 +19,7 @@ import java.util.Set; 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; diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerWeatherCaster.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerWeatherCaster.java deleted file mode 100644 index 21f05dc296..0000000000 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerWeatherCaster.java +++ /dev/null @@ -1,386 +0,0 @@ -/** - * 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 . - * - *** - * 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 = 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; - } - } -} diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java new file mode 100644 index 0000000000..dae13ba4e8 --- /dev/null +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java @@ -0,0 +1,49 @@ +/** + * 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; + } +} diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java new file mode 100644 index 0000000000..fbaa8eaac3 --- /dev/null +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java @@ -0,0 +1,389 @@ +/** + * 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 . + * + *** + * 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 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; + } +} diff --git a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java index 79065e6ac6..b2e91af137 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java +++ b/bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java @@ -16,9 +16,9 @@ import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstan 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; @@ -26,8 +26,9 @@ 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.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; @@ -36,6 +37,7 @@ 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.ThingUID; import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; @@ -58,11 +60,12 @@ public class SagerCasterHandler extends BaseThingHandler { private final WindDirectionStateDescriptionProvider stateDescriptionProvider; - private final ExpiringMap> pressureCache = new ExpiringMap<>(); - private final ExpiringMap> temperatureCache = new ExpiringMap<>(); - private final ExpiringMap> bearingCache = new ExpiringMap<>(); + private final ExpiringMap pressureCache = new ExpiringMap<>(); + private final ExpiringMap temperatureCache = new ExpiringMap<>(); + private final ExpiringMap bearingCache = new ExpiringMap<>(); - private int currentTemp = 0; + private double currentTemp = 0; + private String currentSagerCode = SagerWeatherCaster.UNDEF; public SagerCasterHandler(Thing thing, WindDirectionStateDescriptionProvider stateDescriptionProvider, SagerWeatherCaster sagerWeatherCaster) { @@ -73,15 +76,17 @@ public class SagerCasterHandler extends BaseThingHandler { @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); } @@ -95,10 +100,9 @@ public class SagerCasterHandler extends BaseThingHandler { } 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 @@ -109,7 +113,7 @@ public class SagerCasterHandler extends BaseThingHandler { 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(() -> { @@ -127,26 +131,17 @@ public class SagerCasterHandler extends BaseThingHandler { 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: @@ -165,12 +160,13 @@ public class SagerCasterHandler extends BaseThingHandler { logger.debug("Sea-level pressure updated, updating forecast"); if (command instanceof QuantityType) { @SuppressWarnings("unchecked") - QuantityType newPressure = ((QuantityType) command) + QuantityType pressQtty = ((QuantityType) 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(); @@ -182,14 +178,13 @@ public class SagerCasterHandler extends BaseThingHandler { logger.debug("Temperature updated"); if (command instanceof QuantityType) { @SuppressWarnings("unchecked") - QuantityType newTemperature = ((QuantityType) command) + QuantityType tempQtty = ((QuantityType) command) .toUnit(SIUnits.CELSIUS); - if (newTemperature != null) { - temperatureCache.put(newTemperature); - currentTemp = newTemperature.intValue(); - Optional> 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); @@ -201,12 +196,12 @@ public class SagerCasterHandler extends BaseThingHandler { logger.debug("Updated wind direction, updating forecast"); if (command instanceof QuantityType) { @SuppressWarnings("unchecked") - QuantityType newAngle = (QuantityType) command; - bearingCache.put(newAngle); - Optional> agedAngle = bearingCache.getAgedValue(); - agedAngle.ifPresent(angle -> { + QuantityType angleQtty = (QuantityType) 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(); @@ -220,42 +215,36 @@ public class SagerCasterHandler extends BaseThingHandler { } } + 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) { diff --git a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster.properties b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster.properties index a8eb7a0716..36d25ccac2 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster.properties +++ b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster.properties @@ -43,6 +43,8 @@ rainingDescription = Is it currently raining ? 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 ... @@ -52,33 +54,33 @@ forecastC = Fair and cooler 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 diff --git a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster_fr.properties b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster_fr.properties index 6f6720285e..557ef119ae 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster_fr.properties +++ b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster_fr.properties @@ -43,6 +43,8 @@ rainingDescription = Pleut-il actuellement ? 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 diff --git a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/thing/thing-types.xml index 60b0c1e792..5b372f6988 100644 --- a/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/thing/thing-types.xml @@ -72,6 +72,7 @@ @text/tempTrendDescription + @@ -163,6 +164,7 @@ String @text/trendDescription + Line @@ -174,19 +176,23 @@ - + DateTime - - @text/timestampDescription - Observation time - + + @text/timestampChannelDescription + Time + + Status + Timestamp + + Number:Dimensionless @text/cloudinessDescription - Clouds + Sun_Clouds