]> git.basschouten.com Git - openhab-addons.git/blob
81d22bf28dc0b2e550aa24522690c8be98104a83
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.awattar.internal.handler;
14
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;
23
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;
29
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;
49
50 /**
51  * The {@link AwattarPriceHandler} is responsible for handling commands, which are
52  * sent to one of the channels.
53  *
54  * @author Wolfgang Klimt - Initial contribution
55  */
56 @NonNullByDefault
57 public class AwattarPriceHandler extends BaseThingHandler {
58
59     private final Logger logger = LoggerFactory.getLogger(AwattarPriceHandler.class);
60
61     private int thingRefreshInterval = 60;
62     private TimeZoneProvider timeZoneProvider;
63     private @Nullable ScheduledFuture<?> thingRefresher;
64
65     public AwattarPriceHandler(Thing thing, TimeZoneProvider timeZoneProvider) {
66         super(thing);
67         this.timeZoneProvider = timeZoneProvider;
68     }
69
70     @Override
71     public void handleCommand(ChannelUID channelUID, Command command) {
72         if (command instanceof RefreshType) {
73             refreshChannel(channelUID);
74         } else {
75             logger.debug("Binding {} only supports refresh command", BINDING_ID);
76         }
77     }
78
79     /**
80      * Initialize the binding and start the refresh job.
81      * The refresh job runs once after initialization and afterwards every hour.
82      */
83
84     @Override
85     public void initialize() {
86         synchronized (this) {
87             ScheduledFuture<?> localRefresher = thingRefresher;
88             if (localRefresher == null || localRefresher.isCancelled()) {
89                 /*
90                  * The scheduler is required to run exactly at minute borders, hence we can't use scheduleWithFixedDelay
91                  * here
92                  */
93                 thingRefresher = scheduler.scheduleAtFixedRate(this::refreshChannels,
94                         getMillisToNextMinute(1, timeZoneProvider), thingRefreshInterval * 1000, TimeUnit.MILLISECONDS);
95             }
96         }
97         updateStatus(ThingStatus.UNKNOWN);
98     }
99
100     @Override
101     public void dispose() {
102         ScheduledFuture<?> localRefresher = thingRefresher;
103         if (localRefresher != null) {
104             localRefresher.cancel(true);
105             thingRefresher = null;
106         }
107     }
108
109     public void refreshChannels() {
110         updateStatus(ThingStatus.ONLINE);
111         for (Channel channel : getThing().getChannels()) {
112             ChannelUID channelUID = channel.getUID();
113             if (ChannelKind.STATE.equals(channel.getKind()) && channelUID.isInGroup() && channelUID.getGroupId() != null
114                     && isLinked(channelUID)) {
115                 refreshChannel(channel.getUID());
116             }
117         }
118     }
119
120     public void refreshChannel(ChannelUID channelUID) {
121         State state = UnDefType.UNDEF;
122         Bridge bridge = getBridge();
123         if (bridge == null) {
124             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
125             return;
126         }
127         AwattarBridgeHandler bridgeHandler = (AwattarBridgeHandler) bridge.getHandler();
128         if (bridgeHandler == null) {
129             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.bridge.missing");
130             return;
131         }
132         String group = channelUID.getGroupId();
133         if (group == null) {
134             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
135                     "@text/error.channelgroup.missing");
136             return;
137         }
138
139         ZonedDateTime target;
140
141         if (group.equals(CHANNEL_GROUP_CURRENT)) {
142             target = ZonedDateTime.now(bridgeHandler.getTimeZone());
143         } else if (group.startsWith("today")) {
144             target = getCalendarForHour(Integer.valueOf(group.substring(5)), bridgeHandler.getTimeZone());
145         } else if (group.startsWith("tomorrow")) {
146             target = getCalendarForHour(Integer.valueOf(group.substring(8)), bridgeHandler.getTimeZone()).plusDays(1);
147         } else {
148             logger.warn("Unsupported channel group {}", group);
149             updateState(channelUID, state);
150             return;
151         }
152
153         AwattarPrice price = bridgeHandler.getPriceFor(target.toInstant().toEpochMilli());
154
155         if (price == null) {
156             logger.trace("No price found for hour {}", target.toString());
157             updateState(channelUID, state);
158             return;
159         }
160         double currentprice = price.getPrice();
161
162         String channelId = channelUID.getIdWithoutGroup();
163         switch (channelId) {
164             case CHANNEL_MARKET_NET:
165                 state = toDecimalType(currentprice);
166                 break;
167             case CHANNEL_MARKET_GROSS:
168                 state = toDecimalType(currentprice * bridgeHandler.getVatFactor());
169                 break;
170             case CHANNEL_TOTAL_NET:
171                 state = toDecimalType(currentprice + bridgeHandler.getBasePrice());
172                 break;
173             case CHANNEL_TOTAL_GROSS:
174                 state = toDecimalType((currentprice + bridgeHandler.getBasePrice()) * bridgeHandler.getVatFactor());
175                 break;
176             default:
177                 logger.warn("Unknown channel id {} for Thing type {}", channelUID, getThing().getThingTypeUID());
178         }
179         updateState(channelUID, state);
180     }
181
182     private DecimalType toDecimalType(Double value) {
183         BigDecimal bd = BigDecimal.valueOf(value);
184         return new DecimalType(bd.setScale(2, RoundingMode.HALF_UP));
185     }
186 }