2 * Copyright (c) 2010-2024 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.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;
35 import com.google.gson.Gson;
36 import com.google.gson.JsonSyntaxException;
39 * Basic Handler class for all Fronius services.
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
48 public abstract class FroniusBaseThingHandler extends BaseThingHandler {
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;
56 public FroniusBaseThingHandler(Thing thing) {
59 serviceDescription = getDescription();
63 public void handleCommand(ChannelUID channelUID, Command command) {
64 if (command instanceof RefreshType) {
65 updateChannel(channelUID.getId());
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);
79 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
86 protected void updateChannels() {
87 for (Channel channel : getThing().getChannels()) {
88 updateChannel(channel.getUID().getId());
93 * Update the channel from the last data
95 * @param channelId the id identifying the channel to be updated
97 protected void updateChannel(String channelId) {
98 if (!isLinked(channelId)) {
102 State state = getValue(channelId);
104 state = UnDefType.NULL;
107 if (logger.isTraceEnabled()) {
108 logger.trace("Update channel {} with state {} ({})", channelId, state.toString(),
109 state.getClass().getSimpleName());
111 updateState(channelId, state);
115 * return an internal description for logging
117 * @return the description of the thing
119 protected abstract String getDescription();
122 * get the "new" associated value for a channelId
124 * @param channelId the id identifying the channel
125 * @return the "new" associated value
127 protected abstract State getValue(String channelId);
130 * Called by the bridge to fetch data and update channels
132 * @param bridgeConfiguration the connected bridge configuration
134 public void refresh(FroniusBridgeConfiguration bridgeConfiguration) {
136 handleRefresh(bridgeConfiguration);
137 if (getThing().getStatus() != ThingStatus.ONLINE) {
138 updateStatus(ThingStatus.ONLINE);
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());
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
150 * @param bridgeConfiguration the connected bridge configuration
152 protected abstract void handleRefresh(FroniusBridgeConfiguration bridgeConfiguration)
153 throws FroniusCommunicationException;
157 * @param type response class type
158 * @param url to request
159 * @return the object representation of the json response
161 protected @NonNull <T extends BaseFroniusResponse> T collectDataFromUrl(Class<T> type, String url)
162 throws FroniusCommunicationException {
166 logger.trace("Fetching URL = {}", url);
167 String response = FroniusHttpUtil.executeUrl(url, API_TIMEOUT);
168 logger.trace("aqiResponse = {}", response);
170 T result = gson.fromJson(response, type);
171 if (result == null) {
172 throw new FroniusCommunicationException("Empty json result");
175 HeadStatus status = result.getHead().getStatus();
176 if (status.getCode() == 0) {
180 // Sometimes Fronius would return a HTTP status 200 with a proper JSON data
181 // with Reason: Transfer timeout.
185 // "Reason" : "Transfer timeout.",
186 // "UserMessage" : ""
188 logger.debug("Error from Fronius attempt #{}: {} - {}", attempts, status.getCode(), status.getReason());
190 throw new FroniusCommunicationException(status.getReason());
192 Thread.sleep(500 * attempts);
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);