]> git.basschouten.com Git - openhab-addons.git/commitdiff
[Sagercaster] Reintroducing timestamp channel (#11665)
authorGaël L'hopital <gael@lhopital.org>
Sat, 4 Dec 2021 15:55:48 +0000 (16:55 +0100)
committerGitHub <noreply@github.com>
Sat, 4 Dec 2021 15:55:48 +0000 (16:55 +0100)
[Sagercaster] Reintroducing timestamp channel

Signed-off-by: clinique <gael@lhopital.org>
bundles/org.openhab.binding.sagercaster/README.md
bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterBindingConstants.java
bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerCasterHandlerFactory.java
bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/SagerWeatherCaster.java [deleted file]
bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerPrediction.java [new file with mode: 0644]
bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/caster/SagerWeatherCaster.java [new file with mode: 0644]
bundles/org.openhab.binding.sagercaster/src/main/java/org/openhab/binding/sagercaster/internal/handler/SagerCasterHandler.java
bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster.properties
bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/i18n/sagercaster_fr.properties
bundles/org.openhab.binding.sagercaster/src/main/resources/OH-INF/thing/thing-types.xml

index 485b76918219b1619d649b66bca8d60b94729f8d..c9eadf8a16753286ea35bb5fb70306c2614a6257 100644 (file)
@@ -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                |
index f4865f6121bf16bbdd0f13cdd18e4165b0c6e2b0..7b1eef6214784c9ba15bc3ffe69bc39803f41b83 100644 (file)
@@ -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";
index aa8b7ff7998fd86bdcc9a6647e3dbab70858e19f..ad9f8c6448366fc33399050284c55de23284caac 100644 (file)
@@ -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 (file)
index 21f05dc..0000000
+++ /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 <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;
-        }
-    }
-}
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 (file)
index 0000000..dae13ba
--- /dev/null
@@ -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 (file)
index 0000000..fbaa8ea
--- /dev/null
@@ -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 <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;
+    }
+}
index 79065e6ac662b36d666c06df7e94494423c182a2..b2e91af137431a086cacf0354c4253ec2907e7aa 100644 (file)
@@ -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<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) {
@@ -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<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();
@@ -182,14 +178,13 @@ public class SagerCasterHandler extends BaseThingHandler {
                     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);
@@ -201,12 +196,12 @@ public class SagerCasterHandler extends BaseThingHandler {
                     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();
@@ -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) {
index a8eb7a0716a10fcb51eff26c660f61db4e6e9aea..36d25ccac2f6431fe1a2a8dc112b77192109cc2d 100644 (file)
@@ -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
index 6f6720285e51c9ac248bd532bb80db154dbf41b7..557ef119aef0b6e77c91793fd90ac6a3031bc437 100644 (file)
@@ -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
index 60b0c1e7926eb26caf3e7536f5f62eba1ed7a5b4..5b372f6988e092b7a94457fd643b2da60eb97e86 100644 (file)
@@ -72,6 +72,7 @@
                                <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>