@NonNullByDefault
public class NetworkBindingConfiguration {
- public Boolean allowSystemPings = true;
- public Boolean allowDHCPlisten = true;
+ public boolean allowSystemPings = true;
+ public boolean allowDHCPlisten = true;
public BigDecimal cacheDeviceStateTimeInMS = BigDecimal.valueOf(2000);
public String arpPingToolPath = "arping";
- public @NonNullByDefault({}) ArpPingUtilEnum arpPingUtilMethod;
+ public ArpPingUtilEnum arpPingUtilMethod = ArpPingUtilEnum.DISABLED;
// For backwards compatibility reasons, the default is to use the ping method execution time as latency value
public boolean preferResponseTimeAsLatency = false;
*/
package org.openhab.binding.network.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Listener for binding configuration changes.
*
* @author Andreas Hirsch - Initial contribution
*/
+@NonNullByDefault
public interface NetworkBindingConfigurationListener {
void bindingConfigurationChanged();
/// Configuration variables
private boolean useDHCPsniffing = false;
- private String arpPingState = "Disabled";
private String ipPingState = "Disabled";
protected String arpPingUtilPath = "";
- protected ArpPingUtilEnum arpPingMethod = ArpPingUtilEnum.UNKNOWN_TOOL;
+ protected ArpPingUtilEnum arpPingMethod = ArpPingUtilEnum.DISABLED;
protected @Nullable IpPingMethodEnum pingMethod = null;
private boolean iosDevice;
private Set<Integer> tcpPorts = new HashSet<>();
* it will be disabled as well.
*
* @param enable Enable or disable ARP ping
- * @param arpPingUtilPath c
+ * @param destinationAddress target ip address
*/
private void setUseArpPing(boolean enable, @Nullable InetAddress destinationAddress) {
if (!enable || arpPingUtilPath.isEmpty()) {
- arpPingState = "Disabled";
- arpPingMethod = ArpPingUtilEnum.UNKNOWN_TOOL;
- return;
- } else if (destinationAddress == null || !(destinationAddress instanceof Inet4Address)) {
- arpPingState = "Destination is not a valid IPv4 address";
- arpPingMethod = ArpPingUtilEnum.UNKNOWN_TOOL;
- return;
- }
-
- switch (arpPingMethod) {
- case UNKNOWN_TOOL: {
- arpPingState = "Unknown arping tool";
- break;
- }
- case THOMAS_HABERT_ARPING: {
- arpPingState = "Arping tool by Thomas Habets";
- break;
- }
- case THOMAS_HABERT_ARPING_WITHOUT_TIMEOUT: {
- arpPingState = "Arping tool by Thomas Habets (old version)";
- break;
- }
- case ELI_FULKERSON_ARP_PING_FOR_WINDOWS: {
- arpPingState = "Eli Fulkerson ARPing tool for Windows";
- break;
- }
- case IPUTILS_ARPING: {
- arpPingState = "Ipuitls Arping";
- break;
- }
+ arpPingMethod = ArpPingUtilEnum.DISABLED;
+ } else if (!(destinationAddress instanceof Inet4Address)) {
+ arpPingMethod = ArpPingUtilEnum.DISABLED_INVALID_IP;
}
}
}
public String getArpPingState() {
- return arpPingState;
+ return arpPingMethod.description;
}
public String getIPPingState() {
if (pingMethod != null) {
detectionChecks += 1;
}
- if (arpPingMethod != ArpPingUtilEnum.UNKNOWN_TOOL) {
+ if (arpPingMethod.canProceed) {
interfaceNames = networkUtils.getInterfaceNames();
detectionChecks += interfaceNames.size();
}
*/
package org.openhab.binding.network.internal;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
/**
* The {@link SpeedTestConfiguration} is the class used to match the
* thing configuration.
*
* @author Gaël L'hopital - Initial contribution
*/
+@NonNullByDefault
public class SpeedTestConfiguration {
- public Integer refreshInterval = 20;
- public Integer initialDelay = 5;
- public Integer uploadSize = 1000000;
- public Integer maxTimeout = 3;
- private String url;
- private String fileName;
+ public int refreshInterval = 20;
+ public int initialDelay = 5;
+ public int uploadSize = 1000000;
+ public int maxTimeout = 3;
+ private @Nullable String url;
+ private @Nullable String fileName;
- public String getUploadURL() {
- return url + (url.endsWith("/") ? "" : "/");
+ public @Nullable String getUploadURL() {
+ String localUrl = url;
+ if (localUrl != null) {
+ localUrl += localUrl.endsWith("/") ? "" : "/";
+ }
+ return localUrl;
}
- public String getDownloadURL() {
- return getUploadURL() + fileName;
+ public @Nullable String getDownloadURL() {
+ String result = getUploadURL();
+ if (result != null && fileName != null) {
+ result += fileName;
+ }
+ return result;
}
}
package org.openhab.binding.network.internal;
import java.io.IOException;
-import java.net.*;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Stream;
-import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.net.NetUtil;
private final String macAddress;
- @Nullable
- private final String hostname;
-
- @Nullable
- private final Integer port;
+ private final @Nullable String hostname;
+ private final @Nullable Integer port;
private final Consumer<byte[]> magicPacketMacSender;
private final Consumer<byte[]> magicPacketIpSender;
private void sendMagicPacketViaIp(byte[] magicPacket) {
try (DatagramSocket socket = new DatagramSocket()) {
- if (!StringUtils.isEmpty(this.hostname)) {
+ if (hostname != null && !hostname.isBlank()) {
logger.debug("Sending Wake-on-LAN Packet via IP Address");
- SocketAddress socketAddress = new InetSocketAddress(this.hostname,
- Objects.requireNonNullElse(this.port, WOL_UDP_PORT));
+ SocketAddress socketAddress = new InetSocketAddress(hostname,
+ Objects.requireNonNullElse(port, WOL_UDP_PORT));
sendMagicPacketToIp(magicPacket, socket, socketAddress);
} else {
throw new IllegalStateException("Hostname is not set!");
Byte dhcpMessageType = request.getDHCPMessageType();
- if (dhcpMessageType != DHCPPacket.DHCPREQUEST) {
+ if (dhcpMessageType == null || dhcpMessageType != DHCPPacket.DHCPREQUEST) {
return; // skipping non DHCPREQUEST message types
}
}
private synchronized void startSpeedTest() {
- if (speedTestSocket == null) {
+ String url = configuration.getDownloadURL();
+ if (speedTestSocket == null && url != null) {
logger.debug("Network speedtest started");
final SpeedTestSocket socket = new SpeedTestSocket(1500);
speedTestSocket = socket;
updateState(CHANNEL_TEST_START, new DateTimeType());
updateState(CHANNEL_TEST_END, UnDefType.NULL);
updateProgress(new QuantityType<>(0, Units.PERCENT));
- socket.startDownload(configuration.getDownloadURL());
+ socket.startDownload(url);
} else {
logger.info("A speedtest is already in progress, will retry on next refresh");
}
switch (testReport.getSpeedTestMode()) {
case DOWNLOAD:
updateState(CHANNEL_RATE_DOWN, quantity);
- if (speedTestSocket != null && configuration != null) {
+ String url = configuration.getUploadURL();
+ if (speedTestSocket != null && url != null) {
speedTestSocket.startUpload(configuration.getUploadURL(), configuration.uploadSize);
}
break;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
*
* @author Andreas Hirsch - Initial contribution
*/
+@NonNullByDefault
public class LatencyParser {
- private static Pattern LATENCY_PATTERN = Pattern.compile(".*time=(.*) ?ms");
+ private static final Pattern LATENCY_PATTERN = Pattern.compile(".*time=(.*) ?ms");
private final Logger logger = LoggerFactory.getLogger(LatencyParser.class);
// This is how the input looks like on Mac and Linux:
public ArpPingUtilEnum determineNativeARPpingMethod(String arpToolPath) {
String result = ExecUtil.executeCommandLineAndWaitResponse(Duration.ofMillis(100), arpToolPath, "--help");
if (result == null || result.isBlank()) {
- return ArpPingUtilEnum.UNKNOWN_TOOL;
+ return ArpPingUtilEnum.DISABLED_UNKNOWN_TOOL;
} else if (result.contains("Thomas Habets")) {
if (result.matches("(?s)(.*)w sec Specify a timeout(.*)")) {
return ArpPingUtilEnum.THOMAS_HABERT_ARPING;
} else {
return ArpPingUtilEnum.THOMAS_HABERT_ARPING_WITHOUT_TIMEOUT;
}
- } else if (result.contains("-w timeout")) {
+ } else if (result.contains("-w timeout") || result.contains("-w <timeout>")) {
return ArpPingUtilEnum.IPUTILS_ARPING;
} else if (result.contains("Usage: arp-ping.exe")) {
return ArpPingUtilEnum.ELI_FULKERSON_ARP_PING_FOR_WINDOWS;
}
- return ArpPingUtilEnum.UNKNOWN_TOOL;
+ return ArpPingUtilEnum.DISABLED_UNKNOWN_TOOL;
}
public enum IpPingMethodEnum {
}
public enum ArpPingUtilEnum {
- UNKNOWN_TOOL,
- IPUTILS_ARPING,
- THOMAS_HABERT_ARPING,
- THOMAS_HABERT_ARPING_WITHOUT_TIMEOUT,
- ELI_FULKERSON_ARP_PING_FOR_WINDOWS
+ DISABLED("Disabled", false),
+ DISABLED_INVALID_IP("Destination is not a valid IPv4 address", false),
+ DISABLED_UNKNOWN_TOOL("Unknown arping tool", false),
+ IPUTILS_ARPING("Iputils Arping", true),
+ THOMAS_HABERT_ARPING("Arping tool by Thomas Habets", true),
+ THOMAS_HABERT_ARPING_WITHOUT_TIMEOUT("Arping tool by Thomas Habets (old version)", true),
+ ELI_FULKERSON_ARP_PING_FOR_WINDOWS("Eli Fulkerson ARPing tool for Windows", true);
+
+ public final String description;
+ public final boolean canProceed;
+
+ ArpPingUtilEnum(String description, boolean canProceed) {
+ this.description = description;
+ this.canProceed = canProceed;
+ }
}
/**
String interfaceName, String ipV4address, int timeoutInMS) throws IOException, InterruptedException {
double execStartTimeInMS = System.currentTimeMillis();
- if (arpUtilPath == null || arpingTool == null || arpingTool == ArpPingUtilEnum.UNKNOWN_TOOL) {
+ if (arpUtilPath == null || arpingTool == null || !arpingTool.canProceed) {
return Optional.empty();
}
Process proc;
import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Information about the ping result.
*
* @author Andreas Hirsch - Initial contribution
*/
+@NonNullByDefault
public class PingResult {
private boolean success;
- private Double responseTimeInMS;
+ private Optional<Double> responseTimeInMS = Optional.empty();
private double executionTimeInMS;
/**
* by ping command is not available.
*/
public Optional<Double> getResponseTimeInMS() {
- return responseTimeInMS == null ? Optional.empty() : Optional.of(responseTimeInMS);
+ return responseTimeInMS;
}
/**
* @param responseTimeInMS Response time in ms which was returned by the ping command.
*/
public void setResponseTimeInMS(double responseTimeInMS) {
- this.responseTimeInMS = responseTimeInMS;
+ this.responseTimeInMS = Optional.of(responseTimeInMS);
}
@Override
<description>States whether a device is online or offline</description>
<state readOnly="true"></state>
</channel-type>
+
<channel-type id="latency">
<item-type>Number:Time</item-type>
<label>Latency</label>
<description>States the latency time</description>
<state readOnly="true" pattern="%d %unit%"></state>
</channel-type>
+
<channel-type id="lastseen">
<item-type>DateTime</item-type>
<label>Last Seen</label>
<description>States the last seen date/time</description>
+ <category>time</category>
<state readOnly="true"></state>
</channel-type>
</thing:thing-descriptions>
@Test
public void reuseValueTests() throws InterruptedException, IOException {
- final long START_TIME = 1000L;
- when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(START_TIME));
+ final long startTime = 1000L;
+ when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(startTime));
// The PresenceDetectionValue.getLowestLatency() should return the smallest latency
PresenceDetectionValue v = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 20);
assertThat(v.getLowestLatency(), is(19.0));
// Advance in time but not expire the cache (1ms left)
- final long ALMOST_EXPIRE = START_TIME + CACHETIME - 1;
- when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(ALMOST_EXPIRE));
+ final long almostExpire = startTime + CACHETIME - 1;
+ when(subject.cache.getCurrentNanoTime()).thenReturn(TimeUnit.MILLISECONDS.toNanos(almostExpire));
// Updating should reset the expire timer of the cache
v2 = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 28);
assertEquals(v, v2);
assertThat(v2.getLowestLatency(), is(19.0));
assertThat(ExpiringCacheHelper.expireTime(subject.cache),
- is(TimeUnit.MILLISECONDS.toNanos(ALMOST_EXPIRE + CACHETIME)));
+ is(TimeUnit.MILLISECONDS.toNanos(almostExpire + CACHETIME)));
// Cache expire. A new PresenceDetectionValue instance will be returned
when(subject.cache.getCurrentNanoTime())
- .thenReturn(TimeUnit.MILLISECONDS.toNanos(ALMOST_EXPIRE + CACHETIME + CACHETIME + 1));
+ .thenReturn(TimeUnit.MILLISECONDS.toNanos(almostExpire + CACHETIME + CACHETIME + 1));
v2 = subject.updateReachableValue(PresenceDetectionType.ICMP_PING, 25);
assertNotEquals(v, v2);
assertThat(v2.getLowestLatency(), is(25.0));
import java.net.DatagramSocket;
import java.util.Arrays;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
* @author Wouter Born - Initial contribution
*/
@Timeout(value = 10)
+@NonNullByDefault
public class WakeOnLanPacketSenderTest {
private void assertValidMagicPacket(byte[] macBytes, byte[] packet) {
assertThrows(IllegalStateException.class, () -> sendWOLTest(null, 4444));
}
- private void sendWOLTest(String hostname, Integer port) throws InterruptedException, IOException {
+ private void sendWOLTest(@Nullable String hostname, @Nullable Integer port)
+ throws InterruptedException, IOException {
DatagramSocket socket = new DatagramSocket(4444);
byte[] buf = new byte[256];
import java.net.InetAddress;
import java.net.SocketException;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.openhab.binding.network.internal.dhcp.DHCPPacket.BadPacketException;
*
* @author David Graeff - Initial contribution
*/
+@NonNullByDefault
public class DHCPTest {
@Test
public void testService() throws SocketException {
import java.util.function.Consumer;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.openhab.binding.network.internal.toberemoved.cache.ExpiringCacheAsync.ExpiringCacheUpdate;
*
* @author David Graeff - Initial contribution
*/
+@NonNullByDefault
public class ExpiringCacheAsyncTest {
@Test
public void testConstructorWrongCacheTime() {
*/
package org.openhab.binding.network.internal.toberemoved.cache;
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
/**
* Helper class to make the package private cacheUpdater field available for tests.
*
- * @author David Graeff
+ * @author David Graeff - Initial Contribution
*/
+@NonNullByDefault
public class ExpiringCacheHelper {
public static long expireTime(@SuppressWarnings("rawtypes") ExpiringCacheAsync cache) {
return cache.expiresAt;
import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNullByDefault;
import org.junit.jupiter.api.Test;
/**
*
* @author Andreas Hirsch - Initial contribution
*/
+@NonNullByDefault
public class LatencyParserTest {
@Test