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