2 * Copyright (c) 2010-2022 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.fronius.internal.handler;
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;
38 import com.google.gson.Gson;
39 import com.google.gson.JsonSyntaxException;
42 * Basic Handler class for all Fronius services.
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
49 public abstract class FroniusBaseThingHandler extends BaseThingHandler {
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;
57 public FroniusBaseThingHandler(Thing thing) {
60 serviceDescription = getDescription();
64 public void handleCommand(ChannelUID channelUID, Command command) {
65 if (command instanceof RefreshType) {
66 updateChannel(channelUID.getId());
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);
80 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
87 protected void updateChannels() {
88 for (Channel channel : getThing().getChannels()) {
89 updateChannel(channel.getUID().getId());
94 * Update the channel from the last data
96 * @param channelId the id identifying the channel to be updated
98 protected void updateChannel(String channelId) {
99 if (!isLinked(channelId)) {
103 Object value = getValue(channelId);
105 logger.debug("Value retrieved for channel '{}' was null. Can't update.", channelId);
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);
119 logger.warn("Update channel {}: Unsupported value type {}", channelId, value.getClass().getSimpleName());
121 logger.trace("Update channel {} with state {} ({})", channelId, (state == null) ? "null" : state.toString(),
122 value.getClass().getSimpleName());
124 // Update the channel
126 updateState(channelId, state);
131 * return an internal description for logging
133 * @return the description of the thing
135 protected abstract String getDescription();
138 * get the "new" associated value for a channelId
140 * @param channelId the id identifying the channel
141 * @return the "new" associated value
143 protected abstract Object getValue(String channelId);
146 * Called by the bridge to fetch data and update channels
148 * @param bridgeConfiguration the connected bridge configuration
150 public void refresh(FroniusBridgeConfiguration bridgeConfiguration) {
152 handleRefresh(bridgeConfiguration);
153 if (getThing().getStatus() != ThingStatus.ONLINE) {
154 updateStatus(ThingStatus.ONLINE);
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());
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
166 * @param bridgeConfiguration the connected bridge configuration
168 protected abstract void handleRefresh(FroniusBridgeConfiguration bridgeConfiguration)
169 throws FroniusCommunicationException;
173 * @param type response class type
174 * @param url to request
175 * @return the object representation of the json response
177 protected @NonNull <T extends BaseFroniusResponse> T collectDataFromUrl(Class<T> type, String url)
178 throws FroniusCommunicationException {
182 logger.trace("Fetching URL = {}", url);
183 String response = FroniusHttpUtil.executeUrl(url, API_TIMEOUT);
184 logger.trace("aqiResponse = {}", response);
186 T result = gson.fromJson(response, type);
187 if (result == null) {
188 throw new FroniusCommunicationException("Empty json result");
191 HeadStatus status = result.getHead().getStatus();
192 if (status.getCode() == 0) {
196 // Sometimes Fronius would return a HTTP status 200 with a proper JSON data
197 // with Reason: Transfer timeout.
201 // "Reason" : "Transfer timeout.",
202 // "UserMessage" : ""
204 logger.debug("Error from Fronius attempt #{}: {} - {}", attempts, status.getCode(), status.getReason());
206 throw new FroniusCommunicationException(status.getReason());
208 Thread.sleep(500 * attempts);
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);