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