[gce] File based items are not updated (#13545)

* [gce] Correcting non updated input channels

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2022-10-15 10:02:01 +02:00 committed by GitHub
parent 61b9e74c95
commit bdbd5a1ede
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 73 additions and 112 deletions

View File

@ -38,6 +38,4 @@ public class GCEBindingConstants {
public static final String EVENT_SHORT_PRESS = "SHORT_PRESS"; public static final String EVENT_SHORT_PRESS = "SHORT_PRESS";
public static final String EVENT_LONG_PRESS = "LONG_PRESS"; public static final String EVENT_LONG_PRESS = "LONG_PRESS";
public static final String EVENT_PULSE = "PULSE"; public static final String EVENT_PULSE = "PULSE";
// Adressable thing
} }

View File

@ -110,7 +110,7 @@ public class Ipx800DeviceConnector extends Thread {
/** /**
* Stop the device thread * Stop the device thread
*/ */
public void destroyAndExit() { public void dispose() {
interrupt(); interrupt();
disconnect(); disconnect();
} }
@ -161,7 +161,7 @@ public class Ipx800DeviceConnector extends Thread {
try { try {
Thread.sleep(DEFAULT_RECONNECT_TIMEOUT_MS); Thread.sleep(DEFAULT_RECONNECT_TIMEOUT_MS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
destroyAndExit(); dispose();
} }
} }

View File

@ -29,7 +29,6 @@ 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.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gce.internal.action.Ipx800Actions; import org.openhab.binding.gce.internal.action.Ipx800Actions;
import org.openhab.binding.gce.internal.config.AnalogInputConfiguration; import org.openhab.binding.gce.internal.config.AnalogInputConfiguration;
import org.openhab.binding.gce.internal.config.DigitalInputConfiguration; import org.openhab.binding.gce.internal.config.DigitalInputConfiguration;
@ -56,7 +55,6 @@ 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.ThingHandlerService; import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.thing.binding.builder.ChannelBuilder; import org.openhab.core.thing.binding.builder.ChannelBuilder;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.thing.type.ChannelKind; import org.openhab.core.thing.type.ChannelKind;
import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
@ -78,14 +76,11 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
private final Logger logger = LoggerFactory.getLogger(Ipx800v3Handler.class); private final Logger logger = LoggerFactory.getLogger(Ipx800v3Handler.class);
private @NonNullByDefault({}) Ipx800Configuration configuration; private Optional<Ipx800DeviceConnector> connector = Optional.empty();
private @NonNullByDefault({}) Ipx800DeviceConnector connector;
private @NonNullByDefault({}) StatusFileInterpreter statusFile;
private Optional<M2MMessageParser> parser = Optional.empty(); private Optional<M2MMessageParser> parser = Optional.empty();
private Optional<ScheduledFuture<?>> refreshJob = Optional.empty(); private Optional<ScheduledFuture<?>> refreshJob = Optional.empty();
private final Map<String, @Nullable PortData> portDatas = new HashMap<>(); private final Map<String, PortData> portDatas = new HashMap<>();
private class LongPressEvaluator implements Runnable { private class LongPressEvaluator implements Runnable {
private final ZonedDateTime referenceTime; private final ZonedDateTime referenceTime;
@ -115,36 +110,37 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
@Override @Override
public void initialize() { public void initialize() {
configuration = getConfigAs(Ipx800Configuration.class);
logger.debug("Initializing IPX800 handler for uid '{}'", getThing().getUID()); logger.debug("Initializing IPX800 handler for uid '{}'", getThing().getUID());
statusFile = new StatusFileInterpreter(configuration.hostname, this); Ipx800Configuration config = getConfigAs(Ipx800Configuration.class);
StatusFileInterpreter statusFile = new StatusFileInterpreter(config.hostname, this);
if (thing.getProperties().isEmpty()) { if (thing.getProperties().isEmpty()) {
discoverAttributes(); updateProperties(Map.of(Thing.PROPERTY_VENDOR, "GCE Electronics", Thing.PROPERTY_FIRMWARE_VERSION,
statusFile.getElement(StatusEntry.VERSION), Thing.PROPERTY_MAC_ADDRESS,
statusFile.getElement(StatusEntry.CONFIG_MAC)));
} }
List<Channel> channels = new ArrayList<>(getThing().getChannels()); List<Channel> channels = new ArrayList<>(getThing().getChannels());
ThingBuilder thingBuilder = editThing();
PortDefinition.asStream().forEach(portDefinition -> { PortDefinition.asStream().forEach(portDefinition -> {
int nbElements = statusFile.getMaxNumberofNodeType(portDefinition); int nbElements = statusFile.getMaxNumberofNodeType(portDefinition);
for (int i = 0; i < nbElements; i++) { for (int i = 0; i < nbElements; i++) {
createChannels(portDefinition, i, channels); ChannelUID portChannelUID = createChannels(portDefinition, i, channels);
portDatas.put(portChannelUID.getId(), new PortData());
} }
}); });
thingBuilder.withChannels(channels);
updateThing(thingBuilder.build());
connector = new Ipx800DeviceConnector(configuration.hostname, configuration.portNumber, getThing().getUID()); updateThing(editThing().withChannels(channels).build());
parser = Optional.of(new M2MMessageParser(connector, this));
connector = Optional.of(new Ipx800DeviceConnector(config.hostname, config.portNumber, getThing().getUID()));
parser = Optional.of(new M2MMessageParser(connector.get(), this));
updateStatus(ThingStatus.UNKNOWN); updateStatus(ThingStatus.UNKNOWN);
refreshJob = Optional.of(scheduler.scheduleWithFixedDelay(statusFile::read, 3000, configuration.pullInterval, refreshJob = Optional.of(
TimeUnit.MILLISECONDS)); scheduler.scheduleWithFixedDelay(statusFile::read, 3000, config.pullInterval, TimeUnit.MILLISECONDS));
connector.start(); connector.get().start();
} }
@Override @Override
@ -152,25 +148,15 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
refreshJob.ifPresent(job -> job.cancel(true)); refreshJob.ifPresent(job -> job.cancel(true));
refreshJob = Optional.empty(); refreshJob = Optional.empty();
if (connector != null) { connector.ifPresent(Ipx800DeviceConnector::dispose);
connector.destroyAndExit(); connector = Optional.empty();
}
parser = Optional.empty(); parser = Optional.empty();
portDatas.values().stream().forEach(portData -> { portDatas.values().stream().forEach(PortData::dispose);
if (portData != null) {
portData.dispose();
}
});
super.dispose(); super.dispose();
} }
protected void discoverAttributes() {
updateProperties(Map.of(Thing.PROPERTY_VENDOR, "GCE Electronics", Thing.PROPERTY_FIRMWARE_VERSION,
statusFile.getElement(StatusEntry.VERSION), Thing.PROPERTY_MAC_ADDRESS,
statusFile.getElement(StatusEntry.CONFIG_MAC)));
}
private void addIfChannelAbsent(ChannelBuilder channelBuilder, List<Channel> channels) { private void addIfChannelAbsent(ChannelBuilder channelBuilder, List<Channel> channels) {
Channel newChannel = channelBuilder.build(); Channel newChannel = channelBuilder.build();
if (channels.stream().noneMatch(c -> c.getUID().equals(newChannel.getUID()))) { if (channels.stream().noneMatch(c -> c.getUID().equals(newChannel.getUID()))) {
@ -178,7 +164,7 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
} }
} }
private void createChannels(PortDefinition portDefinition, int portIndex, List<Channel> channels) { private ChannelUID createChannels(PortDefinition portDefinition, int portIndex, List<Channel> channels) {
String ndx = Integer.toString(portIndex + 1); String ndx = Integer.toString(portIndex + 1);
String advancedChannelTypeName = portDefinition.toString() String advancedChannelTypeName = portDefinition.toString()
+ (portDefinition.isAdvanced(portIndex) ? "Advanced" : ""); + (portDefinition.isAdvanced(portIndex) ? "Advanced" : "");
@ -210,9 +196,12 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
.withLabel("Relay " + ndx).withType(channelType), channels); .withLabel("Relay " + ndx).withType(channelType), channels);
break; break;
} }
addIfChannelAbsent(ChannelBuilder.create(new ChannelUID(groupUID, ndx + "-duration"), "Number:Time") addIfChannelAbsent(ChannelBuilder.create(new ChannelUID(groupUID, ndx + "-duration"), "Number:Time")
.withType(new ChannelTypeUID(BINDING_ID, CHANNEL_LAST_STATE_DURATION)) .withType(new ChannelTypeUID(BINDING_ID, CHANNEL_LAST_STATE_DURATION))
.withLabel("Previous state duration " + ndx), channels); .withLabel("Previous state duration " + ndx), channels);
return mainChannelUID;
} }
@Override @Override
@ -258,7 +247,7 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
return; return;
} }
logger.debug("About to update port '{}' with data '{}'", port, value); logger.debug("About to update port '{}' with data '{}'", port, value);
State state = UnDefType.UNDEF; State state = UnDefType.NULL;
switch (portDefinition) { switch (portDefinition) {
case COUNTER: case COUNTER:
state = new DecimalType(value); state = new DecimalType(value);
@ -268,7 +257,7 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
break; break;
case ANALOG: case ANALOG:
state = new DecimalType(value); state = new DecimalType(value);
updateState(channelId + PROPERTY_SEPARATOR + CHANNEL_VOLTAGE, updateIfLinked(channelId + PROPERTY_SEPARATOR + CHANNEL_VOLTAGE,
new QuantityType<>(value * ANALOG_SAMPLING, Units.VOLT)); new QuantityType<>(value * ANALOG_SAMPLING, Units.VOLT));
break; break;
case CONTACT: case CONTACT:
@ -303,9 +292,9 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
break; break;
} }
updateState(channelId, state); updateIfLinked(channelId, state);
if (!portData.isInitializing()) { if (!portData.isInitializing()) {
updateState(channelId + PROPERTY_SEPARATOR + CHANNEL_LAST_STATE_DURATION, updateIfLinked(channelId + PROPERTY_SEPARATOR + CHANNEL_LAST_STATE_DURATION,
new QuantityType<>(sinceLastChange / 1000, Units.SECOND)); new QuantityType<>(sinceLastChange / 1000, Units.SECOND));
} }
portData.setData(value, now); portData.setData(value, now);
@ -317,6 +306,12 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
} }
} }
private void updateIfLinked(String channelId, State state) {
if (isLinked(channelId)) {
updateState(channelId, state);
}
}
protected void triggerPushButtonChannel(Channel channel, String event) { protected void triggerPushButtonChannel(Channel channel, String event) {
logger.debug("Triggering event '{}' on channel '{}'", event, channel.getUID()); logger.debug("Triggering event '{}' on channel '{}'", event, channel.getUID());
triggerChannel(channel.getUID().getId() + PROPERTY_SEPARATOR + TRIGGER_CONTACT, event); triggerChannel(channel.getUID().getId() + PROPERTY_SEPARATOR + TRIGGER_CONTACT, event);
@ -342,32 +337,10 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
logger.debug("Can not handle command '{}' on channel '{}'", command, channelUID); logger.debug("Can not handle command '{}' on channel '{}'", command, channelUID);
} }
@Override
public void channelLinked(ChannelUID channelUID) {
logger.debug("channelLinked: {}", channelUID);
final String channelId = channelUID.getId();
if (isValidPortId(channelUID)) {
Channel channel = thing.getChannel(channelUID);
if (channel != null) {
PortData data = new PortData();
portDatas.put(channelId, data);
}
}
}
private boolean isValidPortId(ChannelUID channelUID) { private boolean isValidPortId(ChannelUID channelUID) {
return channelUID.getIdWithoutGroup().chars().allMatch(Character::isDigit); return channelUID.getIdWithoutGroup().chars().allMatch(Character::isDigit);
} }
@Override
public void channelUnlinked(ChannelUID channelUID) {
super.channelUnlinked(channelUID);
PortData portData = portDatas.remove(channelUID.getId());
if (portData != null) {
portData.dispose();
}
}
public void resetCounter(int counter) { public void resetCounter(int counter) {
parser.ifPresent(p -> p.resetCounter(counter)); parser.ifPresent(p -> p.resetCounter(counter));
} }

