2 * Copyright (c) 2010-2023 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.awattar.internal.handler;
15 import static org.openhab.binding.awattar.internal.AwattarBindingConstants.BINDING_ID;
16 import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_GROUP_CURRENT;
17 import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_MARKET_GROSS;
18 import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_MARKET_NET;
19 import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_TOTAL_GROSS;
20 import static org.openhab.binding.awattar.internal.AwattarBindingConstants.CHANNEL_TOTAL_NET;
21 import static org.openhab.binding.awattar.internal.AwattarUtil.getCalendarForHour;
22 import static org.openhab.binding.awattar.internal.AwattarUtil.getMillisToNextMinute;
24 import java.math.BigDecimal;
25 import java.math.RoundingMode;
26 import java.time.ZonedDateTime;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.awattar.internal.AwattarPrice;
33 import org.openhab.core.i18n.TimeZoneProvider;
34 import org.openhab.core.library.types.DecimalType;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.Channel;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.binding.BaseThingHandler;
42 import org.openhab.core.thing.type.ChannelKind;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.openhab.core.types.State;
46 import org.openhab.core.types.UnDefType;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
51 * The {@link AwattarPriceHandler} is responsible for handling commands, which are
52 * sent to one of the channels.
54 * @author Wolfgang Klimt - Initial contribution
57 public class AwattarPriceHandler extends BaseThingHandler {
59 private final Logger logger = LoggerFactory.getLogger(AwattarPriceHandler.class);
61 private int thingRefreshInterval = 60;
62 private TimeZoneProvider timeZoneProvider;
63 private @Nullable ScheduledFuture<?> thingRefresher;
65 public AwattarPriceHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
67 this.timeZoneProvider = timeZoneProvider;
71 public void handleCommand(ChannelUID channelUID, Command command) {
72 if (command instanceof RefreshType) {
73 refreshChannel(channelUID);
75 logger.debug("Binding {} only supports refresh command", BINDING_ID);
80 * Initialize the binding and start the refresh job.
81 * The refresh job runs once after initialization and afterwards every hour.
85 public void initialize() {
87 ScheduledFuture<?> localRefresher = thingRefresher;
88 if (localRefresher == null || localRefresher.isCancelled()) {
90 * The scheduler is required to run exactly at minute borders, hence we can't use scheduleWithFixedDelay
93 thingRefresher = scheduler.scheduleAtFixedRate(this::refreshChannels,
94 getMillisToNextMinute(1, timeZoneProvider), thingRefreshInterval * 1000, TimeUnit.MILLISECONDS);
97 updateStatus(ThingStatus.UNKNOWN);
100 public void dispose() {
101 ScheduledFuture<?> localRefresher = thingRefresher;
102 if (localRefresher != null) {
103 localRefresher.cancel(true);
104 thingRefresher = null;
108 public void refreshChannels() {
109 updateStatus(ThingStatus.ONLINE);
110 for (Channel channel : getThing().getChannels()) {
111 ChannelUID channelUID = channel.getUID();
112 if (ChannelKind.STATE.equals(channel.getKind()) && channelUID.isInGroup() && channelUID.getGroupId() != null
113 && isLinked(channelUID)) {
114 refreshChannel(channel.getUID());
119 public void refreshChannel(ChannelUID channelUID) {
120 State state = UnDefType.UNDEF;
121 Bridge bridge = getBridge();
122 if (bridge == null) {
123 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
126 AwattarBridgeHandler bridgeHandler = (AwattarBridgeHandler) bridge.getHandler();
127 if (bridgeHandler == null) {
128 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
131 String group = channelUID.getGroupId();
133 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
134 "@text/error.channelgroup.missing");
138 ZonedDateTime target;
140 if (group.equals(CHANNEL_GROUP_CURRENT)) {
141 target = ZonedDateTime.now(bridgeHandler.getTimeZone());
142 } else if (group.startsWith("today")) {
143 target = getCalendarForHour(Integer.valueOf(group.substring(5)), bridgeHandler.getTimeZone());
144 } else if (group.startsWith("tomorrow")) {
145 target = getCalendarForHour(Integer.valueOf(group.substring(8)), bridgeHandler.getTimeZone()).plusDays(1);
147 logger.warn("Unsupported channel group {}", group);
148 updateState(channelUID, state);
152 AwattarPrice price = bridgeHandler.getPriceFor(target.toInstant().toEpochMilli());
155 logger.trace("No price found for hour {}", target.toString());
156 updateState(channelUID, state);
159 double currentprice = price.getPrice();
161 String channelId = channelUID.getIdWithoutGroup();
163 case CHANNEL_MARKET_NET:
164 state = toDecimalType(currentprice);
166 case CHANNEL_MARKET_GROSS:
167 state = toDecimalType(currentprice * bridgeHandler.getVatFactor());
169 case CHANNEL_TOTAL_NET:
170 state = toDecimalType(currentprice + bridgeHandler.getBasePrice());
172 case CHANNEL_TOTAL_GROSS:
173 state = toDecimalType((currentprice + bridgeHandler.getBasePrice()) * bridgeHandler.getVatFactor());
176 logger.warn("Unknown channel id {} for Thing type {}", channelUID, getThing().getThingTypeUID());
178 updateState(channelUID, state);
181 private DecimalType toDecimalType(Double value) {
182 BigDecimal bd = BigDecimal.valueOf(value);
183 return new DecimalType(bd.setScale(2, RoundingMode.HALF_UP));