mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[solarforecast] wait 1 hour after http 429 error (#16819)
* wait 1 hour after 429 error Signed-off-by: Bernd Weymann <bernd.weymann@gmail.com> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
7d9c2f51d1
commit
792836d787
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
|
@ -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
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user