View File

@ -15,7 +15,6 @@ package org.openhab.binding.gce.internal.model;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gce.internal.handler.Ipx800DeviceConnector; import org.openhab.binding.gce.internal.handler.Ipx800DeviceConnector;
import org.openhab.binding.gce.internal.handler.Ipx800EventListener; import org.openhab.binding.gce.internal.handler.Ipx800EventListener;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -35,11 +34,11 @@ public class M2MMessageParser {
private final Logger logger = LoggerFactory.getLogger(M2MMessageParser.class); private final Logger logger = LoggerFactory.getLogger(M2MMessageParser.class);
private final Ipx800DeviceConnector connector; private final Ipx800DeviceConnector connector;
private final @Nullable Ipx800EventListener listener; private final Ipx800EventListener listener;
private String expectedResponse = ""; private String expectedResponse = "";
public M2MMessageParser(Ipx800DeviceConnector connector, @Nullable Ipx800EventListener listener) { public M2MMessageParser(Ipx800DeviceConnector connector, Ipx800EventListener listener) {
this.connector = connector; this.connector = connector;
this.listener = listener; this.listener = listener;
connector.setParser(this); connector.setParser(this);
@ -87,10 +86,8 @@ public class M2MMessageParser {
private void setStatus(String port, double value) { private void setStatus(String port, double value) {
logger.debug("Received {} : {}", port, value); logger.debug("Received {} : {}", port, value);
if (listener != null) {
listener.dataReceived(port, value); listener.dataReceived(port, value);
} }
}
public void setExpectedResponse(String expectedResponse) { public void setExpectedResponse(String expectedResponse) {
if (expectedResponse.endsWith("s")) { // GetInputs or GetOutputs if (expectedResponse.endsWith("s")) { // GetInputs or GetOutputs
@ -125,10 +122,8 @@ public class M2MMessageParser {
public void errorOccurred(Exception e) { public void errorOccurred(Exception e) {
logger.warn("Error received from connector : {}", e.getMessage()); logger.warn("Error received from connector : {}", e.getMessage());
if (listener != null) {
listener.errorOccurred(e); listener.errorOccurred(e);
} }
}
public void resetPLC() { public void resetPLC() {
connector.send("Reset"); connector.send("Reset");

View File

@ -17,6 +17,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
@ -25,7 +26,6 @@ import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.gce.internal.handler.Ipx800EventListener; import org.openhab.binding.gce.internal.handler.Ipx800EventListener;
import org.openhab.core.io.net.http.HttpUtil; import org.openhab.core.io.net.http.HttpUtil;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -44,47 +44,53 @@ import org.xml.sax.SAXException;
@NonNullByDefault @NonNullByDefault
public class StatusFileInterpreter { public class StatusFileInterpreter {
private static final String URL_TEMPLATE = "http://%s/globalstatus.xml"; private static final String URL_TEMPLATE = "http://%s/globalstatus.xml";
private final Logger logger = LoggerFactory.getLogger(StatusFileInterpreter.class); private final Logger logger = LoggerFactory.getLogger(StatusFileInterpreter.class);
private final String hostname; private final DocumentBuilder builder;
private @Nullable Document doc; private final String url;
private final Ipx800EventListener listener; private final Ipx800EventListener listener;
private Optional<Document> doc = Optional.empty();
public static enum StatusEntry { public static enum StatusEntry {
VERSION, VERSION,
CONFIG_MAC; CONFIG_MAC;
} }
public StatusFileInterpreter(String hostname, Ipx800EventListener listener) { public StatusFileInterpreter(String hostname, Ipx800EventListener listener) {
this.hostname = hostname; this.url = String.format(URL_TEMPLATE, hostname);
this.listener = listener; this.listener = listener;
}
public void read() {
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
try {
factory.setFeature("http://xml.org/sax/features/external-general-entities", false); factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
factory.setXIncludeAware(false); factory.setXIncludeAware(false);
factory.setExpandEntityReferences(false); factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder(); builder = factory.newDocumentBuilder();
String statusPage = HttpUtil.executeUrl("GET", String.format(URL_TEMPLATE, hostname), 5000); } catch (ParserConfigurationException e) {
logger.warn("Error initializing StatusFileInterpreter :{}", e.getMessage());
throw new IllegalArgumentException(e);
}
}
public void read() {
try {
String statusPage = HttpUtil.executeUrl("GET", url, 5000);
InputStream inputStream = new ByteArrayInputStream(statusPage.getBytes()); InputStream inputStream = new ByteArrayInputStream(statusPage.getBytes());
Document document = builder.parse(inputStream); Document document = builder.parse(inputStream);
document.getDocumentElement().normalize(); document.getDocumentElement().normalize();
doc = document;
pushDatas();
inputStream.close(); inputStream.close();
} catch (IOException | SAXException | ParserConfigurationException e) { this.doc = Optional.of(document);
pushDatas();
} catch (IOException | SAXException e) {
logger.warn("Unable to read IPX800 status page : {}", e.getMessage()); logger.warn("Unable to read IPX800 status page : {}", e.getMessage());
doc = null;
} }
} }
private void pushDatas() { private void pushDatas() {
Element root = getRoot(); getRoot().ifPresent(root -> {
if (root != null) {
PortDefinition.asStream().forEach(portDefinition -> { PortDefinition.asStream().forEach(portDefinition -> {
List<Node> xmlNodes = getMatchingNodes(root.getChildNodes(), portDefinition.getNodeName()); List<Node> xmlNodes = getMatchingNodes(root.getChildNodes(), portDefinition.getNodeName());
xmlNodes.forEach(xmlNode -> { xmlNodes.forEach(xmlNode -> {
@ -94,40 +100,29 @@ public class StatusFileInterpreter {
listener.dataReceived(String.format("%s%d", portDefinition.getPortName(), portNum), value); listener.dataReceived(String.format("%s%d", portDefinition.getPortName(), portNum), value);
}); });
}); });
} });
} }
public String getElement(StatusEntry entry) { public String getElement(StatusEntry entry) {
Element root = getRoot(); return getRoot().map(root -> root.getElementsByTagName(entry.name().toLowerCase()).item(0).getTextContent())
if (root != null) { .orElse("");
return root.getElementsByTagName(entry.name().toLowerCase()).item(0).getTextContent();
} else {
return "";
}
} }
private List<Node> getMatchingNodes(NodeList nodeList, String criteria) { private List<Node> getMatchingNodes(NodeList nodeList, String criteria) {
return IntStream.range(0, nodeList.getLength()).boxed().map(nodeList::item) return IntStream.range(0, nodeList.getLength()).boxed().map(nodeList::item)
.filter(node -> node.getNodeName().startsWith(criteria)) .filter(node -> node.getNodeName().startsWith(criteria)).sorted(Comparator.comparing(Node::getNodeName))
.sorted(Comparator.comparing(o -> o.getNodeName())).collect(Collectors.toList()); .collect(Collectors.toList());
} }
public int getMaxNumberofNodeType(PortDefinition portDefinition) { public int getMaxNumberofNodeType(PortDefinition portDefinition) {
Element root = getRoot(); return getRoot().map(root -> getMatchingNodes(root.getChildNodes(), portDefinition.getNodeName()).size())
if (root != null) { .orElse(0);
List<Node> filteredNodes = getMatchingNodes(root.getChildNodes(), portDefinition.getNodeName());
return filteredNodes.size();
}
return 0;
} }
private @Nullable Element getRoot() { private Optional<Element> getRoot() {
if (doc == null) { if (doc.isEmpty()) {
read(); read();
} }
if (doc != null) { return Optional.ofNullable(doc.map(Document::getDocumentElement).orElse(null));
return doc.getDocumentElement();
}
return null;
} }
} }