Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 25 additions & 25 deletions bundles/org.openhab.binding.sagercaster/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ 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 up to 6 hours before producing reliable weather forecasts (wind direction and sea level pressure).

For these reasons, this binding is not a binding in the usual sense.

Expand All @@ -22,37 +22,37 @@ The binding itself does not require any configuration.

### SagerCaster

| Name | Type | Description |
| ------------------ | -------- | -------------------------------------------------------------------- |
| location (*) | Location | Latitude and longitude of the desired weather forecast. |
| observation-period | int | Minimum delay (in hours) before producing forecasts. Defaulted to 6. |
| Name | Type | Description |
| ------------ | -------- | ------------------------------------------------------ |
| location (*) | Location | Latitude and longitude of the desired weather forecast |

(*) Only latitude is used by the algorithm.

## Channels

The binding will use some input channels, that can be configured directly with profiles (sample below).

| Name | Group | Type | Description |
| ------------------- | ------ | -------------------- | --------------------------------------------------------------- |
| is-raining (*) | input | Switch | On if it is raining, else Off. |
| rain-qtty (*) | input | Number | Any value that give indication of a current rain volume |
| or | input | Number:Speed | Any value that give indication of a current rain volume eg mm/h |
| or | input | Number:Length | Any value that give indication of a current rain volume eg mm |
| cloudiness | input | Number:Dimensionless | Cloud cover percentage |
| 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 |
| wind-from | output | String | Expected wind orientation |
| wind-to | output | String | Evolution of the expected wind orientation |
| wind-evolution | output | String | Wind orientation evolution over observation period |
| pressure-trend | output | String | Pressure evolution over observation period |
| temperature-trend | output | String | Temperature evolution over observation period |
| Name | Group | Type | Description |
| ------------------- | ------ | -------------------- | --------------------------------------------------------------------- |
| is-raining (*) | input | Switch | On if it is raining, else Off. |
| rain-qtty (*) | input | Number | Any value that give indication of a current rain volume |
| or | input | Number:Speed | Any value that give indication of a current rain volume eg mm/h |
| or | input | Number:Length | Any value that give indication of a current rain volume eg mm |
| cloudiness | input | Number:Dimensionless | Cloud cover percentage |
| 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 |
| wind-from | output | String | Expected wind orientation |
| wind-to | output | String | Evolution of the expected wind orientation |
| wind-evolution | output | String | Wind orientation evolution over observation period |
| pressure-trend | output | String | Pressure evolution over observation period |
| temperature-trend | output | String | Temperature evolution over observation period |
| reliability | output | Number:Dimensionless | Algorithm efficiency toward buffered data, grows during first 6 hours |

(*) You may use either is-raining, either rain-qtty depending upon the data available in your system.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class SagerCasterBindingConstants {

// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_SAGERCASTER = new ThingTypeUID(BINDING_ID, "sagercaster");

public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAGERCASTER);
// Configuration elements
public static final String CONFIG_LOCATION = "location";
public static final String CONFIG_PERIOD = "observation-period";
Expand All @@ -49,6 +49,7 @@ public class SagerCasterBindingConstants {
public static final String CHANNEL_PRESSURETREND = "pressure-trend";
public static final String CHANNEL_TEMPERATURETREND = "temperature-trend";
public static final String CHANNEL_TIMESTAMP = "timestamp";
public static final String CHANNEL_RELIABILITY = "reliability";

// Input channel ids
public static final String CHANNEL_CLOUDINESS = "cloudiness";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
*/
package org.openhab.binding.sagercaster.internal;

import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.THING_TYPE_SAGERCASTER;

import java.util.Set;
import static org.openhab.binding.sagercaster.internal.SagerCasterBindingConstants.*;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
Expand All @@ -38,13 +36,12 @@
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.sagercaster")
@NonNullByDefault
public class SagerCasterHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_SAGERCASTER);
private final WindDirectionStateDescriptionProvider stateDescriptionProvider;
private final SagerWeatherCaster sagerWeatherCaster;

