2 * Copyright (c) 2010-2024 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.solarforecast;
15 import static org.junit.jupiter.api.Assertions.*;
17 import java.time.Instant;
18 import java.time.LocalDate;
19 import java.time.LocalDateTime;
20 import java.time.ZoneId;
21 import java.time.ZonedDateTime;
22 import java.time.format.DateTimeFormatter;
23 import java.time.temporal.ChronoUnit;
24 import java.util.Iterator;
25 import java.util.Optional;
27 import javax.measure.quantity.Energy;
28 import javax.measure.quantity.Power;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.junit.jupiter.api.Test;
32 import org.openhab.binding.solarforecast.internal.SolarForecastBindingConstants;
33 import org.openhab.binding.solarforecast.internal.SolarForecastException;
34 import org.openhab.binding.solarforecast.internal.actions.SolarForecastActions;
35 import org.openhab.binding.solarforecast.internal.forecastsolar.ForecastSolarObject;
36 import org.openhab.binding.solarforecast.internal.forecastsolar.handler.ForecastSolarBridgeHandler;
37 import org.openhab.binding.solarforecast.internal.forecastsolar.handler.ForecastSolarPlaneHandler;
38 import org.openhab.binding.solarforecast.internal.forecastsolar.handler.ForecastSolarPlaneMock;
39 import org.openhab.binding.solarforecast.internal.solcast.SolcastObject.QueryMode;
40 import org.openhab.binding.solarforecast.internal.utils.Utils;
41 import org.openhab.core.library.types.PointType;
42 import org.openhab.core.library.types.QuantityType;
43 import org.openhab.core.library.unit.Units;
44 import org.openhab.core.thing.internal.BridgeImpl;
45 import org.openhab.core.types.State;
46 import org.openhab.core.types.TimeSeries;
49 * The {@link ForecastSolarTest} tests responses from forecast solar object
51 * @author Bernd Weymann - Initial contribution
54 class ForecastSolarTest {
55 private static final double TOLERANCE = 0.001;
56 public static final ZoneId TEST_ZONE = ZoneId.of("Europe/Berlin");
57 public static final QuantityType<Power> POWER_UNDEF = Utils.getPowerState(-1);
58 public static final QuantityType<Energy> ENERGY_UNDEF = Utils.getEnergyState(-1);
60 public static final String TOO_EARLY_INDICATOR = "too early";
61 public static final String TOO_LATE_INDICATOR = "too late";
62 public static final String INVALID_RANGE_INDICATOR = "invalid time range";
63 public static final String NO_GORECAST_INDICATOR = "No forecast data";
64 public static final String DAY_MISSING_INDICATOR = "not available in forecast";
67 void testForecastObject() {
68 String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
69 ZonedDateTime queryDateTime = LocalDateTime.of(2022, 7, 17, 17, 00).atZone(TEST_ZONE);
70 ForecastSolarObject fo = new ForecastSolarObject("fs-test", content,
71 queryDateTime.toInstant().plus(1, ChronoUnit.DAYS));
72 // "2022-07-17 21:32:00": 63583,
73 assertEquals(63.583, fo.getDayTotal(queryDateTime.toLocalDate()), TOLERANCE, "Total production");
74 // "2022-07-17 17:00:00": 52896,
75 assertEquals(52.896, fo.getActualEnergyValue(queryDateTime), TOLERANCE, "Current Production");
76 // 63583 - 52896 = 10687
77 assertEquals(10.687, fo.getRemainingProduction(queryDateTime), TOLERANCE, "Current Production");
79 assertEquals(fo.getDayTotal(queryDateTime.toLocalDate()),
80 fo.getActualEnergyValue(queryDateTime) + fo.getRemainingProduction(queryDateTime), TOLERANCE,
81 "actual + remain = total");
83 queryDateTime = LocalDateTime.of(2022, 7, 18, 19, 00).atZone(TEST_ZONE);
84 // "2022-07-18 19:00:00": 63067,
85 assertEquals(63.067, fo.getActualEnergyValue(queryDateTime), TOLERANCE, "Actual production");
86 // "2022-07-18 21:31:00": 65554
87 assertEquals(65.554, fo.getDayTotal(queryDateTime.toLocalDate()), TOLERANCE, "Total production");
91 void testActualPower() {
92 String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
93 ZonedDateTime queryDateTime = LocalDateTime.of(2022, 7, 17, 10, 00).atZone(TEST_ZONE);
94 ForecastSolarObject fo = new ForecastSolarObject("fs-test", content,
95 queryDateTime.toInstant().plus(1, ChronoUnit.DAYS));
96 // "2022-07-17 10:00:00": 4874,
97 assertEquals(4.874, fo.getActualPowerValue(queryDateTime), TOLERANCE, "Actual estimation");
99 queryDateTime = LocalDateTime.of(2022, 7, 18, 14, 00).atZone(TEST_ZONE);
100 // "2022-07-18 14:00:00": 7054,
101 assertEquals(7.054, fo.getActualPowerValue(queryDateTime), TOLERANCE, "Actual estimation");
105 void testInterpolation() {
106 String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
107 ZonedDateTime queryDateTime = LocalDateTime.of(2022, 7, 17, 16, 0).atZone(TEST_ZONE);
108 ForecastSolarObject fo = new ForecastSolarObject("fs-test", content,
109 queryDateTime.toInstant().plus(1, ChronoUnit.DAYS));
111 // test steady value increase
112 double previousValue = 0;
113 for (int i = 0; i < 60; i++) {
114 queryDateTime = queryDateTime.plusMinutes(1);
115 assertTrue(previousValue < fo.getActualEnergyValue(queryDateTime));
116 previousValue = fo.getActualEnergyValue(queryDateTime);
119 queryDateTime = LocalDateTime.of(2022, 7, 18, 6, 23).atZone(TEST_ZONE);
120 // "2022-07-18 06:00:00": 132,
121 // "2022-07-18 07:00:00": 1188,
122 // 1188 - 132 = 1056 | 1056 * 23 / 60 = 404 | 404 + 131 = 535
123 assertEquals(0.535, fo.getActualEnergyValue(queryDateTime), 0.002, "Actual estimation");
127 void testForecastSum() {
128 String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
129 ZonedDateTime queryDateTime = LocalDateTime.of(2022, 7, 17, 16, 23).atZone(TEST_ZONE);
130 ForecastSolarObject fo = new ForecastSolarObject("fs-test", content,
131 queryDateTime.toInstant().plus(1, ChronoUnit.DAYS));
132 QuantityType<Energy> actual = QuantityType.valueOf(0, Units.KILOWATT_HOUR);
133 QuantityType<Energy> st = Utils.getEnergyState(fo.getActualEnergyValue(queryDateTime));
134 assertTrue(st instanceof QuantityType);
135 actual = actual.add(st);
136 assertEquals(49.431, actual.floatValue(), TOLERANCE, "Current Production");
137 actual = actual.add(st);
138 assertEquals(98.862, actual.floatValue(), TOLERANCE, "Doubled Current Production");
142 void testCornerCases() {
144 ForecastSolarObject fo = new ForecastSolarObject("fs-test");
145 ZonedDateTime query = LocalDateTime.of(2022, 7, 17, 16, 23).atZone(TEST_ZONE);
147 double d = fo.getActualEnergyValue(query);
148 fail("Exception expected instead of " + d);
149 } catch (SolarForecastException sfe) {
150 String message = sfe.getMessage();
151 assertNotNull(message);
152 assertTrue(message.contains(INVALID_RANGE_INDICATOR),
153 "Expected: " + INVALID_RANGE_INDICATOR + " Received: " + sfe.getMessage());
156 double d = fo.getRemainingProduction(query);
157 fail("Exception expected instead of " + d);
158 } catch (SolarForecastException sfe) {
159 String message = sfe.getMessage();
160 assertNotNull(message);
161 assertTrue(message.contains(NO_GORECAST_INDICATOR),
162 "Expected: " + NO_GORECAST_INDICATOR + " Received: " + sfe.getMessage());
165 double d = fo.getDayTotal(query.toLocalDate());
166 fail("Exception expected instead of " + d);
167 } catch (SolarForecastException sfe) {
168 String message = sfe.getMessage();
169 assertNotNull(message);
170 assertTrue(message.contains(NO_GORECAST_INDICATOR),
171 "Expected: " + NO_GORECAST_INDICATOR + " Received: " + sfe.getMessage());
174 double d = fo.getDayTotal(query.plusDays(1).toLocalDate());
175 fail("Exception expected instead of " + d);
176 } catch (SolarForecastException sfe) {
177 String message = sfe.getMessage();
178 assertNotNull(message);
179 assertTrue(message.contains(NO_GORECAST_INDICATOR),
180 "Expected: " + NO_GORECAST_INDICATOR + " Received: " + sfe.getMessage());
183 // valid object - query date one day too early
184 String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
185 query = LocalDateTime.of(2022, 7, 16, 23, 59).atZone(TEST_ZONE);
186 fo = new ForecastSolarObject("fs-test", content, query.toInstant());
188 double d = fo.getActualEnergyValue(query);
189 fail("Exception expected instead of " + d);
190 } catch (SolarForecastException sfe) {
191 String message = sfe.getMessage();
192 assertNotNull(message);
193 assertTrue(message.contains(TOO_EARLY_INDICATOR),
194 "Expected: " + TOO_EARLY_INDICATOR + " Received: " + sfe.getMessage());
197 double d = fo.getRemainingProduction(query);
198 fail("Exception expected instead of " + d);
199 } catch (SolarForecastException sfe) {
200 String message = sfe.getMessage();
201 assertNotNull(message);
202 assertTrue(message.contains(DAY_MISSING_INDICATOR),
203 "Expected: " + DAY_MISSING_INDICATOR + " Received: " + sfe.getMessage());
206 double d = fo.getActualPowerValue(query);
207 fail("Exception expected instead of " + d);
208 } catch (SolarForecastException sfe) {
209 String message = sfe.getMessage();
210 assertNotNull(message);
211 assertTrue(message.contains(TOO_EARLY_INDICATOR),
212 "Expected: " + TOO_EARLY_INDICATOR + " Received: " + sfe.getMessage());
215 double d = fo.getDayTotal(query.toLocalDate());
216 fail("Exception expected instead of " + d);
217 } catch (SolarForecastException sfe) {
218 String message = sfe.getMessage();
219 assertNotNull(message);
220 assertTrue(message.contains(DAY_MISSING_INDICATOR),
221 "Expected: " + DAY_MISSING_INDICATOR + " Received: " + sfe.getMessage());
224 // one minute later we reach a valid date
225 query = query.plusMinutes(1);
226 assertEquals(63.583, fo.getDayTotal(query.toLocalDate()), TOLERANCE, "Actual out of scope");
227 assertEquals(0.0, fo.getActualEnergyValue(query), TOLERANCE, "Actual out of scope");
228 assertEquals(63.583, fo.getRemainingProduction(query), TOLERANCE, "Remain out of scope");
229 assertEquals(0.0, fo.getActualPowerValue(query), TOLERANCE, "Remain out of scope");
231 // valid object - query date one day too late
232 query = LocalDateTime.of(2022, 7, 19, 0, 0).atZone(TEST_ZONE);
234 double d = fo.getActualEnergyValue(query);
235 fail("Exception expected instead of " + d);
236 } catch (SolarForecastException sfe) {
237 String message = sfe.getMessage();
238 assertNotNull(message);
239 assertTrue(message.contains(TOO_LATE_INDICATOR),
240 "Expected: " + TOO_LATE_INDICATOR + " Received: " + sfe.getMessage());
243 double d = fo.getRemainingProduction(query);
244 fail("Exception expected instead of " + d);
245 } catch (SolarForecastException sfe) {
246 String message = sfe.getMessage();
247 assertNotNull(message);
248 assertTrue(message.contains(DAY_MISSING_INDICATOR),
249 "Expected: " + DAY_MISSING_INDICATOR + " Received: " + sfe.getMessage());
252 double d = fo.getActualPowerValue(query);
253 fail("Exception expected instead of " + d);
254 } catch (SolarForecastException sfe) {
255 String message = sfe.getMessage();
256 assertNotNull(message);
257 assertTrue(message.contains(TOO_LATE_INDICATOR),
258 "Expected: " + TOO_LATE_INDICATOR + " Received: " + sfe.getMessage());
261 double d = fo.getDayTotal(query.toLocalDate());
262 fail("Exception expected instead of " + d);
263 } catch (SolarForecastException sfe) {
264 String message = sfe.getMessage();
265 assertNotNull(message);
266 assertTrue(message.contains(DAY_MISSING_INDICATOR),
267 "Expected: " + DAY_MISSING_INDICATOR + " Received: " + sfe.getMessage());
270 // one minute earlier we reach a valid date
271 query = query.minusMinutes(1);
272 assertEquals(65.554, fo.getDayTotal(query.toLocalDate()), TOLERANCE, "Actual out of scope");
273 assertEquals(65.554, fo.getActualEnergyValue(query), TOLERANCE, "Actual out of scope");
274 assertEquals(0.0, fo.getRemainingProduction(query), TOLERANCE, "Remain out of scope");
275 assertEquals(0.0, fo.getActualPowerValue(query), TOLERANCE, "Remain out of scope");
277 // test times between 2 dates
278 query = LocalDateTime.of(2022, 7, 17, 23, 59).atZone(TEST_ZONE);
279 assertEquals(63.583, fo.getDayTotal(query.toLocalDate()), TOLERANCE, "Actual out of scope");
280 assertEquals(63.583, fo.getActualEnergyValue(query), TOLERANCE, "Actual out of scope");
281 assertEquals(0.0, fo.getRemainingProduction(query), TOLERANCE, "Remain out of scope");
282 assertEquals(0.0, fo.getActualPowerValue(query), TOLERANCE, "Remain out of scope");
283 query = query.plusMinutes(1);
284 assertEquals(65.554, fo.getDayTotal(query.toLocalDate()), TOLERANCE, "Actual out of scope");
285 assertEquals(0.0, fo.getActualEnergyValue(query), TOLERANCE, "Actual out of scope");
286 assertEquals(65.554, fo.getRemainingProduction(query), TOLERANCE, "Remain out of scope");
287 assertEquals(0.0, fo.getActualPowerValue(query), TOLERANCE, "Remain out of scope");
291 void testExceptions() {
292 String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
293 ZonedDateTime queryDateTime = LocalDateTime.of(2022, 7, 17, 16, 23).atZone(TEST_ZONE);
294 ForecastSolarObject fo = new ForecastSolarObject("fs-test", content, queryDateTime.toInstant());
295 assertEquals("2022-07-17T05:31:00",
296 fo.getForecastBegin().atZone(TEST_ZONE).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME),
298 assertEquals("2022-07-18T21:31:00",
299 fo.getForecastEnd().atZone(TEST_ZONE).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), "Forecast end");
300 assertEquals(QuantityType.valueOf(63.583, Units.KILOWATT_HOUR).toString(),
301 fo.getDay(queryDateTime.toLocalDate()).toFullString(), "Actual out of scope");
303 queryDateTime = LocalDateTime.of(2022, 7, 10, 0, 0).atZone(TEST_ZONE);
304 // "watt_hours_day": {
305 // "2022-07-17": 63583,
306 // "2022-07-18": 65554
309 fo.getEnergy(queryDateTime.toInstant(), queryDateTime.plusDays(2).toInstant());
310 fail("Too early exception missing");
311 } catch (SolarForecastException sfe) {
312 String message = sfe.getMessage();
313 assertNotNull(message);
314 assertTrue(message.contains("not available"), "not available expected: " + sfe.getMessage());
317 fo.getDay(queryDateTime.toLocalDate(), "optimistic");
319 } catch (IllegalArgumentException e) {
320 assertEquals("ForecastSolar doesn't accept arguments", e.getMessage(), "optimistic");
323 fo.getDay(queryDateTime.toLocalDate(), "pessimistic");
325 } catch (IllegalArgumentException e) {
326 assertEquals("ForecastSolar doesn't accept arguments", e.getMessage(), "pessimistic");
329 fo.getDay(queryDateTime.toLocalDate(), "total", "rubbish");
331 } catch (IllegalArgumentException e) {
332 assertEquals("ForecastSolar doesn't accept arguments", e.getMessage(), "rubbish");
337 void testTimeSeries() {
338 String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
339 ZonedDateTime queryDateTime = LocalDateTime.of(2022, 7, 17, 16, 23).atZone(TEST_ZONE);
340 ForecastSolarObject fo = new ForecastSolarObject("fs-test", content, queryDateTime.toInstant());
342 TimeSeries powerSeries = fo.getPowerTimeSeries(QueryMode.Average);
343 assertEquals(36, powerSeries.size()); // 18 values each day for 2 days
344 powerSeries.getStates().forEachOrdered(entry -> {
345 State s = entry.state();
346 assertTrue(s instanceof QuantityType<?>);
347 assertEquals("kW", ((QuantityType<?>) s).getUnit().toString());
350 TimeSeries energySeries = fo.getEnergyTimeSeries(QueryMode.Average);
351 assertEquals(36, energySeries.size());
352 energySeries.getStates().forEachOrdered(entry -> {
353 State s = entry.state();
354 assertTrue(s instanceof QuantityType<?>);
355 assertEquals("kWh", ((QuantityType<?>) s).getUnit().toString());
360 void testPowerTimeSeries() {
361 ForecastSolarBridgeHandler fsbh = new ForecastSolarBridgeHandler(
362 new BridgeImpl(SolarForecastBindingConstants.FORECAST_SOLAR_SITE, "bridge"),
363 Optional.of(PointType.valueOf("1,2")));
364 CallbackMock cm = new CallbackMock();
365 fsbh.setCallback(cm);
367 String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
368 ForecastSolarObject fso1 = new ForecastSolarObject("fs-test", content, Instant.now().plus(1, ChronoUnit.DAYS));
369 ForecastSolarPlaneHandler fsph1 = new ForecastSolarPlaneMock(fso1);
370 fsbh.addPlane(fsph1);
371 fsbh.forecastUpdate();
372 TimeSeries ts1 = cm.getTimeSeries("solarforecast:fs-site:bridge:power-estimate");
374 ForecastSolarPlaneHandler fsph2 = new ForecastSolarPlaneMock(fso1);
375 fsbh.addPlane(fsph2);
376 fsbh.forecastUpdate();
377 TimeSeries ts2 = cm.getTimeSeries("solarforecast:fs-site:bridge:power-estimate");
378 Iterator<TimeSeries.Entry> iter1 = ts1.getStates().iterator();
379 Iterator<TimeSeries.Entry> iter2 = ts2.getStates().iterator();
380 while (iter1.hasNext()) {
381 TimeSeries.Entry e1 = iter1.next();
382 TimeSeries.Entry e2 = iter2.next();
383 assertEquals("kW", ((QuantityType<?>) e1.state()).getUnit().toString(), "Power Unit");
384 assertEquals("kW", ((QuantityType<?>) e2.state()).getUnit().toString(), "Power Unit");
385 assertEquals(((QuantityType<?>) e1.state()).doubleValue(), ((QuantityType<?>) e2.state()).doubleValue() / 2,
391 void testCommonForecastStartEnd() {
392 ForecastSolarBridgeHandler fsbh = new ForecastSolarBridgeHandler(
393 new BridgeImpl(SolarForecastBindingConstants.FORECAST_SOLAR_SITE, "bridge"),
394 Optional.of(PointType.valueOf("1,2")));
395 CallbackMock cmSite = new CallbackMock();
396 fsbh.setCallback(cmSite);
397 String contentOne = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
398 ForecastSolarObject fso1One = new ForecastSolarObject("fs-test", contentOne,
399 Instant.now().plus(1, ChronoUnit.DAYS));
400 ForecastSolarPlaneHandler fsph1 = new ForecastSolarPlaneMock(fso1One);
401 fsbh.addPlane(fsph1);
402 fsbh.forecastUpdate();
404 String contentTwo = FileReader.readFileInString("src/test/resources/forecastsolar/resultNextDay.json");
405 ForecastSolarObject fso1Two = new ForecastSolarObject("fs-plane", contentTwo,
406 Instant.now().plus(1, ChronoUnit.DAYS));
407 ForecastSolarPlaneHandler fsph2 = new ForecastSolarPlaneMock(fso1Two);
408 CallbackMock cmPlane = new CallbackMock();
409 fsph2.setCallback(cmPlane);
410 ((ForecastSolarPlaneMock) fsph2).updateForecast(fso1Two);
411 fsbh.addPlane(fsph2);
412 fsbh.forecastUpdate();
414 TimeSeries tsPlaneOne = cmPlane.getTimeSeries("test::plane:power-estimate");
415 TimeSeries tsSite = cmSite.getTimeSeries("solarforecast:fs-site:bridge:power-estimate");
416 Iterator<TimeSeries.Entry> planeIter = tsPlaneOne.getStates().iterator();
417 Iterator<TimeSeries.Entry> siteIter = tsSite.getStates().iterator();
418 while (siteIter.hasNext()) {
419 TimeSeries.Entry planeEntry = planeIter.next();
420 TimeSeries.Entry siteEntry = siteIter.next();
421 assertEquals("kW", ((QuantityType<?>) planeEntry.state()).getUnit().toString(), "Power Unit");
422 assertEquals("kW", ((QuantityType<?>) siteEntry.state()).getUnit().toString(), "Power Unit");
423 assertEquals(((QuantityType<?>) planeEntry.state()).doubleValue(),
424 ((QuantityType<?>) siteEntry.state()).doubleValue() / 2, 0.1, "Power Value");
426 // only one day shall be reported which is available in both planes
427 LocalDate ld = LocalDate.of(2022, 7, 18);
428 assertEquals(ld.atStartOfDay(ZoneId.of("UTC")).toInstant(), tsSite.getBegin().truncatedTo(ChronoUnit.DAYS),
430 assertEquals(ld.atStartOfDay(ZoneId.of("UTC")).toInstant(), tsSite.getEnd().truncatedTo(ChronoUnit.DAYS),
436 ForecastSolarBridgeHandler fsbh = new ForecastSolarBridgeHandler(
437 new BridgeImpl(SolarForecastBindingConstants.FORECAST_SOLAR_SITE, "bridge"),
438 Optional.of(PointType.valueOf("1,2")));
439 CallbackMock cmSite = new CallbackMock();
440 fsbh.setCallback(cmSite);
441 String contentOne = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
442 ForecastSolarObject fso1One = new ForecastSolarObject("fs-test", contentOne,
443 Instant.now().plus(1, ChronoUnit.DAYS));
444 ForecastSolarPlaneHandler fsph1 = new ForecastSolarPlaneMock(fso1One);
445 fsbh.addPlane(fsph1);
446 fsbh.forecastUpdate();
448 String contentTwo = FileReader.readFileInString("src/test/resources/forecastsolar/resultNextDay.json");
449 ForecastSolarObject fso1Two = new ForecastSolarObject("fs-plane", contentTwo,
450 Instant.now().plus(1, ChronoUnit.DAYS));
451 ForecastSolarPlaneHandler fsph2 = new ForecastSolarPlaneMock(fso1Two);
452 CallbackMock cmPlane = new CallbackMock();
453 fsph2.setCallback(cmPlane);
454 ((ForecastSolarPlaneMock) fsph2).updateForecast(fso1Two);
455 fsbh.addPlane(fsph2);
456 fsbh.forecastUpdate();
458 SolarForecastActions sfa = new SolarForecastActions();
459 sfa.setThingHandler(fsbh);
460 // only one day shall be reported which is available in both planes
461 LocalDate ld = LocalDate.of(2022, 7, 18);
462 assertEquals(ld.atStartOfDay(ZoneId.of("UTC")).toInstant(), sfa.getForecastBegin().truncatedTo(ChronoUnit.DAYS),
464 assertEquals(ld.atStartOfDay(ZoneId.of("UTC")).toInstant(), sfa.getForecastEnd().truncatedTo(ChronoUnit.DAYS),
469 void testEnergyTimeSeries() {
470 ForecastSolarBridgeHandler fsbh = new ForecastSolarBridgeHandler(
471 new BridgeImpl(SolarForecastBindingConstants.FORECAST_SOLAR_SITE, "bridge"),
472 Optional.of(PointType.valueOf("1,2")));
473 CallbackMock cm = new CallbackMock();
474 fsbh.setCallback(cm);
476 String content = FileReader.readFileInString("src/test/resources/forecastsolar/result.json");
477 ForecastSolarObject fso1 = new ForecastSolarObject("fs-test", content, Instant.now().plus(1, ChronoUnit.DAYS));
478 ForecastSolarPlaneHandler fsph1 = new ForecastSolarPlaneMock(fso1);
479 fsbh.addPlane(fsph1);
480 fsbh.forecastUpdate();
481 TimeSeries ts1 = cm.getTimeSeries("solarforecast:fs-site:bridge:energy-estimate");
483 ForecastSolarPlaneHandler fsph2 = new ForecastSolarPlaneMock(fso1);
484 fsbh.addPlane(fsph2);
485 fsbh.forecastUpdate();
486 TimeSeries ts2 = cm.getTimeSeries("solarforecast:fs-site:bridge:energy-estimate");
487 Iterator<TimeSeries.Entry> iter1 = ts1.getStates().iterator();
488 Iterator<TimeSeries.Entry> iter2 = ts2.getStates().iterator();
489 while (iter1.hasNext()) {
490 TimeSeries.Entry e1 = iter1.next();
491 TimeSeries.Entry e2 = iter2.next();
492 assertEquals("kWh", ((QuantityType<?>) e1.state()).getUnit().toString(), "Power Unit");
493 assertEquals("kWh", ((QuantityType<?>) e2.state()).getUnit().toString(), "Power Unit");
494 assertEquals(((QuantityType<?>) e1.state()).doubleValue(), ((QuantityType<?>) e2.state()).doubleValue() / 2,