]> git.basschouten.com Git - openhab-addons.git/commitdiff
[solarforecast] wait 1 hour after http 429 error (#16819)
authorBernd Weymann <bernd.weymann@gmail.com>
Sun, 9 Jun 2024 08:54:54 +0000 (10:54 +0200)
committerGitHub <noreply@github.com>
Sun, 9 Jun 2024 08:54:54 +0000 (10:54 +0200)
* wait 1 hour after 429 error

Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com>
bundles/org.openhab.binding.solarforecast/src/main/java/org/openhab/binding/solarforecast/internal/SolarForecastHandlerFactory.java
bundles/org.openhab.binding.solarforecast/src/main/java/org/openhab/binding/solarforecast/internal/forecastsolar/handler/ForecastSolarBridgeHandler.java
bundles/org.openhab.binding.solarforecast/src/main/java/org/openhab/binding/solarforecast/internal/forecastsolar/handler/ForecastSolarPlaneHandler.java
bundles/org.openhab.binding.solarforecast/src/main/java/org/openhab/binding/solarforecast/internal/utils/Utils.java
bundles/org.openhab.binding.solarforecast/src/main/resources/OH-INF/i18n/solarforecast.properties
bundles/org.openhab.binding.solarforecast/src/test/java/org/openhab/binding/solarforecast/CallbackMock.java
bundles/org.openhab.binding.solarforecast/src/test/java/org/openhab/binding/solarforecast/ForecastSolarTest.java

index 9597ab2fdb60aa3541b2a342cb37e039a6e16f4d..b1c11916d30195f28f94dbfe0e6bca0ba1472d37 100644 (file)
@@ -23,6 +23,7 @@ import org.openhab.binding.solarforecast.internal.forecastsolar.handler.Forecast
 import org.openhab.binding.solarforecast.internal.forecastsolar.handler.ForecastSolarPlaneHandler;
 import org.openhab.binding.solarforecast.internal.solcast.handler.SolcastBridgeHandler;
 import org.openhab.binding.solarforecast.internal.solcast.handler.SolcastPlaneHandler;
+import org.openhab.binding.solarforecast.internal.utils.Utils;
 import org.openhab.core.i18n.LocationProvider;
 import org.openhab.core.i18n.TimeZoneProvider;
 import org.openhab.core.io.net.http.HttpClientFactory;
@@ -55,6 +56,7 @@ public class SolarForecastHandlerFactory extends BaseThingHandlerFactory {
             final @Reference TimeZoneProvider tzp) {
         timeZoneProvider = tzp;
         httpClient = hcf.getCommonHttpClient();
+        Utils.setTimeZoneProvider(tzp);
         PointType pt = lp.getLocation();
         if (pt != null) {
             location = Optional.of(pt);
index 487d93e86dd8bb985564bdaee6df42117d61cd69..0800444b6c2891652ea28a7e24d2e9c4a9f81733 100644 (file)
@@ -14,8 +14,10 @@ package org.openhab.binding.solarforecast.internal.forecastsolar.handler;
 
 import static org.openhab.binding.solarforecast.internal.SolarForecastBindingConstants.*;
 
+import java.time.Duration;
 import java.time.Instant;
 import java.time.ZonedDateTime;
+import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Iterator;
@@ -55,10 +57,13 @@ import org.openhab.core.types.TimeSeries.Policy;
  */
 @NonNullByDefault
 public class ForecastSolarBridgeHandler extends BaseBridgeHandler implements SolarForecastProvider {
+    private static final int CALM_DOWN_TIME_MINUTES = 61;
+
     private List<ForecastSolarPlaneHandler> planes = new ArrayList<>();
     private Optional<PointType> homeLocation;
     private Optional<ForecastSolarBridgeConfiguration> configuration = Optional.empty();
     private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
+    private Instant calmDownEnd = Instant.MIN;
 
     public ForecastSolarBridgeHandler(Bridge bridge, Optional<PointType> location) {
         super(bridge);
@@ -130,6 +135,13 @@ public class ForecastSolarBridgeHandler extends BaseBridgeHandler implements Sol
         if (planes.isEmpty()) {
             return;
         }
+        if (calmDownEnd.isAfter(Instant.now(Utils.getClock()))) {
+            // wait until calm down time is expired
+            long minutes = Duration.between(Instant.now(Utils.getClock()), calmDownEnd).toMinutes();
+            updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                    "@text/solarforecast.site.status.calmdown [\"" + minutes + "\"]");
+            return;
+        }
         boolean update = true;
         double energySum = 0;
         double powerSum = 0;
@@ -138,7 +150,7 @@ public class ForecastSolarBridgeHandler extends BaseBridgeHandler implements Sol
             try {
                 ForecastSolarPlaneHandler sfph = iterator.next();
                 ForecastSolarObject fo = sfph.fetchData();
-                ZonedDateTime now = ZonedDateTime.now(fo.getZone());
+                ZonedDateTime now = ZonedDateTime.now(Utils.getClock());
                 energySum += fo.getActualEnergyValue(now);
                 powerSum += fo.getActualPowerValue(now);
                 daySum += fo.getDayTotal(now.toLocalDate());
@@ -232,4 +244,8 @@ public class ForecastSolarBridgeHandler extends BaseBridgeHandler implements Sol
         });
         return l;
     }
+
+    public void calmDown() {
+        calmDownEnd = Instant.now(Utils.getClock()).plus(CALM_DOWN_TIME_MINUTES, ChronoUnit.MINUTES);
+    }
 }
index a15f617fdc3c58c8cf030eed5a2d532c6c67ddfa..faecb63c1a72163c9fbef08ed7d824543f6159f6 100644 (file)
@@ -144,11 +144,12 @@ public class ForecastSolarPlaneHandler extends BaseThingHandler implements Solar
                 }
                 try {
                     ContentResponse cr = httpClient.GET(url);
-                    if (cr.getStatus() == 200) {
+                    int responseStatus = cr.getStatus();
+                    if (responseStatus == 200) {
                         try {
                             ForecastSolarObject localForecast = new ForecastSolarObject(thing.getUID().getAsString(),
-                                    cr.getContentAsString(),
-                                    Instant.now().plus(configuration.get().refreshInterval, ChronoUnit.MINUTES));
+                                    cr.getContentAsString(), Instant.now(Utils.getClock())
+                                            .plus(configuration.get().refreshInterval, ChronoUnit.MINUTES));
                             updateStatus(ThingStatus.ONLINE);
                             updateState(CHANNEL_JSON, StringType.valueOf(cr.getContentAsString()));
                             setForecast(localForecast);
@@ -156,6 +157,14 @@ public class ForecastSolarPlaneHandler extends BaseThingHandler implements Solar
                             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
                                     "@text/solarforecast.plane.status.json-status [\"" + fse.getMessage() + "\"]");
                         }
+                    } else if (responseStatus == 429) {
+                        // special handling for 429 response: https://doc.forecast.solar/facing429
+                        // bridge shall "calm down" until at least one hour is expired
+                        if (bridgeHandler.isPresent()) {
+                            bridgeHandler.get().calmDown();
+                        }
+                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+                                "@text/solarforecast.plane.status.http-status [\"" + cr.getStatus() + "\"]");
                     } else {
                         logger.trace("Call {} failed with status {}. Response: {}", url, cr.getStatus(),
                                 cr.getContentAsString());
@@ -179,7 +188,7 @@ public class ForecastSolarPlaneHandler extends BaseThingHandler implements Solar
     }
 
     private void updateChannels(ForecastSolarObject f) {
-        ZonedDateTime now = ZonedDateTime.now(f.getZone());
+        ZonedDateTime now = ZonedDateTime.now(Utils.getClock());
         double energyDay = f.getDayTotal(now.toLocalDate());
         double energyProduced = f.getActualEnergyValue(now);
         updateState(CHANNEL_ENERGY_ACTUAL, Utils.getEnergyState(energyProduced));
index 44844a6db2516d83bcddfe50e4e485dcfc91de89..e7237231b96242f22699aa00ef8b6985015311d3 100644 (file)
@@ -12,7 +12,9 @@
  */
 package org.openhab.binding.solarforecast.internal.utils;
 
+import java.time.Clock;
 import java.time.Instant;
+import java.time.ZoneId;
 import java.util.Iterator;
 import java.util.List;
 import java.util.TreeMap;
@@ -23,6 +25,7 @@ import javax.measure.quantity.Power;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.binding.solarforecast.internal.actions.SolarForecast;
+import org.openhab.core.i18n.TimeZoneProvider;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.unit.Units;
 import org.openhab.core.types.TimeSeries.Entry;
@@ -34,6 +37,32 @@ import org.openhab.core.types.TimeSeries.Entry;
  */
 @NonNullByDefault
 public class Utils {
+    private static TimeZoneProvider timeZoneProvider = new TimeZoneProvider() {
+        @Override
+        public ZoneId getTimeZone() {
+            return ZoneId.systemDefault();
+        }
+    };
+
+    private static Clock clock = Clock.systemDefaultZone();
+
+    /**
+     * Only for unit testing setting a fixed clock with desired date-time
+     *
+     * @param c
+     */
+    public static void setClock(Clock c) {
+        clock = c;
+    }
+
+    public static void setTimeZoneProvider(TimeZoneProvider tzp) {
+        timeZoneProvider = tzp;
+    }
+
+    public static Clock getClock() {
+        return clock.withZone(timeZoneProvider.getTimeZone());
+    }
+
     public static QuantityType<Energy> getEnergyState(double d) {
         if (d < 0) {
             return QuantityType.valueOf(-1, Units.KILOWATT_HOUR);
index a2fe132236b2fc4615a7d369d7b93098d7fd8ae2..36ef6e7ac14b1a362a5b2bfe90dbe5be19833943 100644 (file)
@@ -79,6 +79,7 @@ solarforecast.site.status.api-key-missing = API key is mandatory
 solarforecast.site.status.timezone = Time zone {0} not found
 solarforecast.site.status.location-missing = Location neither configured in openHAB nor configuration
 solarforecast.site.status.exception = Exception during update: {0}
+solarforecast.site.status.calmdown = Too many requests, continue in {0} minutes
 solarforecast.plane.status.bridge-missing = Bridge not set
 solarforecast.plane.status.bridge-handler-not-found = Bridge handler not found
 solarforecast.plane.status.wrong-handler = Wrong handler {0}
index a47b9d9c38b35a5561360d886f59c3e8bff13527..d8ca0a52f8bb4ea40f8e855a41c2e35122599b64 100644 (file)
@@ -12,6 +12,7 @@
  */
 package org.openhab.binding.solarforecast;
 
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -25,6 +26,8 @@ import org.openhab.core.thing.Channel;
 import org.openhab.core.thing.ChannelGroupUID;
 import org.openhab.core.thing.ChannelUID;
 import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.ThingStatusInfo;
 import org.openhab.core.thing.ThingTypeUID;
 import org.openhab.core.thing.ThingUID;
@@ -45,10 +48,27 @@ import org.openhab.core.types.TimeSeries.Policy;
 @NonNullByDefault
 public class CallbackMock implements ThingHandlerCallback {
 
-    Map<String, TimeSeries> seriesMap = new HashMap<String, TimeSeries>();
+    Map<String, TimeSeries> seriesMap = new HashMap<>();
+    Map<String, List<State>> stateMap = new HashMap<>();
+    ThingStatusInfo currentInfo = new ThingStatusInfo(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, null);
 
     @Override
     public void stateUpdated(ChannelUID channelUID, State state) {
+        String key = channelUID.getAsString();
+        List<State> stateList = stateMap.get(key);
+        if (stateList == null) {
+            stateList = new ArrayList<>();
+        }
+        stateList.add(state);
+        stateMap.put(key, stateList);
+    }
+
+    public List<State> getStateList(String cuid) {
+        List<State> stateList = stateMap.get(cuid);
+        if (stateList == null) {
+            stateList = new ArrayList<State>();
+        }
+        return stateList;
     }
 
     @Override
@@ -70,6 +90,11 @@ public class CallbackMock implements ThingHandlerCallback {
 
     @Override
     public void statusUpdated(Thing thing, ThingStatusInfo thingStatus) {
+        currentInfo = thingStatus;
+    }
+
+    public ThingStatusInfo getStatus() {
+        return currentInfo;
     }
 
     @Override
index 608bc02ebec7f0cd1881d50e7e2239f9b1bc3340..e81c7ca1eef711bb349c862df3c347c6176a6f12 100644 (file)
@@ -14,6 +14,7 @@ package org.openhab.binding.solarforecast;
 
 import static org.junit.jupiter.api.Assertions.*;
 
+import java.time.Clock;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
@@ -41,7 +42,11 @@ import org.openhab.binding.solarforecast.internal.utils.Utils;
 import org.openhab.core.library.types.PointType;
 import org.openhab.core.library.types.QuantityType;
 import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.internal.BridgeImpl;
+import org.openhab.core.types.RefreshType;
 import org.openhab.core.types.State;
 import org.openhab.core.types.TimeSeries;
 
@@ -358,6 +363,10 @@ class ForecastSolarTest {
 
     @Test
     void testPowerTimeSeries() {
+        // Instant matching the date of test resources
+        String fixedInstant = "2022-07-17T15:00:00Z";
+        Clock fixedClock = Clock.fixed(Instant.parse(fixedInstant), TEST_ZONE);
+        Utils.setClock(fixedClock);
         ForecastSolarBridgeHandler fsbh = new ForecastSolarBridgeHandler(
                 new BridgeImpl(SolarForecastBindingConstants.FORECAST_SOLAR_SITE, "bridge"),
                 Optional.of(PointType.valueOf("1,2")));
@@ -389,11 +398,16 @@ class ForecastSolarTest {
 
     @Test
     void testCommonForecastStartEnd() {
+        // Instant matching the date of test resources
+        String fixedInstant = "2022-07-17T15:00:00Z";
+        Clock fixedClock = Clock.fixed(Instant.parse(fixedInstant), TEST_ZONE);
+        Utils.setClock(fixedClock);
         ForecastSolarBridgeHandler fsbh = new ForecastSolarBridgeHandler(
                 new BridgeImpl(SolarForecastBindingConstants.FORECAST_SOLAR_SITE, "bridge"),
                 Optional.of(PointType.valueOf("1,2")));
         CallbackMock cmSite = new CallbackMock();
         fsbh.setCallback(cmSite);
+
         String contentOne = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
         ForecastSolarObject fso1One = new ForecastSolarObject("fs-test", contentOne,
                 Instant.now().plus(1, ChronoUnit.DAYS));
@@ -433,11 +447,16 @@ class ForecastSolarTest {
 
     @Test
     void testActions() {
+        // Instant matching the date of test resources
+        String fixedInstant = "2022-07-17T15:00:00Z";
+        Clock fixedClock = Clock.fixed(Instant.parse(fixedInstant), TEST_ZONE);
+        Utils.setClock(fixedClock);
         ForecastSolarBridgeHandler fsbh = new ForecastSolarBridgeHandler(
                 new BridgeImpl(SolarForecastBindingConstants.FORECAST_SOLAR_SITE, "bridge"),
                 Optional.of(PointType.valueOf("1,2")));
         CallbackMock cmSite = new CallbackMock();
         fsbh.setCallback(cmSite);
+
         String contentOne = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
         ForecastSolarObject fso1One = new ForecastSolarObject("fs-test", contentOne,
                 Instant.now().plus(1, ChronoUnit.DAYS));
@@ -467,6 +486,10 @@ class ForecastSolarTest {
 
     @Test
     void testEnergyTimeSeries() {
+        // Instant matching the date of test resources
+        String fixedInstant = "2022-07-17T15:00:00Z";
+        Clock fixedClock = Clock.fixed(Instant.parse(fixedInstant), TEST_ZONE);
+        Utils.setClock(fixedClock);
         ForecastSolarBridgeHandler fsbh = new ForecastSolarBridgeHandler(
                 new BridgeImpl(SolarForecastBindingConstants.FORECAST_SOLAR_SITE, "bridge"),
                 Optional.of(PointType.valueOf("1,2")));
@@ -495,4 +518,50 @@ class ForecastSolarTest {
                     0.1, "Power Value");
         }
     }
+
+    @Test
+    void testCalmDown() {
+        // Instant matching the date of test resources
+        String fixedInstant = "2022-07-17T15:00:00Z";
+        Clock fixedClock = Clock.fixed(Instant.parse(fixedInstant), TEST_ZONE);
+        Utils.setClock(fixedClock);
+        ForecastSolarBridgeHandler fsbh = new ForecastSolarBridgeHandler(
+                new BridgeImpl(SolarForecastBindingConstants.FORECAST_SOLAR_SITE, "bridge"),
+                Optional.of(PointType.valueOf("1,2")));
+        CallbackMock cm = new CallbackMock();
+        fsbh.setCallback(cm);
+
+        String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
+        ForecastSolarObject fso1 = new ForecastSolarObject("fs-test", content, Instant.now().plus(1, ChronoUnit.DAYS));
+        ForecastSolarPlaneHandler fsph1 = new ForecastSolarPlaneMock(fso1);
+        fsbh.addPlane(fsph1);
+        // first update after add plane - 1 state shall be received
+        assertEquals(1, cm.getStateList("solarforecast:fs-site:bridge:power-actual").size(), "First update");
+        assertEquals(ThingStatus.ONLINE, cm.getStatus().getStatus(), "Online");
+        fsbh.handleCommand(
+                new ChannelUID("solarforecast:fs-site:bridge:" + SolarForecastBindingConstants.CHANNEL_ENERGY_ACTUAL),
+                RefreshType.REFRESH);
+        // second update after refresh request - 2 states shall be received
+        assertEquals(2, cm.getStateList("solarforecast:fs-site:bridge:power-actual").size(), "Second update");
+        assertEquals(ThingStatus.ONLINE, cm.getStatus().getStatus(), "Online");
+
+        fsbh.calmDown();
+        fsbh.handleCommand(
+                new ChannelUID("solarforecast:fs-site:bridge:" + SolarForecastBindingConstants.CHANNEL_ENERGY_ACTUAL),
+                RefreshType.REFRESH);
+        // after calm down refresh shall have no effect . still 2 states
+        assertEquals(2, cm.getStateList("solarforecast:fs-site:bridge:power-actual").size(), "Calm update");
+        assertEquals(ThingStatus.OFFLINE, cm.getStatus().getStatus(), "Offline");
+        assertEquals(ThingStatusDetail.COMMUNICATION_ERROR, cm.getStatus().getStatusDetail(), "Offline");
+
+        // forward Clock to get ONLINE again
+        fixedInstant = "2022-07-17T16:15:00Z";
+        fixedClock = Clock.fixed(Instant.parse(fixedInstant), ZoneId.of("UTC"));
+        Utils.setClock(fixedClock);
+        fsbh.handleCommand(
+                new ChannelUID("solarforecast:fs-site:bridge:" + SolarForecastBindingConstants.CHANNEL_ENERGY_ACTUAL),
+                RefreshType.REFRESH);
+        assertEquals(3, cm.getStateList("solarforecast:fs-site:bridge:power-actual").size(), "Second update");
+        assertEquals(ThingStatus.ONLINE, cm.getStatus().getStatus(), "Online");
+    }
 }