]> git.basschouten.com Git - openhab-addons.git/blob
974d45bd1a3613ce5dcbebc7c3987496cd6bfca9
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.fronius.internal.handler;
14
15 import java.math.BigDecimal;
16
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
19 import org.openhab.binding.fronius.internal.FroniusCommunicationException;
20 import org.openhab.binding.fronius.internal.FroniusHttpUtil;
21 import org.openhab.binding.fronius.internal.api.BaseFroniusResponse;
22 import org.openhab.binding.fronius.internal.api.HeadStatus;
23 import org.openhab.binding.fronius.internal.api.ValueUnit;
24 import org.openhab.core.library.types.DecimalType;
25 import org.openhab.core.library.types.QuantityType;
26 import org.openhab.core.library.types.StringType;
27 import org.openhab.core.thing.Bridge;
28 import org.openhab.core.thing.Channel;
29 import org.openhab.core.thing.ChannelUID;
30 import org.openhab.core.thing.Thing;
31 import org.openhab.core.thing.ThingStatus;
32 import org.openhab.core.thing.ThingStatusDetail;
33 import org.openhab.core.thing.binding.BaseThingHandler;
34 import org.openhab.core.types.Command;
35 import org.openhab.core.types.RefreshType;
36 import org.openhab.core.types.State;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39
40 import com.google.gson.Gson;
41 import com.google.gson.JsonSyntaxException;
42
43 /**
44  * Basic Handler class for all Fronius services.
45  *
46  * @author Gerrit Beine - Initial contribution
47  * @author Thomas Rokohl - Refactoring to merge the concepts
48  * @author Thomas Kordelle - Added inverter power, battery state of charge and PV solar yield
49  * @author Jimmy Tanagra - Implement connection retry
50  */
51 public abstract class FroniusBaseThingHandler extends BaseThingHandler {
52
53     private static final int API_TIMEOUT = 5000;
54     private final Logger logger = LoggerFactory.getLogger(FroniusBaseThingHandler.class);
55     private final String serviceDescription;
56     private FroniusBridgeHandler bridgeHandler;
57     private final Gson gson;
58
59     public FroniusBaseThingHandler(Thing thing) {
60         super(thing);
61         gson = new Gson();
62         serviceDescription = getDescription();
63     }
64
65     @Override
66     public void handleCommand(ChannelUID channelUID, Command command) {
67         if (command instanceof RefreshType) {
68             updateChannel(channelUID.getId());
69         }
70     }
71
72     @Override
73     public void initialize() {
74         logger.debug("Initializing {} Service", serviceDescription);
75         // this is important so FroniusBridgeHandler::childHandlerInitialized gets called
76         Bridge bridge = getBridge();
77         if (bridge == null || bridge.getHandler() == null) {
78             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
79         } else if (bridge.getStatus() == ThingStatus.ONLINE) {
80             updateStatus(ThingStatus.UNKNOWN);
81         } else {
82             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
83         }
84     }
85
86     /**
87      * Update all Channels
88      */
89     protected void updateChannels() {
90         for (Channel channel : getThing().getChannels()) {
91             updateChannel(channel.getUID().getId());
92         }
93     }
94
95     /**
96      * Update the channel from the last data
97      *
98      * @param channelId the id identifying the channel to be updated
99      */
100     protected void updateChannel(String channelId) {
101         if (!isLinked(channelId)) {
102             return;
103         }
104
105         Object value = getValue(channelId);
106         if (value == null) {
107             logger.debug("Value retrieved for channel '{}' was null. Can't update.", channelId);
108             return;
109         }
110
111         State state = null;
112         if (value instanceof BigDecimal) {
113             state = new DecimalType((BigDecimal) value);
114         } else if (value instanceof Integer) {
115             state = new DecimalType(BigDecimal.valueOf(((Integer) value).longValue()));
116         } else if (value instanceof Double) {
117             state = new DecimalType((double) value);
118         } else if (value instanceof ValueUnit) {
119             state = new DecimalType(((ValueUnit) value).getValue());
120         } else if (value instanceof String) {
121             state = new StringType((String) value);
122         } else if (value instanceof QuantityType) {
123             state = (QuantityType) value;
124         } else {
125             logger.warn("Update channel {}: Unsupported value type {}", channelId, value.getClass().getSimpleName());
126         }
127         logger.trace("Update channel {} with state {} ({})", channelId, (state == null) ? "null" : state.toString(),
128                 value.getClass().getSimpleName());
129
130         // Update the channel
131         if (state != null) {
132             updateState(channelId, state);
133         }
134     }
135
136     /**
137      * return an internal description for logging
138      *
139      * @return the description of the thing
140      */
141     protected abstract String getDescription();
142
143     /**
144      * get the "new" associated value for a channelId
145      *
146      * @param channelId the id identifying the channel
147      * @return the "new" associated value
148      */
149     protected abstract Object getValue(String channelId);
150
151     /**
152      * Called by the bridge to fetch data and update channels
153      *
154      * @param bridgeConfiguration the connected bridge configuration
155      */
156     public void refresh(FroniusBridgeConfiguration bridgeConfiguration) {
157         try {
158             handleRefresh(bridgeConfiguration);
159             if (getThing().getStatus() != ThingStatus.ONLINE) {
160                 updateStatus(ThingStatus.ONLINE);
161             }
162         } catch (FroniusCommunicationException | RuntimeException e) {
163             logger.debug("Exception caught in refresh() for {}", getThing().getUID().getId(), e);
164             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
165         }
166     }
167
168     /**
169      * This method should be overridden to do whatever a thing must do to update its channels
170      * this function is called from the bridge in a given interval
171      *
172      * @param bridgeConfiguration the connected bridge configuration
173      */
174     protected abstract void handleRefresh(FroniusBridgeConfiguration bridgeConfiguration)
175             throws FroniusCommunicationException;
176
177     /**
178      *
179      * @param type response class type
180      * @param url to request
181      * @return the object representation of the json response
182      */
183     protected @NonNull <T extends BaseFroniusResponse> T collectDataFromUrl(Class<T> type, String url)
184             throws FroniusCommunicationException {
185         try {
186             int attempts = 1;
187             while (true) {
188                 logger.trace("Fetching URL = {}", url);
189                 String response = FroniusHttpUtil.executeUrl(url, API_TIMEOUT);
190                 logger.trace("aqiResponse = {}", response);
191
192                 T result = gson.fromJson(response, type);
193                 if (result == null) {
194                     throw new FroniusCommunicationException("Empty json result");
195                 }
196
197                 HeadStatus status = result.getHead().getStatus();
198                 if (status.getCode() == 0) {
199                     return result;
200                 }
201
202                 // Sometimes Fronius would return a HTTP status 200 with a proper JSON data
203                 // with Reason: Transfer timeout.
204                 //
205                 // "Status" : {
206                 // "Code" : 8,
207                 // "Reason" : "Transfer timeout.",
208                 // "UserMessage" : ""
209                 // },
210                 logger.debug("Error from Fronius attempt #{}: {} - {}", attempts, status.getCode(), status.getReason());
211                 if (attempts >= 3) {
212                     throw new FroniusCommunicationException(status.getReason());
213                 }
214                 Thread.sleep(500 * attempts);
215                 attempts++;
216             }
217
218         } catch (JsonSyntaxException | NumberFormatException e) {
219             logger.debug("Received Invalid JSON Data", e);
220             throw new FroniusCommunicationException("Invalid JSON data received", e);
221         } catch (InterruptedException e) {
222             Thread.currentThread().interrupt();
223             throw new FroniusCommunicationException("Data collection interrupted", e);
224         }
225     }
226 }