@Activate
public SagerCasterHandlerFactory(@Reference SagerWeatherCaster sagerWeatherCaster,
@Reference WindDirectionStateDescriptionProvider provider) {
public SagerCasterHandlerFactory(final @Reference SagerWeatherCaster sagerWeatherCaster,
final @Reference WindDirectionStateDescriptionProvider provider) {
this.stateDescriptionProvider = provider;
this.sagerWeatherCaster = sagerWeatherCaster;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,37 +13,29 @@
package org.openhab.binding.sagercaster.internal.caster;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
* This class holds the result of the SagerCaster algorithm
* This record 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 record SagerPrediction(String sagerCode) {

public String getForecast() {
return Character.toString(prediction.charAt(0));
return Character.toString(sagerCode.charAt(0));
}

public String getWindVelocity() {
return Character.toString(prediction.charAt(1));
return Character.toString(sagerCode.charAt(1));
}

public String getWindDirection() {
return Character.toString(prediction.charAt(2));
return Character.toString(sagerCode.charAt(2));
}

public String getWindDirection2() {
return prediction.length() > 3 ? Character.toString(prediction.charAt(3)) : SagerWeatherCaster.UNDEF;
public @Nullable String getWindDirection2() {
return sagerCode.length() > 3 ? Character.toString(sagerCode.charAt(3)) : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
@Component(service = SagerWeatherCaster.class, scope = ServiceScope.SINGLETON)
@NonNullByDefault
public class SagerWeatherCaster {
public static final String UNDEF = "-";
// Northern Polar Zone & Northern Tropical Zone
private static final String[] NPZDIRECTIONS = { "S", "SW", "W", "NW", "N", "NE", "E", "SE" };
// Northern Temperate Zone
Expand All @@ -101,7 +100,7 @@ public class SagerWeatherCaster {
// 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 Integer cloudLevel = -1;
private boolean raining = false;

@Activate
Expand Down Expand Up @@ -169,15 +168,12 @@ public int getPressureEvolution() {
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
}
result = switch (cloudLevel) {
case Integer i when i > 80 -> 4; // overcast
case Integer i when i > 50 -> 3; // mostly overcast
case Integer i when i > 20 -> 2; // partly cloudy
default -> 1; // clear
};
} else {
result = 5; // raining
}
Expand All @@ -187,38 +183,29 @@ private void sagerNubesUpdate() {
}
}

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 sagerPressureLevel(Double current) {
return switch (current) {
case Double c when c > 1029.46 -> 1;
case Double c when c > 1019.3 -> 2;
case Double c when c > 1012.53 -> 3;
case Double c when c > 1005.76 -> 4;
case Double c when c > 999 -> 5;
case Double c when c > 988.8 -> 6;
case Double c when c > 975.28 -> 7;
default -> 8;
};
}

private static int sagerPressureTrend(double current, double historic) {
double evol = current - 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
return switch (evol) {
case Double e when e > 1.4 -> 1; // Rising Rapidly
case Double e when e > 0.68 -> 2; // Rising Slowly
case Double e when e > -0.68 -> 3; // Normal
case Double e when e > -1.4 -> 4; // Decreasing Slowly
default -> 5; // Decreasing Rapidly
};
}

private static int sagerWindTrend(double historic, double position) {
Expand All @@ -233,14 +220,14 @@ private static int sagerWindTrend(double historic, double position) {
}

private String getCompass() {
double step = 360.0 / NTZDIRECTIONS.length;
double step = 360.0 / usedDirections.length;
double b = Math.floor((currentBearing + (step / 2.0)) / step);
return NTZDIRECTIONS[(int) (b % NTZDIRECTIONS.length)];
return usedDirections[(int) (b % usedDirections.length)];
}

private void updatePrediction() {
int zWind = Arrays.asList(usedDirections).indexOf(getCompass());
String d1 = UNDEF;
String d1 = null;
switch (zWind) {
case 0:
if (windEvolution == 3) {
Expand Down Expand Up @@ -319,75 +306,52 @@ private void updatePrediction() {
d1 = "Z";
}
}
String forecast = forecaster.getProperty(d1 + sagerPressure + pressureEvolution + nubes);
String forecast = d1 == null ? null : forecaster.getProperty(d1 + sagerPressure + pressureEvolution + nubes);
prevision = forecast != null ? new SagerPrediction(forecast) : null;
}

public String getForecast() {
return Objects.requireNonNullElse(this.prevision.getForecast(), UNDEF);
public @Nullable String getForecast() {
return prevision instanceof SagerPrediction p ? p.getForecast() : null;
}

public String getWindVelocity() {
SagerPrediction prevision = this.prevision;
return prevision != null ? prevision.getWindVelocity() : UNDEF;
public @Nullable String getWindVelocity() {
return prevision instanceof SagerPrediction p ? p.getWindVelocity() : null;
}

public String getWindDirection() {
SagerPrediction prevision = this.prevision;
return prevision != null ? prevision.getWindDirection() : UNDEF;
public @Nullable String getWindDirection() {
return prevision instanceof SagerPrediction p ? p.getWindDirection() : null;
}

public String getWindDirection2() {
SagerPrediction prevision = this.prevision;
return prevision != null ? prevision.getWindDirection2() : UNDEF;
public @Nullable String getWindDirection2() {
return prevision instanceof SagerPrediction p ? p.getWindDirection2() : null;
}

public String getSagerCode() {
SagerPrediction prevision = this.prevision;
return prevision != null ? prevision.getSagerCode() : UNDEF;
public @Nullable String getSagerCode() {
return prevision instanceof SagerPrediction p ? p.sagerCode() : null;
}

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 void setLatitude(Double latitude) {
usedDirections = switch (latitude) {
case Double d when d >= 66.6 -> NPZDIRECTIONS;
case Double d when d >= 23.5 -> NTZDIRECTIONS;
case Double d when d >= 0 -> NPZDIRECTIONS;
case Double d when d > -23.5 -> SPZDIRECTIONS;
case Double d when d > -66.6 -> STZDIRECTIONS;
default -> 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;
return switch (getWindVelocity()) {
case "N" -> currentBeaufort + 1;
case "F" -> 4;
case "S" -> 6;
case "G" -> 8;
case "W" -> 10;
case "H" -> 12;
case "D" -> currentBeaufort - 1;
case null -> currentBeaufort;
default -> currentBeaufort;
};
}
}
Loading