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;
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;
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;
@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
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()))) {
}
}
- 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" : "");
.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
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);
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:
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);
}
}
+ 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);
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));
}
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;
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;
@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() {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
try {
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- // see https://cheatsheetseries.owasp.org/cheatsheets/XML_External_Entity_Prevention_Cheat_Sheet.html
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 -> {
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));
}
}