[insteon] Fix device request failure handling (#18087)

Signed-off-by: Jeremy Setton <jeremy.setton@gmail.com>
This commit is contained in:
Jeremy 2025-01-11 05:24:29 -05:00 committed by Jacob Laursen
parent c6a7cecc40
commit be82a36eff
4 changed files with 88 additions and 77 deletions

View File

@ -43,6 +43,7 @@ public abstract class BaseDevice<@NonNull T extends DeviceAddress, @NonNull S ex
implements Device {
private static final int DIRECT_ACK_TIMEOUT = 6000; // in milliseconds
private static final int REQUEST_QUEUE_TIMEOUT = 30000; // in milliseconds
private static final int FAILED_REQUEST_THRESHOLD = 5;
protected static enum DeviceStatus {
INITIALIZED,
@ -63,6 +64,7 @@ public abstract class BaseDevice<@NonNull T extends DeviceAddress, @NonNull S ex
private Map<Msg, DeviceRequest> requestQueueHash = new HashMap<>();
private @Nullable DeviceFeature featureQueried;
private long pollInterval = -1L; // in milliseconds
private volatile int failedRequestCount = 0;
private volatile long lastRequestQueued = 0L;
private volatile long lastRequestSent = 0L;
@ -145,6 +147,10 @@ public abstract class BaseDevice<@NonNull T extends DeviceAddress, @NonNull S ex
}
}
public boolean isResponding() {
return failedRequestCount < FAILED_REQUEST_THRESHOLD;
}
public void setModem(@Nullable InsteonModem modem) {
this.modem = modem;
}
@ -403,10 +409,8 @@ public abstract class BaseDevice<@NonNull T extends DeviceAddress, @NonNull S ex
public void handleMessage(Msg msg) {
getFeatures().stream().filter(feature -> feature.handleMessage(msg)).findFirst().ifPresent(feature -> {
logger.trace("handled reply of direct for {}", feature.getName());
// mark feature queried as processed and answered
setFeatureQueried(null);
feature.setQueryMessage(null);
feature.setQueryStatus(QueryStatus.QUERY_ANSWERED);
// notify feature queried was answered
featureQueriedAnswered(feature);
});
}
@ -527,9 +531,8 @@ public abstract class BaseDevice<@NonNull T extends DeviceAddress, @NonNull S ex
return now + 1000L; // retry in 1000 ms
}
logger.debug("gave up waiting for {} query to be sent to {}", feature.getName(), address);
// reset feature queried as never queried
feature.setQueryMessage(null);
feature.setQueryStatus(QueryStatus.NEVER_QUERIED);
// notify feature queried failed
featureQueriedFailed(feature);
break;
case QUERY_SENT:
case QUERY_ACKED:
@ -541,20 +544,61 @@ public abstract class BaseDevice<@NonNull T extends DeviceAddress, @NonNull S ex
return now + 500L; // retry in 500 ms
}
logger.debug("gave up waiting for {} query reply from {}", feature.getName(), address);
// reset feature queried as never queried
feature.setQueryMessage(null);
feature.setQueryStatus(QueryStatus.NEVER_QUERIED);
// notify feature queried failed
featureQueriedFailed(feature);
break;
default:
logger.debug("unexpected feature {} query status {} for {}", feature.getName(), queryStatus,
address);
// reset feature queried
setFeatureQueried(null);
}
// reset feature queried otheriwse
setFeatureQueried(null);
}
return 0L;
}
/**
* Notifies that the feature queried was answered
*
* @param feature the feature queried
*/
protected void featureQueriedAnswered(DeviceFeature feature) {
// store current failed request count
int prevCount = failedRequestCount;
// reset failed request count
failedRequestCount = 0;
// mark feature queried as processed and answered
setFeatureQueried(null);
feature.setQueryMessage(null);
feature.setQueryStatus(QueryStatus.QUERY_ANSWERED);
// notify status changed if failed request count was above threshold
if (prevCount >= FAILED_REQUEST_THRESHOLD) {
statusChanged();
}
}
/**
* Notifies that the feature queried failed
*
* @param feature the feature queried
*/
protected void featureQueriedFailed(DeviceFeature feature) {
// increase failed request count
failedRequestCount++;
// mark feature queried as processed and never queried
setFeatureQueried(null);
feature.setQueryMessage(null);
feature.setQueryStatus(QueryStatus.NEVER_QUERIED);
// poll feature again if device is responding
if (isResponding()) {
feature.doPoll(0L);
}
// notify status changed if failed request count at threshold
if (failedRequestCount == FAILED_REQUEST_THRESHOLD) {
statusChanged();
}
}
/**
* Notifies that a message request was replied for this device
*
@ -564,10 +608,17 @@ public abstract class BaseDevice<@NonNull T extends DeviceAddress, @NonNull S ex
public void requestReplied(Msg msg) {
DeviceFeature feature = getFeatureQueried();
if (feature != null && feature.isMyReply(msg)) {
// mark feature queried as processed and answered
setFeatureQueried(null);
feature.setQueryMessage(null);
feature.setQueryStatus(QueryStatus.QUERY_ANSWERED);
if (msg.isReplyNack()) {
logger.debug("got a reply nack msg: {}", msg);
// notify feature queried failed
featureQueriedFailed(feature);
} else if (!msg.isInsteon()) {
// notify feature queried was answered
featureQueriedAnswered(feature);
} else {
// mark feature queried as acked
feature.setQueryStatus(QueryStatus.QUERY_ACKED);
}
}
}
@ -588,6 +639,18 @@ public abstract class BaseDevice<@NonNull T extends DeviceAddress, @NonNull S ex
}
}
/**
* Notifies that the status has changed for this device
*/
public void statusChanged() {
logger.trace("status for {} has changed", address);
@Nullable
S handler = getHandler();
if (handler != null) {
handler.updateStatus();
}
}
/**
* Refreshes this device
*/

