[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_LONG_PRESS = "LONG_PRESS";
public static final String EVENT_PULSE = "PULSE";
// Adressable thing
}

View File

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

View File

@ -29,7 +29,6 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
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.config.AnalogInputConfiguration;
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.ThingHandlerService;
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.ChannelTypeUID;
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 @NonNullByDefault({}) Ipx800Configuration configuration;
private @NonNullByDefault({}) Ipx800DeviceConnector connector;
private @NonNullByDefault({}) StatusFileInterpreter statusFile;
private Optional<Ipx800DeviceConnector> connector = Optional.empty();
private Optional<M2MMessageParser> parser = 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 final ZonedDateTime referenceTime;
@ -115,36 +110,37 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
@Override
public void initialize() {
configuration = getConfigAs(Ipx800Configuration.class);
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()) {
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());
ThingBuilder thingBuilder = editThing();
PortDefinition.asStream().forEach(portDefinition -> {
int nbElements = statusFile.getMaxNumberofNodeType(portDefinition);
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());
parser = Optional.of(new M2MMessageParser(connector, this));
updateThing(editThing().withChannels(channels).build());
connector = Optional.of(new Ipx800DeviceConnector(config.hostname, config.portNumber, getThing().getUID()));
parser = Optional.of(new M2MMessageParser(connector.get(), this));
updateStatus(ThingStatus.UNKNOWN);
refreshJob = Optional.of(scheduler.scheduleWithFixedDelay(statusFile::read, 3000, configuration.pullInterval,
TimeUnit.MILLISECONDS));
refreshJob = Optional.of(
scheduler.scheduleWithFixedDelay(statusFile::read, 3000, config.pullInterval, TimeUnit.MILLISECONDS));
connector.start();
connector.get().start();
}
@Override
@ -152,25 +148,15 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
refreshJob.ifPresent(job -> job.cancel(true));
refreshJob = Optional.empty();
if (connector != null) {
connector.destroyAndExit();
}
connector.ifPresent(Ipx800DeviceConnector::dispose);
connector = Optional.empty();
parser = Optional.empty();
portDatas.values().stream().forEach(portData -> {
if (portData != null) {
portData.dispose();
}
});
portDatas.values().stream().forEach(PortData::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) {
Channel newChannel = channelBuilder.build();
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 advancedChannelTypeName = portDefinition.toString()
+ (portDefinition.isAdvanced(portIndex) ? "Advanced" : "");
@ -210,9 +196,12 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
.withLabel("Relay " + ndx).withType(channelType), channels);
break;
}
addIfChannelAbsent(ChannelBuilder.create(new ChannelUID(groupUID, ndx + "-duration"), "Number:Time")
.withType(new ChannelTypeUID(BINDING_ID, CHANNEL_LAST_STATE_DURATION))
.withLabel("Previous state duration " + ndx), channels);
return mainChannelUID;
}
@Override
@ -258,7 +247,7 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
return;
}
logger.debug("About to update port '{}' with data '{}'", port, value);
State state = UnDefType.UNDEF;
State state = UnDefType.NULL;
switch (portDefinition) {
case COUNTER:
state = new DecimalType(value);
@ -268,7 +257,7 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
break;
case ANALOG:
state = new DecimalType(value);
updateState(channelId + PROPERTY_SEPARATOR + CHANNEL_VOLTAGE,
updateIfLinked(channelId + PROPERTY_SEPARATOR + CHANNEL_VOLTAGE,
new QuantityType<>(value * ANALOG_SAMPLING, Units.VOLT));
break;
case CONTACT:
@ -303,9 +292,9 @@ public class Ipx800v3Handler extends BaseThingHandler implements Ipx800EventList
break;
}
updateState(channelId, state);
updateIfLinked(channelId, state);
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));
}
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) {
logger.debug("Triggering event '{}' on channel '{}'", event, channel.getUID());
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);
}
@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) {
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) {
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 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.Ipx800EventListener;
import org.slf4j.Logger;
@ -35,11 +34,11 @@ public class M2MMessageParser {
private final Logger logger = LoggerFactory.getLogger(M2MMessageParser.class);
private final Ipx800DeviceConnector connector;
private final @Nullable Ipx800EventListener listener;
private final Ipx800EventListener listener;
private String expectedResponse = "";
public M2MMessageParser(Ipx800DeviceConnector connector, @Nullable Ipx800EventListener listener) {
public M2MMessageParser(Ipx800DeviceConnector connector, Ipx800EventListener listener) {
this.connector = connector;
this.listener = listener;
connector.setParser(this);
@ -87,10 +86,8 @@ public class M2MMessageParser {
private void setStatus(String port, double value) {
logger.debug("Received {} : {}", port, value);
if (listener != null) {
listener.dataReceived(port, value);
}
}
public void setExpectedResponse(String expectedResponse) {
if (expectedResponse.endsWith("s")) { // GetInputs or GetOutputs
@ -125,10 +122,8 @@ public class M2MMessageParser {
public void errorOccurred(Exception e) {
logger.warn("Error received from connector : {}", e.getMessage());
if (listener != null) {
listener.errorOccurred(e);
}
}
public void resetPLC() {
connector.send("Reset");

View File

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