[haassohnpelletstove] Improve connection handling (#18212)

* Adding a Reconnect Rate to improve connectivity for the thing in case of weak wifi signal and automatically reconnect the thing.

Signed-off-by: chingon007 <tron81@gmx.de>
pull/14578/merge
chingon007 2025-02-20 13:05:34 +01:00 committed by GitHub
parent c3fa94302d
commit 96def56c6d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 100 additions and 196 deletions

View File

@ -12,7 +12,7 @@ WIFI module. More information about the WIFI module can be found here: <https://
## Thing Configuration
In general two parameters are required. The IP-Address of the WIFI-Modul of the Stove in the local Network and the Access PIN of the Stove.
The PIN can be found directly at the stove under the Menue/Network/WLAN-PIN
The PIN can be found directly at the stove under the Menue/Network/WLAN-PIN.
```java
Thing haassohnpelletstove:oven:myOven "Pelletstove" [ hostIP="192.168.0.23", hostPIN="1234"]

View File

@ -13,7 +13,6 @@
package org.openhab.binding.haassohnpelletstove.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link HaasSohnpelletstoveConfiguration} class contains fields mapping thing configuration parameters.
@ -23,7 +22,7 @@ import org.eclipse.jdt.annotation.Nullable;
@NonNullByDefault
public class HaasSohnpelletstoveConfiguration {
public @Nullable String hostIP = null;
public @Nullable String hostPIN = null;
public String hostIP = "";
public String hostPIN = "";
public int refreshRate = 30;
}

View File

@ -108,82 +108,63 @@ public class HaasSohnpelletstoveHandler extends BaseThingHandler {
}
}
/**
* Calls the service to update the oven data
*
* @param postdata
*/
private boolean updateOvenData(@Nullable String postdata) {
Helper message = new Helper();
if (serviceCommunication.updateOvenData(postdata, message, this.getThing().getUID().toString())) {
updateStatus(ThingStatus.ONLINE);
return true;
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
message.getStatusDesription());
return false;
}
}
@Override
public void initialize() {
logger.debug("Initializing haassohnpelletstove handler for thing {}", getThing().getUID());
config = getConfigAs(HaasSohnpelletstoveConfiguration.class);
boolean validConfig = true;
String errors = "";
String statusDescr = null;
if (config.refreshRate < 0 && config.refreshRate > 999) {
errors += " Parameter 'refresh Rate' greater then 0 and less then 1000.";
statusDescr = "Parameter 'refresh Rate' greater then 0 and less then 1000.";
validConfig = false;
if (config.refreshRate < 1 || config.refreshRate > 1000) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Parameter 'refresh Rate' must be in the range 1-1000!");
return;
}
if (config.hostIP == null) {
errors += " Parameter 'hostIP' must be configured.";
statusDescr = "IP Address must be configured!";
validConfig = false;
if (config.hostIP.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "IP Address must be configured!");
return;
}
if (config.hostPIN == null) {
errors += " Parameter 'hostPin' must be configured.";
statusDescr = "PIN must be configured!";
validConfig = false;
if (config.hostPIN.isBlank()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Parameter 'hostPin' must be configured!");
return;
}
errors = errors.trim();
Helper message = new Helper();
message.setStatusDescription(statusDescr);
if (validConfig) {
serviceCommunication.setConfig(config);
if (serviceCommunication.refreshOvenConnection(message, this.getThing().getUID().toString())) {
if (updateOvenData(null)) {
updateStatus(ThingStatus.ONLINE);
updateLinkedChannels();
serviceCommunication.setConfig(config);
updateStatus(ThingStatus.UNKNOWN);
scheduler.submit(() -> {
if (updateOvenData(null)) {
for (Channel channel : getThing().getChannels()) {
if (isLinked(channel.getUID().getId())) {
channelLinked(channel.getUID());
}
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message.getStatusDesription());
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, message.getStatusDesription());
}
});
}
private void updateLinkedChannels() {
verifyLinkedChannel(CHANNELISTEMP);
verifyLinkedChannel(CHANNELMODE);
verifyLinkedChannel(CHANNELPOWER);
verifyLinkedChannel(CHANNELSPTEMP);
verifyLinkedChannel(CHANNELECOMODE);
verifyLinkedChannel(CHANNELIGNITIONS);
verifyLinkedChannel(CHANNELMAINTENANCEIN);
verifyLinkedChannel(CHANNELCLEANINGIN);
verifyLinkedChannel(CHANNELCONSUMPTION);
verifyLinkedChannel(CHANNELONTIME);
if (!linkedChannels.isEmpty()) {
updateOvenData(null);
for (Channel channel : getThing().getChannels()) {
updateChannel(channel.getUID().getId());
}
startAutomaticRefresh();
automaticRefreshing = true;
/**
* Calls the service to update the oven data
*
* @return true if the update succeeded, false otherwise
*/
private boolean updateOvenData(@Nullable String postdata) {
String error = "";
if (postdata != null) {
error = serviceCommunication.updateOvenData(postdata);
} else {
error = serviceCommunication.refreshOvenConnection();
}
if (error.isEmpty()) {
if (ThingStatus.OFFLINE.equals(getThing().getStatus())) {
updateStatus(ThingStatus.UNKNOWN);
}
if (!ThingStatus.ONLINE.equals(getThing().getStatus())) {
updateStatus(ThingStatus.ONLINE);
}
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, error);
return false;
}
return error.isEmpty();
}
private void verifyLinkedChannel(String channelID) {
@ -195,14 +176,24 @@ public class HaasSohnpelletstoveHandler extends BaseThingHandler {
@Override
public void dispose() {
stopScheduler();
linkedChannels.clear();
automaticRefreshing = false;
}
private void stopScheduler() {
ScheduledFuture<?> job = refreshJob;
if (job != null) {
job.cancel(true);
if (job == null || job.isCancelled()) {
refreshJob = scheduler.scheduleWithFixedDelay(this::refreshChannels, 0, config.refreshRate,
TimeUnit.SECONDS);
}
}
private void refreshChannels() {
if (updateOvenData(null)) {
for (Channel channel : getThing().getChannels()) {
updateChannel(channel.getUID().getId());
}
}
refreshJob = null;
}
/**
@ -217,9 +208,10 @@ public class HaasSohnpelletstoveHandler extends BaseThingHandler {
}
private void run() {
updateOvenData(null);
for (Channel channel : getThing().getChannels()) {
updateChannel(channel.getUID().getId());
if (updateOvenData(null)) {
for (Channel channel : getThing().getChannels()) {
updateChannel(channel.getUID().getId());
}
}
}

View File

@ -26,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* This class handles the JSON communication with the Wifi Modul of the Stove
@ -52,105 +53,65 @@ public class HaasSohnpelletstoveJSONCommunication {
/**
* Refreshes the oven Connection with the internal oven token.
*
* @param message Message object to pass errors to the calling method.
* @param thingUID Thing UID for logging purposes
* @return true if no error occurred, false otherwise.
* @return an empty string if no error occurred, the error message otherwise.
*/
public boolean refreshOvenConnection(Helper message, String thingUID) {
if (config.hostIP == null || config.hostPIN == null) {
message.setStatusDescription("Error in configuration. Please recreate Thing.");
return false;
}
HaasSohnpelletstoveJsonDataDTO result = null;
boolean resultOk = false;
String error = "", errorDetail = "", statusDescr = "";
public String refreshOvenConnection() {
String result = "";
HaasSohnpelletstoveJsonDataDTO responseObject = null;
String urlStr = "http://" + config.hostIP + "/status.cgi";
String response = null;
try {
response = HttpUtil.executeUrl("GET", urlStr, 10000);
logger.debug("OvenData = {}", response);
result = gson.fromJson(response, HaasSohnpelletstoveJsonDataDTO.class);
resultOk = true;
} catch (IOException e) {
responseObject = gson.fromJson(response, HaasSohnpelletstoveJsonDataDTO.class);
ovenData = responseObject;
xhspin = getValidXHSPIN(ovenData);
} catch (IOException | JsonSyntaxException e) {
logger.debug("Error processiong Get request {}", urlStr);
statusDescr = "Timeout error with" + config.hostIP
+ ". Cannot find service on give IP. Please verify the IP-Address!";
errorDetail = e.getMessage();
resultOk = false;
result = "Timeout error with " + config.hostIP
+ ". Cannot find service on given IP. Please verify the IP-Address!";
logger.debug("Error in establishing connection: {}", e.getMessage());
} catch (Exception e) {
logger.debug("Unknwon Error: {}", e.getMessage());
errorDetail = e.getMessage();
resultOk = false;
}
if (resultOk) {
ovenData = result;
xhspin = getValidXHSPIN(ovenData);
} else {
logger.debug("Setting thing '{}' to OFFLINE: Error '{}': {}", thingUID, error, errorDetail);
ovenData = new HaasSohnpelletstoveJsonDataDTO();
}
message.setStatusDescription(statusDescr);
return resultOk;
return result;
}
/**
* Gets the status of the oven
*
* @return true if success or false in case of error
* @return an empty string if no error occurred, the error message otherwise.
*/
public boolean updateOvenData(@Nullable String postData, Helper helper, String thingUID) {
String statusDescr = "";
boolean resultOk = false;
String error = "", errorDetail = "";
if (config.hostIP == null || config.hostPIN == null) {
return false;
}
public String updateOvenData(@Nullable String postData) {
String error = "";
String urlStr = "http://" + config.hostIP + "/status.cgi";
// Run the HTTP POST request and get the JSON response from Oven
String response = null;
Properties httpHeader = new Properties();
if (postData != null) {
try {
InputStream targetStream = new ByteArrayInputStream(postData.getBytes(StandardCharsets.UTF_8));
refreshOvenConnection(helper, thingUID);
httpHeader = createHeader(postData);
response = HttpUtil.executeUrl("POST", urlStr, httpHeader, targetStream, "application/json", 10000);
resultOk = true;
logger.debug("Execute POST request with content to {} with header: {}", urlStr, httpHeader.toString());
} catch (IOException e) {
logger.debug("Error processiong POST request {}", urlStr);
statusDescr = "Cannot execute command on Stove. Please verify connection and Thing Status";
resultOk = false;
try {
InputStream targetStream = null;
if (postData != null) {
targetStream = new ByteArrayInputStream(postData.getBytes(StandardCharsets.UTF_8));
}
} else {
try {
refreshOvenConnection(helper, thingUID);
httpHeader = createHeader(null);
response = HttpUtil.executeUrl("POST", urlStr, httpHeader, null, "", 10000);
resultOk = true;
logger.debug("Execute POST request to {} with header: {}", urlStr, httpHeader.toString());
} catch (IOException e) {
logger.debug("Error processiong POST request {}", e.getMessage());
String message = e.getMessage();
if (message != null && message.contains("Authentication challenge without WWW-Authenticate ")) {
statusDescr = "Cannot connect to stove. Given PIN: " + config.hostPIN + " is incorrect!";
}
resultOk = false;
}
}
if (resultOk) {
logger.debug("OvenData = {}", response);
refreshOvenConnection();
httpHeader = createHeader(postData != null ? postData : null);
response = HttpUtil.executeUrl("POST", urlStr, httpHeader, targetStream != null ? targetStream : null,
"application/json", 10000);
logger.debug("Execute POST request with content to {} with header: {}", urlStr, httpHeader.toString());
ovenData = gson.fromJson(response, HaasSohnpelletstoveJsonDataDTO.class);
} else {
logger.debug("Setting thing '{}' to OFFLINE: Error '{}': {}", thingUID, error, errorDetail);
ovenData = new HaasSohnpelletstoveJsonDataDTO();
logger.debug("OvenData = {}", response);
} catch (IOException e) {
logger.debug("Error processiong POST request {}", urlStr);
error = "Cannot execute command on Stove. Please verify connection or PIN";
} catch (JsonSyntaxException e) {
logger.debug("Error in establishing connection: {}", e.getMessage());
error = "Cannot find service on given IP " + config.hostIP + ". Please verify the IP address!";
}
helper.setStatusDescription(statusDescr);
return resultOk;
return error;
}
/**

View File

@ -1,48 +0,0 @@
/*
* Copyright (c) 2010-2025 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.haassohnpelletstove.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link Helper} is a Helper class to overcome Call by value for a Status Description.
*
*
* @author Christian Feininger - Initial contribution
*/
@NonNullByDefault
public class Helper {
private String statusDescription = "";
/***
* Gets the Status Description
*
* @return
*/
public String getStatusDesription() {
return statusDescription;
}
/***
* Sets the Status Description
*
* @param status
*/
public void setStatusDescription(@Nullable String status) {
if (status != null) {
statusDescription = statusDescription + "\n" + status;
}
}
}

View File

@ -36,7 +36,7 @@
<label>PIN</label>
<description>Please add the PIN of your oven here. You can find it in the Menu directly in your oven.</description>
</parameter>
<parameter name="refreshRate" type="integer" unit="s" min="0" max="1000">
<parameter name="refreshRate" type="integer" unit="s" min="1" max="1000">
<label>Refresh Rate</label>
<description>How often the Pellet Stove should schedule a refresh after a channel is linked to an item. Temperature
data will be refreshed according this set time in seconds. Valid input is 0 - 999.