View File

@ -66,7 +66,6 @@ import org.openhab.core.types.UnDefType;
public class InsteonDevice extends BaseDevice<InsteonAddress, InsteonDeviceHandler> {
private static final int BCAST_STATE_TIMEOUT = 2000; // in milliseconds
private static final int DEFAULT_HEARTBEAT_TIMEOUT = 1440; // in minutes
private static final int FAILED_MSG_COUNT_THRESHOLD = 5;
private InsteonEngine engine = InsteonEngine.UNKNOWN;
private LinkDB linkDB;
@ -76,7 +75,6 @@ public class InsteonDevice extends BaseDevice<InsteonAddress, InsteonDeviceHandl
private Map<Msg, DeviceRequest> deferredQueueHash = new HashMap<>();
private Map<Byte, Long> lastBroadcastReceived = new HashMap<>();
private Map<Integer, GroupMessageStateMachine> groupState = new HashMap<>();
private volatile int failedMsgCount = 0;
private volatile long lastMsgReceived = 0L;
public InsteonDevice() {
@ -145,10 +143,6 @@ public class InsteonDevice extends BaseDevice<InsteonAddress, InsteonDeviceHandl
return Optional.ofNullable(getFeature(type, group)).map(DeviceFeature::getState).orElse(null);
}
public boolean isResponding() {
return failedMsgCount < FAILED_MSG_COUNT_THRESHOLD;
}
public boolean isBatteryPowered() {
return getFlag("batteryPowered", false);
}
@ -444,35 +438,21 @@ public class InsteonDevice extends BaseDevice<InsteonAddress, InsteonDeviceHandl
}
return;
}
// store current responding state
boolean isPrevResponding = isResponding();
// handle message depending if failure report or not
if (msg.isFailureReport()) {
getFeatures().stream().filter(feature -> feature.isMyDirectAckOrNack(msg)).findFirst()
.ifPresent(feature -> {
logger.debug("got a failure report reply of direct for {}", feature.getName());
// increase failed message counter
failedMsgCount++;
// mark feature queried as processed and never queried
setFeatureQueried(null);
feature.setQueryMessage(null);
feature.setQueryStatus(QueryStatus.NEVER_QUERIED);
// poll feature again if device is responding
if (isResponding()) {
feature.doPoll(0L);
}
// notify feature queried failed
featureQueriedFailed(feature);
});
} else {
// update non-status features
getFeatures().stream().filter(feature -> !feature.isStatusFeature() && feature.handleMessage(msg))
.findFirst().ifPresent(feature -> {
logger.trace("handled reply of direct for {}", feature.getName());
// reset failed message counter
failedMsgCount = 0;
// mark feature queried as processed and answered
setFeatureQueried(null);
feature.setQueryMessage(null);
feature.setQueryStatus(QueryStatus.QUERY_ANSWERED);
// notify feature queried was answered
featureQueriedAnswered(feature);
});
// update all status features (e.g. device last update time)
getFeatures().stream().filter(DeviceFeature::isStatusFeature)
@ -485,10 +465,6 @@ public class InsteonDevice extends BaseDevice<InsteonAddress, InsteonDeviceHandl
long delay = msg.isAllLinkBroadcast() && !msg.isAllLinkSuccessReport() && !msg.isReplayed() ? 1500L : 0L;
doPoll(delay);
}
// notify if responding state changed
if (isPrevResponding != isResponding()) {
statusChanged();
}
}
/**
@ -906,25 +882,6 @@ public class InsteonDevice extends BaseDevice<InsteonAddress, InsteonDeviceHandl
}
}
/**
* Notifies that a message request was replied for this device
*
* @param msg the message received
*/
@Override
public void requestReplied(Msg msg) {
DeviceFeature feature = getFeatureQueried();
if (feature != null && feature.isMyReply(msg)) {
if (msg.isReplyAck()) {
// mark feature queried as acked
feature.setQueryStatus(QueryStatus.QUERY_ACKED);
} else {
logger.debug("got a reply nack msg: {}", msg);
super.requestReplied(msg);
}
}
}
/**
* Notifies that the link db has been updated for this device
*/
@ -966,18 +923,6 @@ public class InsteonDevice extends BaseDevice<InsteonAddress, InsteonDeviceHandl
}
}
/**
* Notifies that the status has changed for this device
*/
public void statusChanged() {
logger.trace("status for {} has changed", address);
InsteonDeviceHandler handler = getHandler();
if (handler != null) {
handler.updateStatus();
}
}
/**
* Factory method for creating a InsteonDevice from a device address, modem and cache
*

View File

@ -330,8 +330,6 @@ public abstract class InsteonBaseThingHandler extends BaseThingHandler implement
updateStatus();
}
public abstract void updateStatus();
public void updateProperties(Device device) {
Map<String, String> properties = editProperties();

View File

@ -91,4 +91,9 @@ public interface InsteonThingHandler extends ThingHandler {
* Refreshes the thing
*/
public void refresh();
/**
* Updates the thing status
*/
public void updateStatus();
}