From 1f13d789ec64b4565c9c289d635105b393e8dda8 Mon Sep 17 00:00:00 2001 From: Christoph Weitkamp Date: Sun, 12 Jan 2020 13:25:43 +0100 Subject: [PATCH 1/5] [darksky] Fixed example items in README.md (#6817) - Fixed example items in README.md Fixes #6812 Signed-off-by: Christoph Weitkamp --- bundles/org.openhab.binding.darksky/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.darksky/README.md b/bundles/org.openhab.binding.darksky/README.md index ad433410e47c9..b443e1b2e0451 100644 --- a/bundles/org.openhab.binding.darksky/README.md +++ b/bundles/org.openhab.binding.darksky/README.md @@ -200,7 +200,7 @@ Number:Speed localCurrentSnowIntensity "Current snow intensity [%.2f mm/h]" { channel="darksky:weather-and-forecast:api:local:current#precip-intensity" } Number:Dimensionless localCurrentPrecipitationProbability "Current precipitation probability [%d %unit%]" { channel="darksky:weather-and-forecast:api:local:current#precip-probability" } String localCurrentPrecipitationType "Current precipitation type [%s]" { channel="darksky:weather-and-forecast:api:local:current#precip-type" } -Number localCurrentUVIndex "Current precipitation probability [%d]" { channel="darksky:weather-and-forecast:api:local:current#uvindex" } +Number localCurrentUVIndex "Current UV index [%d]" { channel="darksky:weather-and-forecast:api:local:current#uvindex" } Number:ArealDensity localCurrentOzone "Current ozone [%.1f %unit%]" { channel="darksky:weather-and-forecast:api:local:current#ozone" } DateTime localSunrise "Sunrise [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" { channel="darksky:weather-and-forecast:api:local:current#sunrise" } DateTime localSunset "Sunset [%1$tY-%1$tm-%1$tdT%1$tH:%1$tM:%1$tS]" { channel="darksky:weather-and-forecast:api:local:current#sunset" } @@ -294,8 +294,8 @@ sitemap demo label="Dark Sky" { Image item=localDailyForecastTodayConditionIcon Text item=localDailyForecastTodayMinTemperature Text item=localDailyForecastTodayMaxTemperature - Text item=localDailyForecastTodayMinApprentTemperature - Text item=localDailyForecastTodayMaxApprentTemperature + Text item=localDailyForecastTodayMinApparentTemperature + Text item=localDailyForecastTodayMaxApparentTemperature Text item=localDailyForecastTodayPressure Text item=localDailyForecastTodayHumidity Text item=localDailyForecastTodayWindSpeed From b916ee9e5173cd6b93ea8f746bfa53ccf39e2aa7 Mon Sep 17 00:00:00 2001 From: pali Date: Sun, 12 Jan 2020 14:26:39 +0200 Subject: [PATCH 2/5] [rfxcom] Changed logging level (#6810) Signed-off-by: Pauli Anttila --- .../binding/rfxcom/internal/handler/RFXComBridgeHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/handler/RFXComBridgeHandler.java b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/handler/RFXComBridgeHandler.java index 4668e253ff68d..9b5911463c981 100644 --- a/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/handler/RFXComBridgeHandler.java +++ b/bundles/org.openhab.binding.rfxcom/src/main/java/org/openhab/binding/rfxcom/internal/handler/RFXComBridgeHandler.java @@ -240,7 +240,7 @@ public void packetReceived(byte[] packet) { RFXComInterfaceMessage msg = (RFXComInterfaceMessage) message; if (msg.subType == SubType.RESPONSE) { if (msg.command == Commands.GET_STATUS) { - logger.info("RFXCOM transceiver/receiver type: {}, hw version: {}.{}, fw version: {}", + logger.debug("RFXCOM transceiver/receiver type: {}, hw version: {}.{}, fw version: {}", msg.transceiverType, msg.hardwareVersion1, msg.hardwareVersion2, msg.firmwareVersion); thing.setProperty(Thing.PROPERTY_HARDWARE_VERSION, From 647d4c653e657c80e02691e0550839bde2fdf242 Mon Sep 17 00:00:00 2001 From: pali Date: Sun, 12 Jan 2020 14:27:11 +0200 Subject: [PATCH 3/5] [ftpupload] Changed logging levels (#6811) Signed-off-by: Pauli Anttila --- .../binding/ftpupload/internal/FtpUploadHandlerFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/FtpUploadHandlerFactory.java b/bundles/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/FtpUploadHandlerFactory.java index 302727f8f1d86..c6ebb1a8aa24a 100644 --- a/bundles/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/FtpUploadHandlerFactory.java +++ b/bundles/org.openhab.binding.ftpupload/src/main/java/org/openhab/binding/ftpupload/internal/FtpUploadHandlerFactory.java @@ -118,7 +118,7 @@ protected synchronized void modified(ComponentContext componentContext) { } try { - logger.info("Starting FTP server, port={}, idleTimeout={}", port, idleTimeout); + logger.debug("Starting FTP server, port={}, idleTimeout={}", port, idleTimeout); ftpServer.startServer(port, idleTimeout); } catch (FtpException | FtpServerConfigurationException e) { logger.warn("FTP server starting failed, reason: {}", e.getMessage()); @@ -126,7 +126,7 @@ protected synchronized void modified(ComponentContext componentContext) { } private void stopFtpServer() { - logger.info("Stopping FTP server"); + logger.debug("Stopping FTP server"); ftpServer.stopServer(); } } From c5c2b31755fb6364f7effd02c767080111c6761b Mon Sep 17 00:00:00 2001 From: Andreas Hirsch Date: Sun, 12 Jan 2020 13:44:21 +0100 Subject: [PATCH 4/5] [network] Fix misleading latency values (#6770) * [network] Fix latency calculation (openhab#6741) * Introduce latency extraction from ping output (openhab#6741) * Provide configuration option for latency mode (openhab#6741) Signed-off-by: Andreas Hirsch --- bundles/org.openhab.binding.network/README.md | 1 + .../internal/NetworkBindingConfiguration.java | 30 +++++ .../NetworkBindingConfigurationListener.java | 23 ++++ .../internal/NetworkHandlerFactory.java | 5 + .../network/internal/PresenceDetection.java | 118 ++++++++++-------- .../internal/handler/NetworkHandler.java | 17 +-- .../network/internal/utils/LatencyParser.java | 59 +++++++++ .../network/internal/utils/NetworkUtils.java | 107 +++++++++------- .../network/internal/utils/PingResult.java | 74 +++++++++++ .../resources/ESH-INF/binding/binding.xml | 7 +- .../internal/PresenceDetectionTest.java | 14 ++- .../internal/utils/LatencyParserTest.java | 64 ++++++++++ 12 files changed, 409 insertions(+), 110 deletions(-) create mode 100644 bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfigurationListener.java create mode 100644 bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/LatencyParser.java create mode 100644 bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/PingResult.java create mode 100644 bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/utils/LatencyParserTest.java diff --git a/bundles/org.openhab.binding.network/README.md b/bundles/org.openhab.binding.network/README.md index ad137ec0a7101..24eaedd130231 100644 --- a/bundles/org.openhab.binding.network/README.md +++ b/bundles/org.openhab.binding.network/README.md @@ -13,6 +13,7 @@ The binding has the following configuration options: - **allowDHCPlisten:** If devices leave and reenter a network, they usually request their last IPv4 address by using DHCP requests. By listening for those messages, the status update can be more "real-time" without having to wait for the next refresh cycle. Default is true. - **arpPingToolPath:** If the arp ping tool is not called `arping` and cannot be found in the PATH environment variable, the absolute path can be configured here. Default is `arping`. - **cacheDeviceStateTimeInMS:** The result of a device presence detection is cached for a small amount of time. Set this time here in milliseconds. Be aware that no new pings will be issued within this time frame, even if explicitly requested. Default is 2000. +- **preferResponseTimeAsLatency:** If enabled, an attempt will be made to extract the latency from the output of the ping command. If no such latency value is found in the ping command output, the time to execute the ping command is used as fallback latency. If disabled, the time to execute the ping command is always used as latency value. This is disabled by default to be backwards-compatible and to not break statistics and monitoring which existed before this feature. Create a `/services/network.cfg` file and use the above options like this: diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfiguration.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfiguration.java index f0d356f8ecd38..edb4e82c817c3 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfiguration.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfiguration.java @@ -13,6 +13,8 @@ package org.openhab.binding.network.internal; import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.network.internal.utils.NetworkUtils; @@ -26,19 +28,47 @@ */ @NonNullByDefault public class NetworkBindingConfiguration { + public Boolean allowSystemPings = true; public Boolean allowDHCPlisten = true; public BigDecimal cacheDeviceStateTimeInMS = BigDecimal.valueOf(2000); public String arpPingToolPath = "arping"; public @NonNullByDefault({}) ArpPingUtilEnum arpPingUtilMethod; + // For backwards compatibility reasons, the default is to use the ping method execution time as latency value + public boolean preferResponseTimeAsLatency = false; + + private List listeners = new ArrayList<>(); public void update(NetworkBindingConfiguration newConfiguration) { this.allowSystemPings = newConfiguration.allowSystemPings; this.allowDHCPlisten = newConfiguration.allowDHCPlisten; this.cacheDeviceStateTimeInMS = newConfiguration.cacheDeviceStateTimeInMS; this.arpPingToolPath = newConfiguration.arpPingToolPath; + this.preferResponseTimeAsLatency = newConfiguration.preferResponseTimeAsLatency; NetworkUtils networkUtils = new NetworkUtils(); this.arpPingUtilMethod = networkUtils.determineNativeARPpingMethod(arpPingToolPath); + + notifyListeners(); + } + + public void addNetworkBindingConfigurationListener(NetworkBindingConfigurationListener listener) { + listeners.add(listener); + } + + private void notifyListeners() { + listeners.forEach(NetworkBindingConfigurationListener::bindingConfigurationChanged); + } + + @Override + public String toString() { + return "NetworkBindingConfiguration{" + + "allowSystemPings=" + allowSystemPings + + ", allowDHCPlisten=" + allowDHCPlisten + + ", cacheDeviceStateTimeInMS=" + cacheDeviceStateTimeInMS + + ", arpPingToolPath='" + arpPingToolPath + '\'' + + ", arpPingUtilMethod=" + arpPingUtilMethod + + ", preferResponseTimeAsLatency=" + preferResponseTimeAsLatency + + '}'; } } diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfigurationListener.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfigurationListener.java new file mode 100644 index 0000000000000..bbd5ca6f5130a --- /dev/null +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkBindingConfigurationListener.java @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2010-2020 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.network.internal; + +/** + * Listener for binding configuration changes. + * + * @author Andreas Hirsch - Initial contribution + */ +public interface NetworkBindingConfigurationListener { + + void bindingConfigurationChanged(); +} diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkHandlerFactory.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkHandlerFactory.java index 58ea18e24bb74..dbac3942e6100 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkHandlerFactory.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/NetworkHandlerFactory.java @@ -29,6 +29,8 @@ import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Deactivate; import org.osgi.service.component.annotations.Modified; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The handler factory retrieves the binding configuration and is responsible for creating @@ -41,6 +43,8 @@ public class NetworkHandlerFactory extends BaseThingHandlerFactory { final NetworkBindingConfiguration configuration = new NetworkBindingConfiguration(); + private final Logger logger = LoggerFactory.getLogger(NetworkHandlerFactory.class); + @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { return NetworkBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); @@ -65,6 +69,7 @@ protected void modified(Map config) { // configuration, the values are automatically available in all handlers. Because they all // share the same instance. configuration.update(new Configuration(config).as(NetworkBindingConfiguration.class)); + logger.debug("Updated binding configuration to {}", configuration); } @Override diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java index 3ed5f3fb6a276..847e1988b4f69 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/PresenceDetection.java @@ -12,20 +12,6 @@ */ package org.openhab.binding.network.internal; -import java.io.IOException; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.core.cache.ExpiringCache; @@ -35,9 +21,20 @@ import org.openhab.binding.network.internal.utils.NetworkUtils; import org.openhab.binding.network.internal.utils.NetworkUtils.ArpPingUtilEnum; import org.openhab.binding.network.internal.utils.NetworkUtils.IpPingMethodEnum; +import org.openhab.binding.network.internal.utils.PingResult; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.*; +import java.util.function.Consumer; + /** * The {@link PresenceDetection} handles the connection to the Device * @@ -47,6 +44,7 @@ */ @NonNullByDefault public class PresenceDetection implements IPRequestReceivedCallback { + public static final double NOT_REACHABLE = -1; public static final int DESTINATION_TTL = 300 * 1000; // in ms, 300 s @@ -71,6 +69,8 @@ public class PresenceDetection implements IPRequestReceivedCallback { private @NonNullByDefault({}) ExpiringCache<@Nullable InetAddress> destination; private @Nullable InetAddress cachedDestination = null; + public boolean preferResponseTimeAsLatency; + /// State variables (cannot be final because of test dependency injections) ExpiringCacheAsync cache; private final PresenceDetectionListener updateListener; @@ -148,6 +148,10 @@ public void setTimeout(int timeout) { this.timeoutInMS = timeout; } + public void setPreferResponseTimeAsLatency(boolean preferResponseTimeAsLatency) { + this.preferResponseTimeAsLatency = preferResponseTimeAsLatency; + } + /** * Sets the ping method. This method will perform a feature test. If SYSTEM_PING * does not work on this system, JAVA_PING will be used instead. @@ -339,13 +343,13 @@ public boolean performPresenceDetection(boolean waitForDetectionToFinish) { performARPping(""); checkIfFinished(); }); - } else if (interfaceNames != null) { + } else if (interfaceNames != null) { for (final String interfaceName : interfaceNames) { executorService.execute(() -> { Thread.currentThread().setName("presenceDetectionARP_" + hostname + " " + interfaceName); performARPping(interfaceName); checkIfFinished(); - }); + }); } } @@ -468,15 +472,15 @@ synchronized PresenceDetectionValue updateReachableValue(PresenceDetectionType t protected void performServicePing(int tcpPort) { logger.trace("Perform TCP presence detection for {} on port: {}", hostname, tcpPort); try { - double pingTime = System.nanoTime(); InetAddress destinationAddress = destination.getValue(); - if (destinationAddress != null - && networkUtils.servicePing(destinationAddress.getHostAddress(), tcpPort, timeoutInMS)) { - final double latency = Math.round((System.nanoTime() - pingTime) / 1000000.0f); - PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.TCP_CONNECTION, latency); - v.addReachableTcpService(tcpPort); - updateListener.partialDetectionResult(v); - } + + networkUtils.servicePing(destinationAddress.getHostAddress(), tcpPort, timeoutInMS).ifPresent(o -> { + if(o.isSuccess()) { + PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.TCP_CONNECTION, getLatency(o, preferResponseTimeAsLatency)); + v.addReachableTcpService(tcpPort); + updateListener.partialDetectionResult(v); + } + }); } catch (IOException e) { // This should not happen and might be a user configuration issue, we log a warning message therefore. logger.warn("Could not create a socket connection", e); @@ -502,13 +506,14 @@ protected void performARPping(String interfaceName) { networkUtils.wakeUpIOS(destinationAddress); Thread.sleep(50); } - double pingTime = System.nanoTime(); - if (networkUtils.nativeARPPing(arpPingMethod, arpPingUtilPath, interfaceName, - destinationAddress.getHostAddress(), timeoutInMS)) { - final double latency = Math.round((System.nanoTime() - pingTime) / 1000000.0f); - PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.ARP_PING, latency); - updateListener.partialDetectionResult(v); - } + + networkUtils.nativeARPPing(arpPingMethod, arpPingUtilPath, interfaceName, + destinationAddress.getHostAddress(), timeoutInMS).ifPresent(o -> { + if (o.isSuccess()) { + PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.ARP_PING, getLatency(o, preferResponseTimeAsLatency)); + updateListener.partialDetectionResult(v); + } + }); } catch (IOException e) { logger.trace("Failed to execute an arp ping for ip {}", hostname, e); } catch (InterruptedException ignored) { @@ -523,36 +528,37 @@ protected void performARPping(String interfaceName) { * (http://docs.oracle.com/javase/7/docs/api/java/net/InetAddress.html#isReachable%28int%29) */ protected void performJavaPing() { - try { - logger.trace("Perform java ping presence detection for {}", hostname); - double pingTime = System.nanoTime(); - InetAddress destinationAddress = destination.getValue(); - if (destinationAddress == null) { - return; - } - if (destinationAddress.isReachable(timeoutInMS)) { - final double latency = Math.round((System.nanoTime() - pingTime) / 1000000.0f); - PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.ICMP_PING, latency); + logger.trace("Perform java ping presence detection for {}", hostname); + + InetAddress destinationAddress = destination.getValue(); + if (destinationAddress == null) { + return; + } + + networkUtils.javaPing(timeoutInMS, destinationAddress).ifPresent(o -> { + if (o.isSuccess()) { + PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.ICMP_PING, getLatency(o, preferResponseTimeAsLatency)); updateListener.partialDetectionResult(v); } - } catch (IOException e) { - logger.trace("Failed to execute a java ping for ip {}", hostname, e); - } + }); } protected void performSystemPing() { try { logger.trace("Perform native ping presence detection for {}", hostname); - double pingTime = System.nanoTime(); InetAddress destinationAddress = destination.getValue(); if (destinationAddress == null) { return; } - if (networkUtils.nativePing(pingMethod, destinationAddress.getHostAddress(), timeoutInMS)) { - final double latency = Math.round((System.nanoTime() - pingTime) / 1000000.0f); - PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.ICMP_PING, latency); - updateListener.partialDetectionResult(v); - } + + networkUtils.nativePing(pingMethod, destinationAddress.getHostAddress(), timeoutInMS).ifPresent(o -> { + if (o.isSuccess()) { + PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.ICMP_PING, getLatency(o, preferResponseTimeAsLatency)); + updateListener.partialDetectionResult(v); + } + }); + + } catch (IOException e) { logger.trace("Failed to execute a native ping for ip {}", hostname, e); } catch (InterruptedException e) { @@ -560,6 +566,18 @@ protected void performSystemPing() { } } + private double getLatency(PingResult pingResult, boolean preferResponseTimeAsLatency) { + logger.debug("Getting latency from ping result {} using latency mode {}", pingResult, preferResponseTimeAsLatency); + // Execution time is always set and this value is also the default. So lets use it first. + double latency = pingResult.getExecutionTimeInMS(); + + if (preferResponseTimeAsLatency && pingResult.getResponseTimeInMS().isPresent()) { + latency = pingResult.getResponseTimeInMS().get(); + } + + return latency; + } + @Override public void dhcpRequestReceived(String ipAddress) { PresenceDetectionValue v = updateReachableValue(PresenceDetectionType.DHCP_REQUEST, 0); diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/handler/NetworkHandler.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/handler/NetworkHandler.java index 2302b6ba2ba11..16902d98394ec 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/handler/NetworkHandler.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/handler/NetworkHandler.java @@ -35,12 +35,7 @@ import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.RefreshType; import org.eclipse.smarthome.core.types.UnDefType; -import org.openhab.binding.network.internal.NetworkBindingConfiguration; -import org.openhab.binding.network.internal.NetworkBindingConstants; -import org.openhab.binding.network.internal.NetworkHandlerConfiguration; -import org.openhab.binding.network.internal.PresenceDetection; -import org.openhab.binding.network.internal.PresenceDetectionListener; -import org.openhab.binding.network.internal.PresenceDetectionValue; +import org.openhab.binding.network.internal.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,7 +47,7 @@ * @author David Graeff - Rewritten */ @NonNullByDefault -public class NetworkHandler extends BaseThingHandler implements PresenceDetectionListener { +public class NetworkHandler extends BaseThingHandler implements PresenceDetectionListener, NetworkBindingConfigurationListener { private final Logger logger = LoggerFactory.getLogger(NetworkHandler.class); private @NonNullByDefault({}) PresenceDetection presenceDetection; @@ -72,6 +67,7 @@ public NetworkHandler(Thing thing, boolean isTCPServiceDevice, NetworkBindingCon super(thing); this.isTCPServiceDevice = isTCPServiceDevice; this.configuration = configuration; + this.configuration.addNetworkBindingConfigurationListener(this); } private void refreshValue(ChannelUID channelUID) { @@ -165,6 +161,7 @@ void initialize(PresenceDetection presenceDetection) { this.presenceDetection = presenceDetection; presenceDetection.setHostname(handlerConfiguration.hostname); + presenceDetection.setPreferResponseTimeAsLatency(configuration.preferResponseTimeAsLatency); if (isTCPServiceDevice) { Integer port = handlerConfiguration.port; @@ -218,4 +215,10 @@ public void initialize() { public boolean isTCPServiceDevice() { return isTCPServiceDevice; } + + @Override + public void bindingConfigurationChanged() { + // Make sure that changed binding configuration is reflected + presenceDetection.setPreferResponseTimeAsLatency(configuration.preferResponseTimeAsLatency); + } } diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/LatencyParser.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/LatencyParser.java new file mode 100644 index 0000000000000..70db4dc062523 --- /dev/null +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/LatencyParser.java @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2010-2020 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.network.internal.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Examines output lines of the ping command and tries to extract the contained latency value. + * + * @author Andreas Hirsch - Initial contribution + */ +public class LatencyParser { + + private final Logger logger = LoggerFactory.getLogger(LatencyParser.class); + + // This is how the input looks like on Mac and Linux: + // ping -c 1 192.168.1.1 + // PING 192.168.1.1 (192.168.1.1): 56 data bytes + // 64 bytes from 192.168.1.1: icmp_seq=0 ttl=64 time=1.225 ms + // + // --- 192.168.1.1 ping statistics --- + // 1 packets transmitted, 1 packets received, 0.0% packet loss + // round-trip min/avg/max/stddev = 1.225/1.225/1.225/0.000 ms + + /** + * Examine a single ping command output line and try to extract the latency value if it is contained. + * + * @param inputLine Single output line of the ping command. + * @return Latency value provided by the ping command. Optional is empty if the provided line did not contain a + * latency value which matches the known patterns. + */ + public Optional parseLatency(String inputLine) { + logger.debug("Parsing latency from input {}", inputLine); + + String pattern = ".*time=(.*) ms"; + Matcher m = Pattern.compile(pattern).matcher(inputLine); + if(m.find() && m.groupCount() == 1) { + return Optional.of(Double.parseDouble(m.group(1))); + } + + logger.debug("Did not find a latency value"); + return Optional.empty(); + } +} diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/NetworkUtils.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/NetworkUtils.java index 3c5a10c1fe810..e612d4663e77b 100644 --- a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/NetworkUtils.java +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/NetworkUtils.java @@ -12,28 +12,6 @@ */ package org.openhab.binding.network.internal.utils; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.ConnectException; -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.NetworkInterface; -import java.net.NoRouteToHostException; -import java.net.PortUnreachableException; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.SocketException; -import java.net.SocketTimeoutException; -import java.util.Enumeration; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.stream.Collectors; - import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.apache.commons.net.util.SubnetUtils; @@ -45,6 +23,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.*; +import java.util.*; +import java.util.stream.Collectors; + /** * Network utility functions for pinging and for determining all interfaces and assigned IP addresses. * @@ -54,6 +39,8 @@ public class NetworkUtils { private final Logger logger = LoggerFactory.getLogger(NetworkUtils.class); + private LatencyParser latencyParser = new LatencyParser(); + /** * Gets every IPv4 Address on each Interface except the loopback * The Address format is ip/subnet @@ -75,7 +62,7 @@ public Set getInterfaceNames() { try { // For each interface ... - for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { + for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) { NetworkInterface networkInterface = en.nextElement(); if (!networkInterface.isLoopback()) { result.add(networkInterface.getName()); @@ -101,7 +88,7 @@ public Set getNetworkIPs(int maximumPerInterface) { /** * Takes the interfaceIPs and fetches every IP which can be assigned on their network * - * @param networkIPs The IPs which are assigned to the Network Interfaces + * @param interfaceIPs The IPs which are assigned to the Network Interfaces * @param maximumPerInterface The maximum of IP addresses per interface or 0 to get all. * @return Every single IP which can be assigned on the Networks the computer is connected to */ @@ -150,17 +137,18 @@ public Set getNetworkIPs(Set interfaceIPs, int maximumPerIn * @param host The IP or hostname * @param port The tcp port. Must be not 0. * @param timeout Timeout in ms - * @param logger A slf4j logger instance to log IOException - * @return + * @return Ping result information. Optional is empty if ping command was not executed. * @throws IOException */ - public boolean servicePing(String host, int port, int timeout) throws IOException { + public Optional servicePing(String host, int port, int timeout) throws IOException { + double execStartTimeInMS = System.currentTimeMillis(); + SocketAddress socketAddress = new InetSocketAddress(host, port); try (Socket socket = new Socket()) { socket.connect(socketAddress, timeout); - return true; + return Optional.of(new PingResult(true, System.currentTimeMillis() - execStartTimeInMS)); } catch (ConnectException | SocketTimeoutException | NoRouteToHostException ignored) { - return false; + return Optional.of(new PingResult(false, System.currentTimeMillis() - execStartTimeInMS)); } } @@ -182,7 +170,8 @@ public IpPingMethodEnum determinePingMethod() { } try { - if (nativePing(method, "127.0.0.1", 1000)) { + Optional pingResult = nativePing(method, "127.0.0.1", 1000); + if (pingResult.isPresent() && pingResult.get().isSuccess()) { return method; } } catch (IOException ignored) { @@ -225,14 +214,16 @@ public enum IpPingMethodEnum { * * @param hostname The DNS name, IPv4 or IPv6 address. Must not be null. * @param timeoutInMS Timeout in milliseconds. Be aware that DNS resolution is not part of this timeout. - * @return Returns true if the device responded + * @return Ping result information. Optional is empty if ping command was not executed. * @throws IOException The ping command could probably not be found */ - public boolean nativePing(@Nullable IpPingMethodEnum method, String hostname, int timeoutInMS) + public Optional nativePing(@Nullable IpPingMethodEnum method, String hostname, int timeoutInMS) throws IOException, InterruptedException { + double execStartTimeInMS = System.currentTimeMillis(); + Process proc; if (method == null) { - return false; + return Optional.empty(); } // Yes, all supported operating systems have their own ping utility with a different command line switch (method) { @@ -250,7 +241,7 @@ public boolean nativePing(@Nullable IpPingMethodEnum method, String hostname, in case JAVA_PING: default: // We cannot estimate the command line for any other operating system and just return false - return false; + return Optional.empty(); } // The return code is 0 for a successful ping, 1 if device didn't @@ -258,13 +249,10 @@ public boolean nativePing(@Nullable IpPingMethodEnum method, String hostname, in // not ready. // Exception: return code is also 0 in Windows for all requests on the local subnet. // see https://superuser.com/questions/403905/ping-from-windows-7-get-no-reply-but-sets-errorlevel-to-0 - if (method != IpPingMethodEnum.WINDOWS_PING) { - return proc.waitFor() == 0; - } int result = proc.waitFor(); if (result != 0) { - return false; + return Optional.of(new PingResult(false, System.currentTimeMillis() - execStartTimeInMS)); } try (BufferedReader r = new BufferedReader(new InputStreamReader(proc.getInputStream()))) { @@ -273,13 +261,17 @@ public boolean nativePing(@Nullable IpPingMethodEnum method, String hostname, in throw new IOException("Received no output from ping process."); } do { - if (line.contains("TTL=")) { - return true; + // Because of the Windows issue, we need to check this. We assume that the ping was successful whenever + // this specific string is contained in the output + if (line.contains("TTL=") || line.contains("ttl=")) { + PingResult pingResult = new PingResult(true, System.currentTimeMillis() - execStartTimeInMS); + latencyParser.parseLatency(line).ifPresent(pingResult::setResponseTimeInMS); + return Optional.of(pingResult); } line = r.readLine(); } while (line != null); - return false; + return Optional.of(new PingResult(false, System.currentTimeMillis() - execStartTimeInMS)); } } @@ -302,13 +294,15 @@ public enum ArpPingUtilEnum { * @param interfaceName An interface name, on linux for example "wlp58s0", shown by ifconfig. Must not be null. * @param ipV4address The ipV4 address. Must not be null. * @param timeoutInMS A timeout in milliseconds - * @return Return true if the device responded + * @return Ping result information. Optional is empty if ping command was not executed. * @throws IOException The ping command could probably not be found */ - public boolean nativeARPPing(@Nullable ArpPingUtilEnum arpingTool, @Nullable String arpUtilPath, - String interfaceName, String ipV4address, int timeoutInMS) throws IOException, InterruptedException { + public Optional nativeARPPing(@Nullable ArpPingUtilEnum arpingTool, @Nullable String arpUtilPath, + String interfaceName, String ipV4address, int timeoutInMS) throws IOException, InterruptedException { + double execStartTimeInMS = System.currentTimeMillis(); + if (arpUtilPath == null || arpingTool == null || arpingTool == ArpPingUtilEnum.UNKNOWN_TOOL) { - return false; + return Optional.empty(); } Process proc; if (arpingTool == ArpPingUtilEnum.THOMAS_HABERT_ARPING_WITHOUT_TIMEOUT) { @@ -325,7 +319,28 @@ public boolean nativeARPPing(@Nullable ArpPingUtilEnum arpingTool, @Nullable Str // The return code is 0 for a successful ping. 1 if device didn't respond and 2 if there is another error like // network interface not ready. - return proc.waitFor() == 0; + return Optional.of(new PingResult(proc.waitFor() == 0, System.currentTimeMillis() - execStartTimeInMS)); + } + + /** + * Execute a Java ping. + * + * @param timeoutInMS A timeout in milliseconds + * @param destinationAddress The address to check + * @return Ping result information. Optional is empty if ping command was not executed. + */ + public Optional javaPing(int timeoutInMS, InetAddress destinationAddress) { + double execStartTimeInMS = System.currentTimeMillis(); + + try { + if(destinationAddress.isReachable(timeoutInMS)) { + return Optional.of(new PingResult(true, System.currentTimeMillis() - execStartTimeInMS)); + } else { + return Optional.of(new PingResult(false, System.currentTimeMillis() - execStartTimeInMS)); + } + } catch (IOException e) { + return Optional.of(new PingResult(false, System.currentTimeMillis() - execStartTimeInMS)); + } } /** diff --git a/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/PingResult.java b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/PingResult.java new file mode 100644 index 0000000000000..46169bef2f062 --- /dev/null +++ b/bundles/org.openhab.binding.network/src/main/java/org/openhab/binding/network/internal/utils/PingResult.java @@ -0,0 +1,74 @@ +/** + * Copyright (c) 2010-2020 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.network.internal.utils; + +import java.util.Optional; + +/** + * Information about the ping result. + * + * @author Andreas Hirsch - Initial contribution + */ +public class PingResult { + + private boolean success; + private Double responseTimeInMS; + private double executionTimeInMS; + + /** + * @param success true if the device was reachable, false if not. + * @param executionTimeInMS Execution time of the ping command in ms. + */ + public PingResult(boolean success, double executionTimeInMS) { + this.success = success; + this.executionTimeInMS = executionTimeInMS; + } + + /** + * @return true if the device was reachable, false if not. + */ + public boolean isSuccess() { + return success; + } + + /** + * @return Response time in ms which was returned by the ping command. Optional is empty if response time provided + * by ping command is not available. + */ + public Optional getResponseTimeInMS() { + return responseTimeInMS == null ? Optional.empty() : Optional.of(responseTimeInMS); + } + + /** + * @param responseTimeInMS Response time in ms which was returned by the ping command. + */ + public void setResponseTimeInMS(double responseTimeInMS) { + this.responseTimeInMS = responseTimeInMS; + } + + @Override + public String toString() { + return "PingResult{" + + "success=" + success + + ", responseTimeInMS=" + responseTimeInMS + + ", executionTimeInMS=" + executionTimeInMS + + '}'; + } + + /** + * @return Execution time of the ping command in ms. + */ + public double getExecutionTimeInMS() { + return executionTimeInMS; + } +} diff --git a/bundles/org.openhab.binding.network/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.network/src/main/resources/ESH-INF/binding/binding.xml index 35af3876b9405..89628e8b919d7 100644 --- a/bundles/org.openhab.binding.network/src/main/resources/ESH-INF/binding/binding.xml +++ b/bundles/org.openhab.binding.network/src/main/resources/ESH-INF/binding/binding.xml @@ -29,7 +29,12 @@ arping - If your arp ping tool is not called arping and cannot be found in the PATH environment, you can configure the absolute path / tool name here + If your arp ping tool is not called arping and cannot be found in the PATH environment, you can configure the absolute path / tool name here. + + + false + + If enabled, an attempt will be made to extract the latency from the output of the ping command. If no such latency value is found in the ping command output, the time to execute the ping command is used as fallback latency. If disabled, the time to execute the ping command is always used as latency value. diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java index 4480013b5edd5..0ae4eca25ed7d 100644 --- a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/PresenceDetectionTest.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.net.UnknownHostException; import java.util.Collections; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; @@ -35,6 +36,7 @@ import org.openhab.binding.network.internal.utils.NetworkUtils; import org.openhab.binding.network.internal.utils.NetworkUtils.ArpPingUtilEnum; import org.openhab.binding.network.internal.utils.NetworkUtils.IpPingMethodEnum; +import org.openhab.binding.network.internal.utils.PingResult; /** * Tests cases for {@see PresenceDetectionValue} @@ -113,10 +115,10 @@ public void threadCountTest() { @Test public void partialAndFinalCallbackTests() throws InterruptedException, IOException { - doReturn(true).when(networkUtils).nativePing(eq(IpPingMethodEnum.WINDOWS_PING), anyString(), anyInt()); - doReturn(true).when(networkUtils).nativeARPPing(eq(ArpPingUtilEnum.IPUTILS_ARPING), anyString(), anyString(), + doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).nativePing(eq(IpPingMethodEnum.WINDOWS_PING), anyString(), anyInt()); + doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).nativeARPPing(eq(ArpPingUtilEnum.IPUTILS_ARPING), anyString(), anyString(), any(), anyInt()); - doReturn(true).when(networkUtils).servicePing(anyString(), anyInt(), anyInt()); + doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).servicePing(anyString(), anyInt(), anyInt()); assertTrue(subject.performPresenceDetection(false)); subject.waitForPresenceDetection(); @@ -135,10 +137,10 @@ public void partialAndFinalCallbackTests() throws InterruptedException, IOExcept @Test public void cacheTest() throws InterruptedException, IOException { - doReturn(true).when(networkUtils).nativePing(eq(IpPingMethodEnum.WINDOWS_PING), anyString(), anyInt()); - doReturn(true).when(networkUtils).nativeARPPing(eq(ArpPingUtilEnum.IPUTILS_ARPING), anyString(), anyString(), + doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).nativePing(eq(IpPingMethodEnum.WINDOWS_PING), anyString(), anyInt()); + doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).nativeARPPing(eq(ArpPingUtilEnum.IPUTILS_ARPING), anyString(), anyString(), any(), anyInt()); - doReturn(true).when(networkUtils).servicePing(anyString(), anyInt(), anyInt()); + doReturn(Optional.of(new PingResult(true, 10))).when(networkUtils).servicePing(anyString(), anyInt(), anyInt()); doReturn(executorService).when(subject).getThreadsFor(anyInt()); diff --git a/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/utils/LatencyParserTest.java b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/utils/LatencyParserTest.java new file mode 100644 index 0000000000000..5758c96023ac9 --- /dev/null +++ b/bundles/org.openhab.binding.network/src/test/java/org/openhab/binding/network/internal/utils/LatencyParserTest.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2020 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.network.internal.utils; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Optional; + +/** + * Tests the parser which extracts latency values from the output of the ping command. + * + * @author Andreas Hirsch - Initial contribution + */ +public class LatencyParserTest { + + @Test + public void parseLinuxAndMacResultFoundTest() { + // Arrange + LatencyParser latencyParser = new LatencyParser(); + String input = "64 bytes from 192.168.1.1: icmp_seq=0 ttl=64 time=1.225 ms"; + + // Act + Optional resultLatency = latencyParser.parseLatency(input); + + // Assert + Assert.assertTrue(resultLatency.isPresent()); + Assert.assertEquals(1.225, resultLatency.get(), 0); + } + + @Test + public void parseLinuxAndMacResultNotFoundTest() { + // Arrange + LatencyParser latencyParser = new LatencyParser(); + // This is the output of the command. We exclude the line which contains the latency, because here we want + // to test that no latency is returned for all other lines. + String[] inputLines = { + "ping -c 1 192.168.1.1", + "PING 192.168.1.1 (192.168.1.1): 56 data bytes", + // "64 bytes from 192.168.1.1: icmp_seq=0 ttl=64 time=1.225 ms", + "--- 192.168.1.1 ping statistics ---", + "1 packets transmitted, 1 packets received, 0.0% packet loss", + "round-trip min/avg/max/stddev = 1.225/1.225/1.225/0.000 ms" + }; + + for (String inputLine : inputLines) { + // Act + Optional resultLatency = latencyParser.parseLatency(inputLine); + + // Assert + Assert.assertFalse(resultLatency.isPresent()); + } + } +} From 672a143074ba45806df794d446f239bfb11f7abc Mon Sep 17 00:00:00 2001 From: Kiryl Bahdanau Date: Sun, 12 Jan 2020 15:49:19 +0300 Subject: [PATCH 5/5] [network] Added note for using DHCP with Docker (#6748) Signed-off-by: Kiryl Bahdanau --- bundles/org.openhab.binding.network/README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/bundles/org.openhab.binding.network/README.md b/bundles/org.openhab.binding.network/README.md index 24eaedd130231..c2bddf56234d0 100644 --- a/bundles/org.openhab.binding.network/README.md +++ b/bundles/org.openhab.binding.network/README.md @@ -157,6 +157,18 @@ iptables -A PREROUTING -t mangle -p udp ! -s 127.0.0.1 --dport 67 -j TEE --gatew iptables -A OUTPUT -t nat -p udp -s 127.0.0.1/32 --dport 67 -j DNAT --to 127.0.0.1:6767 ``` +Above iptables solutions to check *dhcp_state* are not working when OpenHAB is started in Docker. Use another workaround + +```shell +iptables -I PREROUTING -t nat -p udp --src 0.0.0.0 --dport 67 -j DNAT --to 0.0.0.0:6767 +``` + +To verify PREROUTING list use below command + +```shell +iptables -L -n -t nat +``` + ## Channels Things support the following channels: