Skip to content

Commit

Permalink
[awattar] Refactor and add test coverage (#17752)
Browse files Browse the repository at this point in the history
* [aWATTar] push test coverage and improve code readability

Signed-off-by: Thomas Leber <[email protected]>
  • Loading branch information
tl-photography authored Jan 15, 2025
1 parent 968cc56 commit baaaf7f
Show file tree
Hide file tree
Showing 14 changed files with 362 additions and 120 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
*/
package org.openhab.binding.awattar.internal;

import java.time.Instant;

import org.eclipse.jdt.annotation.NonNullByDefault;

/**
Expand All @@ -24,7 +26,7 @@ public abstract class AwattarBestPriceResult {
private long start;
private long end;

public AwattarBestPriceResult() {
protected AwattarBestPriceResult() {
}

public long getStart() {
Expand All @@ -47,7 +49,18 @@ public void updateEnd(long end) {
}
}

public abstract boolean isActive();
/**
* Returns true if the best price is active.
*
* @param pointInTime the current time
* @return true if the best price is active, false otherwise
*/
public abstract boolean isActive(Instant pointInTime);

/**
* Returns the hours of the best price.
*
* @return the hours of the best price as a string
*/
public abstract String getHours();
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ public AwattarConsecutiveBestPriceResult(List<AwattarPrice> prices, int length,
}

@Override
public boolean isActive() {
return contains(Instant.now().toEpochMilli());
public boolean isActive(Instant pointInTime) {
return contains(pointInTime.toEpochMilli());
}

public boolean contains(long timestamp) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
public class AwattarNonConsecutiveBestPriceResult extends AwattarBestPriceResult {
private final List<AwattarPrice> members;
private final ZoneId zoneId;
private boolean sorted = true;

public AwattarNonConsecutiveBestPriceResult(List<AwattarPrice> prices, int length, boolean inverted,
ZoneId zoneId) {
Expand All @@ -57,32 +56,24 @@ public AwattarNonConsecutiveBestPriceResult(List<AwattarPrice> prices, int lengt
}

private void addMember(AwattarPrice member) {
sorted = false;
members.add(member);
updateStart(member.timerange().start());
updateEnd(member.timerange().end());
}

@Override
public boolean isActive() {
return members.stream().anyMatch(x -> x.timerange().contains(Instant.now().toEpochMilli()));
public boolean isActive(Instant pointInTime) {
return members.stream().anyMatch(x -> x.timerange().contains(pointInTime.toEpochMilli()));
}

@Override
public String toString() {
return String.format("NonConsecutiveBestpriceResult with %s", members.toString());
}

private void sort() {
if (!sorted) {
members.sort(Comparator.comparingLong(p -> p.timerange().start()));
}
}

@Override
public String getHours() {
boolean second = false;
sort();
StringBuilder res = new StringBuilder();

for (AwattarPrice price : members) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import javax.measure.quantity.Time;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.library.types.QuantityType;
import org.openhab.core.library.unit.Units;

Expand All @@ -32,9 +31,9 @@
@NonNullByDefault
public class AwattarUtil {

public static long getMillisToNextMinute(int mod, TimeZoneProvider timeZoneProvider) {
public static long getMillisToNextMinute(int mod, ZoneId zoneId) {
long now = Instant.now().toEpochMilli();
ZonedDateTime dt = ZonedDateTime.now(timeZoneProvider.getTimeZone()).truncatedTo(ChronoUnit.MINUTES);
ZonedDateTime dt = ZonedDateTime.now(zoneId).truncatedTo(ChronoUnit.MINUTES);
int min = dt.getMinute();
int offset = min % mod;
offset = offset == 0 ? mod : offset;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,8 @@
import static org.eclipse.jetty.http.HttpMethod.GET;
import static org.eclipse.jetty.http.HttpStatus.OK_200;

import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
Expand All @@ -31,6 +30,7 @@
import org.openhab.binding.awattar.internal.AwattarBridgeConfiguration;
import org.openhab.binding.awattar.internal.AwattarPrice;
import org.openhab.binding.awattar.internal.dto.AwattarApiData;
import org.openhab.binding.awattar.internal.dto.AwattarTimeProvider;
import org.openhab.binding.awattar.internal.dto.Datum;
import org.openhab.binding.awattar.internal.handler.TimeRange;
import org.slf4j.Logger;
Expand Down Expand Up @@ -58,7 +58,7 @@ public class AwattarApi {
private double vatFactor;
private double basePrice;

private ZoneId zone;
private AwattarTimeProvider timeProvider;

private Gson gson;

Expand All @@ -79,8 +79,8 @@ public AwattarApiException(String message) {
* @param httpClient the HTTP client to use
* @param zone the time zone to use
*/
public AwattarApi(HttpClient httpClient, ZoneId zone, AwattarBridgeConfiguration config) {
this.zone = zone;
public AwattarApi(HttpClient httpClient, AwattarTimeProvider timeProvider, AwattarBridgeConfiguration config) {
this.timeProvider = timeProvider;
this.httpClient = httpClient;

this.gson = new Gson();
Expand Down Expand Up @@ -112,7 +112,7 @@ public AwattarApi(HttpClient httpClient, ZoneId zone, AwattarBridgeConfiguration
public SortedSet<AwattarPrice> getData() throws AwattarApiException {
try {
// we start one day in the past to cover ranges that already started yesterday
ZonedDateTime zdt = LocalDate.now(zone).atStartOfDay(zone).minusDays(1);
ZonedDateTime zdt = timeProvider.getZonedDateTimeNow().truncatedTo(ChronoUnit.DAYS).minusDays(1);
long start = zdt.toInstant().toEpochMilli();
// Starting from midnight yesterday we add three days so that the range covers
// the whole next day.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2010-2025 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.awattar.internal.dto;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.i18n.TimeZoneProvider;

/**
* The {@link AwattarTimeProvider} provides a time provider for aWATTar
*
* @author Thomas Leber - Initial contribution
*/
@NonNullByDefault
public class AwattarTimeProvider {

private TimeZoneProvider timeZoneProvider;

public AwattarTimeProvider(TimeZoneProvider timeZoneProvider) {
this.timeZoneProvider = timeZoneProvider;
}

/**
* Get the current zone id.
*
* @return the current zone id
*/
public ZoneId getZoneId() {
return timeZoneProvider.getTimeZone();
}

/**
* Get the current instant.
*
* @return the current instant
*/
public Instant getInstantNow() {
return Instant.now();
}

/**
* Get the current zoned date time.
*
* @return the current zoned date time
*/
public ZonedDateTime getZonedDateTimeNow() {
return Instant.now().atZone(getZoneId());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
import org.openhab.binding.awattar.internal.AwattarConsecutiveBestPriceResult;
import org.openhab.binding.awattar.internal.AwattarNonConsecutiveBestPriceResult;
import org.openhab.binding.awattar.internal.AwattarPrice;
import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.binding.awattar.internal.dto.AwattarTimeProvider;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.QuantityType;
Expand Down Expand Up @@ -70,14 +70,13 @@ public class AwattarBestPriceHandler extends BaseThingHandler {
private static final int THING_REFRESH_INTERVAL = 60;

private final Logger logger = LoggerFactory.getLogger(AwattarBestPriceHandler.class);
private final AwattarTimeProvider timeProvider;

private @Nullable ScheduledFuture<?> thingRefresher;

private final TimeZoneProvider timeZoneProvider;

public AwattarBestPriceHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
public AwattarBestPriceHandler(Thing thing, AwattarTimeProvider timeProvider) {
super(thing);
this.timeZoneProvider = timeZoneProvider;
this.timeProvider = timeProvider;
}

@Override
Expand All @@ -97,7 +96,7 @@ public void initialize() {
* here
*/
thingRefresher = scheduler.scheduleAtFixedRate(this::refreshChannels,
getMillisToNextMinute(1, timeZoneProvider), THING_REFRESH_INTERVAL * 1000L,
getMillisToNextMinute(1, timeProvider.getZoneId()), THING_REFRESH_INTERVAL * 1000L,
TimeUnit.MILLISECONDS);
}
}
Expand Down Expand Up @@ -141,7 +140,7 @@ public void refreshChannel(ChannelUID channelUID) {
return;
}

ZoneId zoneId = bridgeHandler.getTimeZone();
ZoneId zoneId = timeProvider.getZoneId();

AwattarBestPriceConfiguration config = getConfigAs(AwattarBestPriceConfiguration.class);
TimeRange timerange = getRange(config.rangeStart, config.rangeDuration, zoneId);
Expand All @@ -163,7 +162,7 @@ public void refreshChannel(ChannelUID channelUID) {
long diff;
switch (channelId) {
case CHANNEL_ACTIVE:
state = OnOffType.from(result.isActive());
state = OnOffType.from(result.isActive(timeProvider.getInstantNow()));
break;
case CHANNEL_START:
state = new DateTimeType(Instant.ofEpochMilli(result.getStart()));
Expand All @@ -172,16 +171,16 @@ public void refreshChannel(ChannelUID channelUID) {
state = new DateTimeType(Instant.ofEpochMilli(result.getEnd()));
break;
case CHANNEL_COUNTDOWN:
diff = result.getStart() - Instant.now().toEpochMilli();
diff = result.getStart() - timeProvider.getInstantNow().toEpochMilli();
if (diff >= 0) {
state = getDuration(diff);
} else {
state = QuantityType.valueOf(0, Units.MINUTE);
}
break;
case CHANNEL_REMAINING:
if (result.isActive()) {
diff = result.getEnd() - Instant.now().toEpochMilli();
if (result.isActive(timeProvider.getInstantNow())) {
diff = result.getEnd() - timeProvider.getInstantNow().toEpochMilli();
state = getDuration(diff);
} else {
state = QuantityType.valueOf(0, Units.MINUTE);
Expand Down Expand Up @@ -216,20 +215,39 @@ private List<AwattarPrice> getPriceRange(AwattarBridgeHandler bridgeHandler, Tim
return result;
}

/**
* Returns the time range for the given start hour and duration.
*
* @param start the start hour (0-23)
* @param duration the duration in hours
* @param zoneId the time zone to use
* @return the range
*/
protected TimeRange getRange(int start, int duration, ZoneId zoneId) {
ZonedDateTime startCal = getCalendarForHour(start, zoneId);
ZonedDateTime endCal = startCal.plusHours(duration);
ZonedDateTime now = ZonedDateTime.now(zoneId);
ZonedDateTime startTime = getStartTime(start, zoneId);
ZonedDateTime endTime = startTime.plusHours(duration);
ZonedDateTime now = timeProvider.getZonedDateTimeNow();
if (now.getHour() < start) {
// we are before the range, so we might be still within the last range
startCal = startCal.minusDays(1);
endCal = endCal.minusDays(1);
startTime = startTime.minusDays(1);
endTime = endTime.minusDays(1);
}
if (endCal.toInstant().toEpochMilli() < Instant.now().toEpochMilli()) {
if (endTime.isBefore(now)) {
// span is in the past, add one day
startCal = startCal.plusDays(1);
endCal = endCal.plusDays(1);
startTime = startTime.plusDays(1);
endTime = endTime.plusDays(1);
}
return new TimeRange(startCal.toInstant().toEpochMilli(), endCal.toInstant().toEpochMilli());
return new TimeRange(startTime.toInstant().toEpochMilli(), endTime.toInstant().toEpochMilli());
}

/**
* Returns the start time for the given hour.
*
* @param start the hour. Must be between 0 and 23.
* @param zoneId the time zone
* @return the start time
*/
protected ZonedDateTime getStartTime(int start, ZoneId zoneId) {
return getCalendarForHour(start, zoneId);
}
}
Loading

0 comments on commit baaaf7f

Please sign in to comment.