[fronius] Fix communication errors by retrying failed http requests (#12255)
* [fronius] Fix communication errors by retrying failed http requests Signed-off-by: Jimmy Tanagra <jcode@tanagra.id.au>
This commit is contained in:
parent
f70290a5ae
commit
baa9aacf50
@ -13,5 +13,4 @@
|
|||||||
<artifactId>org.openhab.binding.fronius</artifactId>
|
<artifactId>org.openhab.binding.fronius</artifactId>
|
||||||
|
|
||||||
<name>openHAB Add-ons :: Bundles :: Fronius Binding</name>
|
<name>openHAB Add-ons :: Bundles :: Fronius Binding</name>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.fronius.internal;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception for unexpected response from or communication failure with the Fronius controller.
|
||||||
|
*
|
||||||
|
* @author Jimmy Tanagra - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FroniusCommunicationException extends IOException {
|
||||||
|
private static final long serialVersionUID = 619020705591964155L;
|
||||||
|
|
||||||
|
public FroniusCommunicationException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FroniusCommunicationException(Throwable ex) {
|
||||||
|
super(ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FroniusCommunicationException(String message, @Nullable Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,6 +17,8 @@ import static org.openhab.binding.fronius.internal.FroniusBindingConstants.*;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.fronius.internal.handler.FroniusBridgeHandler;
|
import org.openhab.binding.fronius.internal.handler.FroniusBridgeHandler;
|
||||||
import org.openhab.binding.fronius.internal.handler.FroniusMeterHandler;
|
import org.openhab.binding.fronius.internal.handler.FroniusMeterHandler;
|
||||||
import org.openhab.binding.fronius.internal.handler.FroniusOhmpilotHandler;
|
import org.openhab.binding.fronius.internal.handler.FroniusOhmpilotHandler;
|
||||||
@ -37,6 +39,7 @@ import org.osgi.service.component.annotations.Component;
|
|||||||
* @author Hannes Spenger - Added ohmpilot
|
* @author Hannes Spenger - Added ohmpilot
|
||||||
*/
|
*/
|
||||||
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.fronius")
|
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.fronius")
|
||||||
|
@NonNullByDefault
|
||||||
public class FroniusHandlerFactory extends BaseThingHandlerFactory {
|
public class FroniusHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
|
||||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<ThingTypeUID>() {
|
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = new HashSet<ThingTypeUID>() {
|
||||||
@ -56,7 +59,7 @@ public class FroniusHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ThingHandler createHandler(Thing thing) {
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
|
||||||
if (thingTypeUID.equals(THING_TYPE_INVERTER)) {
|
if (thingTypeUID.equals(THING_TYPE_INVERTER)) {
|
||||||
|
|||||||
@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.fronius.internal;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.io.net.http.HttpUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* A version of HttpUtil implementation that retries on failure
|
||||||
|
*
|
||||||
|
* @author Jimmy Tanagra - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class FroniusHttpUtil {
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(FroniusHttpUtil.class);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue a HTTP GET request and retry on failure
|
||||||
|
*
|
||||||
|
* @param url the url to execute
|
||||||
|
* @param timeout the socket timeout in milliseconds to wait for data
|
||||||
|
* @return the response body
|
||||||
|
* @throws FroniusCommunicationException when the request execution failed or interrupted
|
||||||
|
*/
|
||||||
|
public synchronized static String executeUrl(String url, int timeout) throws FroniusCommunicationException {
|
||||||
|
int attemptCount = 1;
|
||||||
|
try {
|
||||||
|
while (true) {
|
||||||
|
Throwable lastException = null;
|
||||||
|
String result = null;
|
||||||
|
try {
|
||||||
|
result = HttpUtil.executeUrl("GET", url, timeout);
|
||||||
|
} catch (IOException e) {
|
||||||
|
// HttpUtil::executeUrl wraps InterruptedException into IOException.
|
||||||
|
// Unwrap and rethrow it so that we don't retry on InterruptedException
|
||||||
|
if (e.getCause() instanceof InterruptedException) {
|
||||||
|
throw (InterruptedException) e.getCause();
|
||||||
|
}
|
||||||
|
lastException = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
if (attemptCount > 1) {
|
||||||
|
logger.debug("Attempt #{} successful {}", attemptCount, url);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attemptCount >= 3) {
|
||||||
|
logger.debug("Failed connecting to {} after {} attempts.", url, attemptCount, lastException);
|
||||||
|
throw new FroniusCommunicationException("Unable to connect", lastException);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("HTTP error on attempt #{} {}", attemptCount, url);
|
||||||
|
Thread.sleep(500 * attemptCount);
|
||||||
|
attemptCount++;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new FroniusCommunicationException("Interrupted", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,13 +12,15 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.fronius.internal.handler;
|
package org.openhab.binding.fronius.internal.handler;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNull;
|
||||||
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
||||||
|
import org.openhab.binding.fronius.internal.FroniusCommunicationException;
|
||||||
|
import org.openhab.binding.fronius.internal.FroniusHttpUtil;
|
||||||
import org.openhab.binding.fronius.internal.api.BaseFroniusResponse;
|
import org.openhab.binding.fronius.internal.api.BaseFroniusResponse;
|
||||||
|
import org.openhab.binding.fronius.internal.api.HeadStatus;
|
||||||
import org.openhab.binding.fronius.internal.api.ValueUnit;
|
import org.openhab.binding.fronius.internal.api.ValueUnit;
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
import org.openhab.core.library.types.StringType;
|
import org.openhab.core.library.types.StringType;
|
||||||
@ -29,7 +31,6 @@ import org.openhab.core.thing.Thing;
|
|||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
import org.openhab.core.thing.binding.ThingHandler;
|
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.openhab.core.types.RefreshType;
|
import org.openhab.core.types.RefreshType;
|
||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
@ -45,6 +46,7 @@ import com.google.gson.JsonSyntaxException;
|
|||||||
* @author Gerrit Beine - Initial contribution
|
* @author Gerrit Beine - Initial contribution
|
||||||
* @author Thomas Rokohl - Refactoring to merge the concepts
|
* @author Thomas Rokohl - Refactoring to merge the concepts
|
||||||
* @author Thomas Kordelle - Added inverter power, battery state of charge and PV solar yield
|
* @author Thomas Kordelle - Added inverter power, battery state of charge and PV solar yield
|
||||||
|
* @author Jimmy Tanagra - Implement connection retry
|
||||||
*/
|
*/
|
||||||
public abstract class FroniusBaseThingHandler extends BaseThingHandler {
|
public abstract class FroniusBaseThingHandler extends BaseThingHandler {
|
||||||
|
|
||||||
@ -69,29 +71,16 @@ public abstract class FroniusBaseThingHandler extends BaseThingHandler {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
if (getFroniusBridgeHandler() == null) {
|
|
||||||
logger.debug("Initializing {} Service is only supported within a bridge", serviceDescription);
|
|
||||||
updateStatus(ThingStatus.OFFLINE);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
logger.debug("Initializing {} Service", serviceDescription);
|
logger.debug("Initializing {} Service", serviceDescription);
|
||||||
getFroniusBridgeHandler().registerService(this);
|
// this is important so FroniusBridgeHandler::childHandlerInitialized gets called
|
||||||
}
|
Bridge bridge = getBridge();
|
||||||
|
if (bridge == null || bridge.getHandler() == null) {
|
||||||
private synchronized FroniusBridgeHandler getFroniusBridgeHandler() {
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED);
|
||||||
if (this.bridgeHandler == null) {
|
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||||
Bridge bridge = getBridge();
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
if (bridge == null) {
|
} else {
|
||||||
return null;
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||||
}
|
|
||||||
ThingHandler handler = bridge.getHandler();
|
|
||||||
if (handler instanceof FroniusBridgeHandler) {
|
|
||||||
this.bridgeHandler = (FroniusBridgeHandler) handler;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return this.bridgeHandler;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,7 +124,7 @@ public abstract class FroniusBaseThingHandler extends BaseThingHandler {
|
|||||||
} else {
|
} else {
|
||||||
logger.warn("Update channel {}: Unsupported value type {}", channelId, value.getClass().getSimpleName());
|
logger.warn("Update channel {}: Unsupported value type {}", channelId, value.getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
logger.debug("Update channel {} with state {} ({})", channelId, (state == null) ? "null" : state.toString(),
|
logger.trace("Update channel {} with state {} ({})", channelId, (state == null) ? "null" : state.toString(),
|
||||||
value.getClass().getSimpleName());
|
value.getClass().getSimpleName());
|
||||||
|
|
||||||
// Update the channel
|
// Update the channel
|
||||||
@ -160,12 +149,30 @@ public abstract class FroniusBaseThingHandler extends BaseThingHandler {
|
|||||||
protected abstract Object getValue(String channelId);
|
protected abstract Object getValue(String channelId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* do whatever a thing must do to update the values
|
* Called by the bridge to fetch data and update channels
|
||||||
|
*
|
||||||
|
* @param bridgeConfiguration the connected bridge configuration
|
||||||
|
*/
|
||||||
|
public void refresh(FroniusBridgeConfiguration bridgeConfiguration) {
|
||||||
|
try {
|
||||||
|
handleRefresh(bridgeConfiguration);
|
||||||
|
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
}
|
||||||
|
} catch (FroniusCommunicationException | RuntimeException e) {
|
||||||
|
logger.debug("Exception caught in refresh() for {}", getThing().getUID().getId(), e);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method should be overridden to do whatever a thing must do to update its channels
|
||||||
* this function is called from the bridge in a given interval
|
* this function is called from the bridge in a given interval
|
||||||
*
|
*
|
||||||
* @param bridgeConfiguration the connected bridge configuration
|
* @param bridgeConfiguration the connected bridge configuration
|
||||||
*/
|
*/
|
||||||
public abstract void refresh(FroniusBridgeConfiguration bridgeConfiguration);
|
protected abstract void handleRefresh(FroniusBridgeConfiguration bridgeConfiguration)
|
||||||
|
throws FroniusCommunicationException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -173,46 +180,47 @@ public abstract class FroniusBaseThingHandler extends BaseThingHandler {
|
|||||||
* @param url to request
|
* @param url to request
|
||||||
* @return the object representation of the json response
|
* @return the object representation of the json response
|
||||||
*/
|
*/
|
||||||
protected <T extends BaseFroniusResponse> T collectDataFormUrl(Class<T> type, String url) {
|
protected @NonNull <T extends BaseFroniusResponse> T collectDataFromUrl(Class<T> type, String url)
|
||||||
T result = null;
|
throws FroniusCommunicationException {
|
||||||
boolean resultOk = false;
|
|
||||||
String errorMsg = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.debug("URL = {}", url);
|
int attempts = 1;
|
||||||
String response = HttpUtil.executeUrl("GET", url, API_TIMEOUT);
|
while (true) {
|
||||||
|
logger.trace("Fetching URL = {}", url);
|
||||||
|
String response = FroniusHttpUtil.executeUrl(url, API_TIMEOUT);
|
||||||
|
logger.trace("aqiResponse = {}", response);
|
||||||
|
|
||||||
if (response != null) {
|
T result = gson.fromJson(response, type);
|
||||||
logger.debug("aqiResponse = {}", response);
|
if (result == null) {
|
||||||
result = gson.fromJson(response, type);
|
throw new FroniusCommunicationException("Empty json result");
|
||||||
}
|
|
||||||
|
|
||||||
if (result == null) {
|
|
||||||
errorMsg = "no data returned";
|
|
||||||
} else {
|
|
||||||
if (result.getHead().getStatus().getCode() == 0) {
|
|
||||||
resultOk = true;
|
|
||||||
} else {
|
|
||||||
errorMsg = result.getHead().getStatus().getReason();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (!resultOk) {
|
|
||||||
logger.debug("Error in fronius response: {}", errorMsg);
|
|
||||||
}
|
|
||||||
} catch (JsonSyntaxException | NumberFormatException e) {
|
|
||||||
errorMsg = "Invalid JSON data received";
|
|
||||||
logger.debug("Error running fronius request: {}", e.getMessage());
|
|
||||||
} catch (IOException | IllegalStateException e) {
|
|
||||||
errorMsg = e.getMessage();
|
|
||||||
logger.debug("Error running fronius request: {}", errorMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the thing status
|
HeadStatus status = result.getHead().getStatus();
|
||||||
if (resultOk) {
|
if (status.getCode() == 0) {
|
||||||
updateStatus(ThingStatus.ONLINE);
|
return result;
|
||||||
} else {
|
}
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, errorMsg);
|
|
||||||
|
// Sometimes Fronius would return a HTTP status 200 with a proper JSON data
|
||||||
|
// with Reason: Transfer timeout.
|
||||||
|
//
|
||||||
|
// "Status" : {
|
||||||
|
// "Code" : 8,
|
||||||
|
// "Reason" : "Transfer timeout.",
|
||||||
|
// "UserMessage" : ""
|
||||||
|
// },
|
||||||
|
logger.debug("Error from Fronius attempt #{}: {} - {}", attempts, status.getCode(), status.getReason());
|
||||||
|
if (attempts >= 3) {
|
||||||
|
throw new FroniusCommunicationException(status.getReason());
|
||||||
|
}
|
||||||
|
Thread.sleep(500 * attempts);
|
||||||
|
attempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (JsonSyntaxException | NumberFormatException e) {
|
||||||
|
logger.debug("Received Invalid JSON Data", e);
|
||||||
|
throw new FroniusCommunicationException("Invalid JSON data received", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
throw new FroniusCommunicationException("Data collection interrupted", e);
|
||||||
}
|
}
|
||||||
return resultOk ? result : null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,19 +12,23 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.fronius.internal.handler;
|
package org.openhab.binding.fronius.internal.handler;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
||||||
import org.openhab.core.io.net.http.HttpUtil;
|
import org.openhab.binding.fronius.internal.FroniusCommunicationException;
|
||||||
|
import org.openhab.binding.fronius.internal.FroniusHttpUtil;
|
||||||
import org.openhab.core.thing.Bridge;
|
import org.openhab.core.thing.Bridge;
|
||||||
import org.openhab.core.thing.ChannelUID;
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingStatus;
|
import org.openhab.core.thing.ThingStatus;
|
||||||
import org.openhab.core.thing.ThingStatusDetail;
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
import org.openhab.core.types.Command;
|
import org.openhab.core.types.Command;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@ -35,13 +39,16 @@ import org.slf4j.LoggerFactory;
|
|||||||
* @author Gerrit Beine - Initial contribution
|
* @author Gerrit Beine - Initial contribution
|
||||||
* @author Thomas Rokohl - Refactoring to merge the concepts.
|
* @author Thomas Rokohl - Refactoring to merge the concepts.
|
||||||
* Check if host is reachable.
|
* Check if host is reachable.
|
||||||
|
* @author Jimmy Tanagra - Refactor the child services registration
|
||||||
|
* Refactor host online check
|
||||||
*/
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
public class FroniusBridgeHandler extends BaseBridgeHandler {
|
public class FroniusBridgeHandler extends BaseBridgeHandler {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(FroniusBridgeHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(FroniusBridgeHandler.class);
|
||||||
private static final int DEFAULT_REFRESH_PERIOD = 10;
|
private static final int DEFAULT_REFRESH_PERIOD = 10;
|
||||||
private final Set<FroniusBaseThingHandler> services = new HashSet<>();
|
private final Set<FroniusBaseThingHandler> services = new HashSet<>();
|
||||||
private ScheduledFuture<?> refreshJob;
|
private @Nullable ScheduledFuture<?> refreshJob;
|
||||||
|
|
||||||
public FroniusBridgeHandler(Bridge bridge) {
|
public FroniusBridgeHandler(Bridge bridge) {
|
||||||
super(bridge);
|
super(bridge);
|
||||||
@ -51,10 +58,6 @@ public class FroniusBridgeHandler extends BaseBridgeHandler {
|
|||||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerService(final FroniusBaseThingHandler service) {
|
|
||||||
this.services.add(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
final FroniusBridgeConfiguration config = getConfigAs(FroniusBridgeConfiguration.class);
|
final FroniusBridgeConfiguration config = getConfigAs(FroniusBridgeConfiguration.class);
|
||||||
@ -74,7 +77,7 @@ public class FroniusBridgeHandler extends BaseBridgeHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (validConfig) {
|
if (validConfig) {
|
||||||
startAutomaticRefresh(config);
|
startAutomaticRefresh();
|
||||||
} else {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMsg);
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, errorMsg);
|
||||||
}
|
}
|
||||||
@ -84,38 +87,59 @@ public class FroniusBridgeHandler extends BaseBridgeHandler {
|
|||||||
public void dispose() {
|
public void dispose() {
|
||||||
if (refreshJob != null) {
|
if (refreshJob != null) {
|
||||||
refreshJob.cancel(true);
|
refreshJob.cancel(true);
|
||||||
|
refreshJob = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||||
|
if (childHandler instanceof FroniusBaseThingHandler) {
|
||||||
|
this.services.add((FroniusBaseThingHandler) childHandler);
|
||||||
|
restartAutomaticRefresh();
|
||||||
|
} else {
|
||||||
|
logger.debug("Child handler {} not added because it is not an instance of FroniusBaseThingHandler",
|
||||||
|
childThing.getUID().getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
|
||||||
|
this.services.remove((FroniusBaseThingHandler) childHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restartAutomaticRefresh() {
|
||||||
|
if (refreshJob != null) { // refreshJob should be null if the config isn't valid
|
||||||
|
refreshJob.cancel(false);
|
||||||
|
startAutomaticRefresh();
|
||||||
}
|
}
|
||||||
services.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the job refreshing the data
|
* Start the job refreshing the data
|
||||||
*/
|
*/
|
||||||
private void startAutomaticRefresh(FroniusBridgeConfiguration config) {
|
private void startAutomaticRefresh() {
|
||||||
if (refreshJob == null || refreshJob.isCancelled()) {
|
if (refreshJob == null || refreshJob.isCancelled()) {
|
||||||
|
final FroniusBridgeConfiguration config = getConfigAs(FroniusBridgeConfiguration.class);
|
||||||
Runnable runnable = () -> {
|
Runnable runnable = () -> {
|
||||||
boolean online = false;
|
|
||||||
try {
|
try {
|
||||||
if (HttpUtil.executeUrl("GET", "http://" + config.hostname, 5000) != null) {
|
checkBridgeOnline(config);
|
||||||
online = true;
|
if (getThing().getStatus() != ThingStatus.ONLINE) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
|
||||||
logger.debug("Connection Error: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!online) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
|
||||||
"hostname or ip is not reachable");
|
|
||||||
} else {
|
|
||||||
updateStatus(ThingStatus.ONLINE);
|
|
||||||
for (FroniusBaseThingHandler service : services) {
|
for (FroniusBaseThingHandler service : services) {
|
||||||
service.refresh(config);
|
service.refresh(config);
|
||||||
}
|
}
|
||||||
|
} catch (FroniusCommunicationException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
int delay = (config.refreshInterval != null) ? config.refreshInterval.intValue() : DEFAULT_REFRESH_PERIOD;
|
int delay = (config.refreshInterval != null) ? config.refreshInterval.intValue() : DEFAULT_REFRESH_PERIOD;
|
||||||
refreshJob = scheduler.scheduleWithFixedDelay(runnable, 0, delay, TimeUnit.SECONDS);
|
refreshJob = scheduler.scheduleWithFixedDelay(runnable, 1, delay, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkBridgeOnline(FroniusBridgeConfiguration config) throws FroniusCommunicationException {
|
||||||
|
FroniusHttpUtil.executeUrl("http://" + config.hostname, 5000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import java.util.Map;
|
|||||||
import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
|
import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
|
||||||
import org.openhab.binding.fronius.internal.FroniusBindingConstants;
|
import org.openhab.binding.fronius.internal.FroniusBindingConstants;
|
||||||
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
||||||
|
import org.openhab.binding.fronius.internal.FroniusCommunicationException;
|
||||||
import org.openhab.binding.fronius.internal.api.MeterRealtimeBodyDataDTO;
|
import org.openhab.binding.fronius.internal.api.MeterRealtimeBodyDataDTO;
|
||||||
import org.openhab.binding.fronius.internal.api.MeterRealtimeResponseDTO;
|
import org.openhab.binding.fronius.internal.api.MeterRealtimeResponseDTO;
|
||||||
import org.openhab.core.library.types.QuantityType;
|
import org.openhab.core.library.types.QuantityType;
|
||||||
@ -46,7 +47,7 @@ public class FroniusMeterHandler extends FroniusBaseThingHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh(FroniusBridgeConfiguration bridgeConfiguration) {
|
protected void handleRefresh(FroniusBridgeConfiguration bridgeConfiguration) throws FroniusCommunicationException {
|
||||||
updateData(bridgeConfiguration, config);
|
updateData(bridgeConfiguration, config);
|
||||||
updateChannels();
|
updateChannels();
|
||||||
updateProperties();
|
updateProperties();
|
||||||
@ -134,14 +135,11 @@ public class FroniusMeterHandler extends FroniusBaseThingHandler {
|
|||||||
/**
|
/**
|
||||||
* Get new data
|
* Get new data
|
||||||
*/
|
*/
|
||||||
private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config) {
|
private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config)
|
||||||
|
throws FroniusCommunicationException {
|
||||||
MeterRealtimeResponseDTO meterRealtimeResponse = getMeterRealtimeData(bridgeConfiguration.hostname,
|
MeterRealtimeResponseDTO meterRealtimeResponse = getMeterRealtimeData(bridgeConfiguration.hostname,
|
||||||
config.deviceId);
|
config.deviceId);
|
||||||
if (meterRealtimeResponse == null) {
|
meterRealtimeBodyData = meterRealtimeResponse.getBody().getData();
|
||||||
meterRealtimeBodyData = null;
|
|
||||||
} else {
|
|
||||||
meterRealtimeBodyData = meterRealtimeResponse.getBody().getData();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -151,10 +149,11 @@ public class FroniusMeterHandler extends FroniusBaseThingHandler {
|
|||||||
* @param deviceId of the device
|
* @param deviceId of the device
|
||||||
* @return {MeterRealtimeResponse} the object representation of the json response
|
* @return {MeterRealtimeResponse} the object representation of the json response
|
||||||
*/
|
*/
|
||||||
private MeterRealtimeResponseDTO getMeterRealtimeData(String ip, int deviceId) {
|
private MeterRealtimeResponseDTO getMeterRealtimeData(String ip, int deviceId)
|
||||||
|
throws FroniusCommunicationException {
|
||||||
String location = FroniusBindingConstants.METER_REALTIME_DATA_URL.replace("%IP%",
|
String location = FroniusBindingConstants.METER_REALTIME_DATA_URL.replace("%IP%",
|
||||||
(ip != null ? ip.trim() : ""));
|
(ip != null ? ip.trim() : ""));
|
||||||
location = location.replace("%DEVICEID%", Integer.toString(deviceId));
|
location = location.replace("%DEVICEID%", Integer.toString(deviceId));
|
||||||
return collectDataFormUrl(MeterRealtimeResponseDTO.class, location);
|
return collectDataFromUrl(MeterRealtimeResponseDTO.class, location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import java.util.Map;
|
|||||||
import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
|
import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
|
||||||
import org.openhab.binding.fronius.internal.FroniusBindingConstants;
|
import org.openhab.binding.fronius.internal.FroniusBindingConstants;
|
||||||
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
||||||
|
import org.openhab.binding.fronius.internal.FroniusCommunicationException;
|
||||||
import org.openhab.binding.fronius.internal.api.OhmpilotRealtimeBodyDataDTO;
|
import org.openhab.binding.fronius.internal.api.OhmpilotRealtimeBodyDataDTO;
|
||||||
import org.openhab.binding.fronius.internal.api.OhmpilotRealtimeResponseDTO;
|
import org.openhab.binding.fronius.internal.api.OhmpilotRealtimeResponseDTO;
|
||||||
import org.openhab.core.library.types.DecimalType;
|
import org.openhab.core.library.types.DecimalType;
|
||||||
@ -46,7 +47,7 @@ public class FroniusOhmpilotHandler extends FroniusBaseThingHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh(FroniusBridgeConfiguration bridgeConfiguration) {
|
public void handleRefresh(FroniusBridgeConfiguration bridgeConfiguration) throws FroniusCommunicationException {
|
||||||
updateData(bridgeConfiguration, config);
|
updateData(bridgeConfiguration, config);
|
||||||
updateChannels();
|
updateChannels();
|
||||||
updateProperties();
|
updateProperties();
|
||||||
@ -111,14 +112,11 @@ public class FroniusOhmpilotHandler extends FroniusBaseThingHandler {
|
|||||||
/**
|
/**
|
||||||
* Get new data
|
* Get new data
|
||||||
*/
|
*/
|
||||||
private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config) {
|
private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config)
|
||||||
|
throws FroniusCommunicationException {
|
||||||
OhmpilotRealtimeResponseDTO ohmpilotRealtimeResponse = getOhmpilotRealtimeData(bridgeConfiguration.hostname,
|
OhmpilotRealtimeResponseDTO ohmpilotRealtimeResponse = getOhmpilotRealtimeData(bridgeConfiguration.hostname,
|
||||||
config.deviceId);
|
config.deviceId);
|
||||||
if (ohmpilotRealtimeResponse == null) {
|
ohmpilotRealtimeBodyData = ohmpilotRealtimeResponse.getBody().getData();
|
||||||
ohmpilotRealtimeBodyData = null;
|
|
||||||
} else {
|
|
||||||
ohmpilotRealtimeBodyData = ohmpilotRealtimeResponse.getBody().getData();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,10 +126,11 @@ public class FroniusOhmpilotHandler extends FroniusBaseThingHandler {
|
|||||||
* @param deviceId of the device
|
* @param deviceId of the device
|
||||||
* @return {OhmpilotRealtimeResponse} the object representation of the json response
|
* @return {OhmpilotRealtimeResponse} the object representation of the json response
|
||||||
*/
|
*/
|
||||||
private OhmpilotRealtimeResponseDTO getOhmpilotRealtimeData(String ip, int deviceId) {
|
private OhmpilotRealtimeResponseDTO getOhmpilotRealtimeData(String ip, int deviceId)
|
||||||
|
throws FroniusCommunicationException {
|
||||||
String location = FroniusBindingConstants.OHMPILOT_REALTIME_DATA_URL.replace("%IP%",
|
String location = FroniusBindingConstants.OHMPILOT_REALTIME_DATA_URL.replace("%IP%",
|
||||||
(ip != null ? ip.trim() : ""));
|
(ip != null ? ip.trim() : ""));
|
||||||
location = location.replace("%DEVICEID%", Integer.toString(deviceId));
|
location = location.replace("%DEVICEID%", Integer.toString(deviceId));
|
||||||
return collectDataFormUrl(OhmpilotRealtimeResponseDTO.class, location);
|
return collectDataFromUrl(OhmpilotRealtimeResponseDTO.class, location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import java.util.Map;
|
|||||||
import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
|
import org.openhab.binding.fronius.internal.FroniusBaseDeviceConfiguration;
|
||||||
import org.openhab.binding.fronius.internal.FroniusBindingConstants;
|
import org.openhab.binding.fronius.internal.FroniusBindingConstants;
|
||||||
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
||||||
|
import org.openhab.binding.fronius.internal.FroniusCommunicationException;
|
||||||
import org.openhab.binding.fronius.internal.api.InverterRealtimeResponse;
|
import org.openhab.binding.fronius.internal.api.InverterRealtimeResponse;
|
||||||
import org.openhab.binding.fronius.internal.api.PowerFlowRealtimeInverter;
|
import org.openhab.binding.fronius.internal.api.PowerFlowRealtimeInverter;
|
||||||
import org.openhab.binding.fronius.internal.api.PowerFlowRealtimeResponse;
|
import org.openhab.binding.fronius.internal.api.PowerFlowRealtimeResponse;
|
||||||
@ -57,7 +58,7 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refresh(FroniusBridgeConfiguration bridgeConfiguration) {
|
protected void handleRefresh(FroniusBridgeConfiguration bridgeConfiguration) throws FroniusCommunicationException {
|
||||||
updateData(bridgeConfiguration, config);
|
updateData(bridgeConfiguration, config);
|
||||||
updateChannels();
|
updateChannels();
|
||||||
}
|
}
|
||||||
@ -177,7 +178,8 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
|||||||
/**
|
/**
|
||||||
* Get new data
|
* Get new data
|
||||||
*/
|
*/
|
||||||
private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config) {
|
private void updateData(FroniusBridgeConfiguration bridgeConfiguration, FroniusBaseDeviceConfiguration config)
|
||||||
|
throws FroniusCommunicationException {
|
||||||
inverterRealtimeResponse = getRealtimeData(bridgeConfiguration.hostname, config.deviceId);
|
inverterRealtimeResponse = getRealtimeData(bridgeConfiguration.hostname, config.deviceId);
|
||||||
powerFlowResponse = getPowerFlowRealtime(bridgeConfiguration.hostname);
|
powerFlowResponse = getPowerFlowRealtime(bridgeConfiguration.hostname);
|
||||||
}
|
}
|
||||||
@ -188,10 +190,10 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
|||||||
* @param ip address of the device
|
* @param ip address of the device
|
||||||
* @return {PowerFlowRealtimeResponse} the object representation of the json response
|
* @return {PowerFlowRealtimeResponse} the object representation of the json response
|
||||||
*/
|
*/
|
||||||
private PowerFlowRealtimeResponse getPowerFlowRealtime(String ip) {
|
private PowerFlowRealtimeResponse getPowerFlowRealtime(String ip) throws FroniusCommunicationException {
|
||||||
String location = FroniusBindingConstants.POWERFLOW_REALTIME_DATA.replace("%IP%",
|
String location = FroniusBindingConstants.POWERFLOW_REALTIME_DATA.replace("%IP%",
|
||||||
(ip != null ? ip.trim() : ""));
|
(ip != null ? ip.trim() : ""));
|
||||||
return collectDataFormUrl(PowerFlowRealtimeResponse.class, location);
|
return collectDataFromUrl(PowerFlowRealtimeResponse.class, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -201,10 +203,10 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
|||||||
* @param deviceId of the device
|
* @param deviceId of the device
|
||||||
* @return {InverterRealtimeResponse} the object representation of the json response
|
* @return {InverterRealtimeResponse} the object representation of the json response
|
||||||
*/
|
*/
|
||||||
private InverterRealtimeResponse getRealtimeData(String ip, int deviceId) {
|
private InverterRealtimeResponse getRealtimeData(String ip, int deviceId) throws FroniusCommunicationException {
|
||||||
String location = FroniusBindingConstants.INVERTER_REALTIME_DATA_URL.replace("%IP%",
|
String location = FroniusBindingConstants.INVERTER_REALTIME_DATA_URL.replace("%IP%",
|
||||||
(ip != null ? ip.trim() : ""));
|
(ip != null ? ip.trim() : ""));
|
||||||
location = location.replace("%DEVICEID%", Integer.toString(deviceId));
|
location = location.replace("%DEVICEID%", Integer.toString(deviceId));
|
||||||
return collectDataFormUrl(InverterRealtimeResponse.class, location);
|
return collectDataFromUrl(InverterRealtimeResponse.class, location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<label>Hostname</label>
|
<label>Hostname</label>
|
||||||
<description>The hostname or IP address of the Fronius gateway/device</description>
|
<description>The hostname or IP address of the Fronius gateway/device</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="refreshInterval" type="integer" min="2">
|
<parameter name="refreshInterval" type="integer" min="1">
|
||||||
<label>Refresh Interval</label>
|
<label>Refresh Interval</label>
|
||||||
<description>Specifies the refresh interval in seconds.</description>
|
<description>Specifies the refresh interval in seconds.</description>
|
||||||
<default>10</default>
|
<default>10</default>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user