diff --git a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/BigAssFanConfig.java b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/BigAssFanConfig.java index b4ca71c4d81..0948c5a854a 100644 --- a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/BigAssFanConfig.java +++ b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/BigAssFanConfig.java @@ -12,26 +12,29 @@ */ package org.openhab.binding.bigassfan.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link BigAssFanConfig} is responsible for storing the BigAssFan thing configuration. * * @author Mark Hilbush - Initial contribution */ +@NonNullByDefault public class BigAssFanConfig { /** * Name of the device */ - private String label; + private String label = ""; /** * IP address of the device */ - private String ipAddress; + private String ipAddress = ""; /** * MAC address of the device */ - private String macAddress; + private String macAddress = ""; public String getLabel() { return label; @@ -58,16 +61,7 @@ public class BigAssFanConfig { } public boolean isValid() { - if (label == null || label.isBlank()) { - return false; - } - if (ipAddress == null || ipAddress.isBlank()) { - return false; - } - if (macAddress == null || macAddress.isBlank()) { - return false; - } - return true; + return !label.isBlank() && !ipAddress.isBlank() && !macAddress.isBlank(); } @Override diff --git a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/BigAssFanDevice.java b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/BigAssFanDevice.java index c2a0718f3f3..c477ab88306 100644 --- a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/BigAssFanDevice.java +++ b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/BigAssFanDevice.java @@ -12,41 +12,44 @@ */ package org.openhab.binding.bigassfan.internal.discovery; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link BigAssFanDevice} is responsible for storing information about a fan. * * @author Mark Hilbush - Initial contribution */ +@NonNullByDefault public class BigAssFanDevice { /** * Name of device (e.g. Master Bedroom Fan) */ - private String label; + private String label = ""; /** * IP address of the device extracted from UDP packet */ - private String ipAddress; + private String ipAddress = ""; /** * MAC address of the device extracted from discovery message */ - private String macAddress; + private String macAddress = ""; /** * Type of device extracted from discovery message (e.g. FAN or SWITCH) */ - private String type; + private String type = ""; /** * Model of device extracted from discovery message (e.g. HSERIES) */ - private String model; + private String model = ""; /** * The raw discovery message */ - private String discoveryMessage; + private String discoveryMessage = ""; public String getLabel() { return label; diff --git a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/BigAssFanDiscoveryService.java b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/BigAssFanDiscoveryService.java index 15f17ac0b31..d311c4b31d4 100644 --- a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/BigAssFanDiscoveryService.java +++ b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/BigAssFanDiscoveryService.java @@ -27,6 +27,8 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryService; @@ -44,6 +46,7 @@ import org.slf4j.LoggerFactory; * * @author Mark Hilbush - Initial contribution */ +@NonNullByDefault @Component(service = DiscoveryService.class, configurationPid = "discovery.bigassfan") public class BigAssFanDiscoveryService extends AbstractDiscoveryService { private final Logger logger = LoggerFactory.getLogger(BigAssFanDiscoveryService.class); @@ -53,12 +56,9 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService { // Our own thread pool for the long-running listener job private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); - private ScheduledFuture listenerJob; - - DiscoveryListener discoveryListener; - + private @Nullable ScheduledFuture listenerJob; + private @Nullable DiscoveryListener discoveryListener; private boolean terminate; - private final Pattern announcementPattern = Pattern.compile("[(](.*);DEVICE;ID;(.*);(.*)[)]"); private Runnable listenerRunnable = () -> { @@ -70,9 +70,9 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService { }; // Frequency (in seconds) with which we poll for new devices - private final long POLL_FREQ = 300L; - private final long POLL_DELAY = 12L; - private ScheduledFuture pollJob; + private static final long POLL_FREQ = 300L; + private static final long POLL_DELAY = 12L; + private @Nullable ScheduledFuture pollJob; public BigAssFanDiscoveryService() { super(SUPPORTED_THING_TYPES_UIDS, 0, BACKGROUND_DISCOVERY_ENABLED); @@ -84,7 +84,7 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService { } @Override - protected void activate(Map configProperties) { + protected void activate(@Nullable Map configProperties) { super.activate(configProperties); logger.trace("BigAssFan discovery service ACTIVATED"); } @@ -97,7 +97,7 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService { @Override @Modified - protected void modified(Map configProperties) { + protected void modified(@Nullable Map configProperties) { super.modified(configProperties); } @@ -115,21 +115,22 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService { cancelListenerJob(); } - private void startListenerJob() { - if (listenerJob == null) { - terminate = false; + private synchronized void startListenerJob() { + if (this.listenerJob == null) { logger.debug("Starting discovery listener job in {} seconds", BACKGROUND_DISCOVERY_DELAY); - listenerJob = scheduledExecutorService.schedule(listenerRunnable, BACKGROUND_DISCOVERY_DELAY, + terminate = false; + this.listenerJob = scheduledExecutorService.schedule(listenerRunnable, BACKGROUND_DISCOVERY_DELAY, TimeUnit.SECONDS); } } private void cancelListenerJob() { - if (listenerJob != null) { + ScheduledFuture localListenerJob = this.listenerJob; + if (localListenerJob != null) { logger.debug("Canceling discovery listener job"); - listenerJob.cancel(true); + localListenerJob.cancel(true); terminate = true; - listenerJob = null; + this.listenerJob = null; } } @@ -143,9 +144,11 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService { private synchronized void listen() { logger.info("BigAssFan discovery service is running"); + DiscoveryListener localDiscoveryListener; try { - discoveryListener = new DiscoveryListener(); + localDiscoveryListener = new DiscoveryListener(); + discoveryListener = localDiscoveryListener; } catch (SocketException se) { logger.warn("Got Socket exception creating multicast socket: {}", se.getMessage(), se); return; @@ -158,7 +161,7 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService { while (!terminate) { try { // Wait for a discovery message - processMessage(discoveryListener.waitForMessage()); + processMessage(localDiscoveryListener.waitForMessage()); } catch (SocketTimeoutException e) { // Read on socket timed out; check for termination continue; @@ -167,14 +170,11 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService { break; } } - discoveryListener.shutdown(); + localDiscoveryListener.shutdown(); logger.debug("DiscoveryListener job is exiting"); } private void processMessage(BigAssFanDevice device) { - if (device == null) { - return; - } Matcher matcher = announcementPattern.matcher(device.getDiscoveryMessage()); if (matcher.find()) { logger.debug("Match: grp1={}, grp2={}, grp(3)={}", matcher.group(1), matcher.group(2), matcher.group(3)); @@ -242,23 +242,30 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService { .withRepresentationProperty(THING_PROPERTY_MAC).withLabel(device.getLabel()).build()); } - private void schedulePollJob() { - logger.debug("Scheduling discovery poll job to run every {} seconds starting in {} sec", POLL_FREQ, POLL_DELAY); + private synchronized void schedulePollJob() { cancelPollJob(); - pollJob = scheduler.scheduleWithFixedDelay(() -> { - try { - discoveryListener.pollForDevices(); - } catch (RuntimeException e) { - logger.warn("Poll job got unexpected exception: {}", e.getMessage(), e); - } - }, POLL_DELAY, POLL_FREQ, TimeUnit.SECONDS); + if (this.pollJob == null) { + logger.debug("Scheduling discovery poll job to run every {} seconds starting in {} sec", POLL_FREQ, + POLL_DELAY); + pollJob = scheduler.scheduleWithFixedDelay(() -> { + try { + DiscoveryListener localListener = discoveryListener; + if (localListener != null) { + localListener.pollForDevices(); + } + } catch (RuntimeException e) { + logger.warn("Poll job got unexpected exception: {}", e.getMessage(), e); + } + }, POLL_DELAY, POLL_FREQ, TimeUnit.SECONDS); + } } private void cancelPollJob() { - if (pollJob != null) { + ScheduledFuture localPollJob = pollJob; + if (localPollJob != null) { logger.debug("Canceling poll job"); - pollJob.cancel(true); - pollJob = null; + localPollJob.cancel(true); + this.pollJob = null; } } } diff --git a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/DiscoveryListener.java b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/DiscoveryListener.java index c8c5883b5c0..c990a6c0617 100644 --- a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/DiscoveryListener.java +++ b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/discovery/DiscoveryListener.java @@ -23,6 +23,8 @@ import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,19 +33,23 @@ import org.slf4j.LoggerFactory; * * @author Mark Hilbush - Initial contribution */ +@NonNullByDefault public class DiscoveryListener { private final Logger logger = LoggerFactory.getLogger(DiscoveryListener.class); - private final String BCAST_ADDRESS = "255.255.255.255"; - private final int SOCKET_RECEIVE_TIMEOUT = 500; - - private final String POLL_MESSAGE = ""; + private static final String BCAST_ADDRESS = "255.255.255.255"; + private static final int SOCKET_RECEIVE_TIMEOUT = 500; + private static final String POLL_MESSAGE = ""; + @Nullable DatagramSocket dSocket; + @Nullable DatagramPacket rcvPacket; - byte[] rcvBuffer; + byte[] rcvBuffer = new byte[0]; + @Nullable InetAddress bcastAddress; - byte[] bcastBuffer; + byte[] bcastBuffer = new byte[0]; + @Nullable DatagramPacket bcastPacket; BigAssFanDevice device; @@ -54,52 +60,66 @@ public class DiscoveryListener { device = new BigAssFanDevice(); try { // Create a socket on the UDP port and get send & receive buffers - dSocket = new DatagramSocket(BAF_PORT); - dSocket.setSoTimeout(SOCKET_RECEIVE_TIMEOUT); - dSocket.setBroadcast(true); + DatagramSocket localDatagramSocket = new DatagramSocket(BAF_PORT); + localDatagramSocket.setSoTimeout(SOCKET_RECEIVE_TIMEOUT); + localDatagramSocket.setBroadcast(true); + dSocket = localDatagramSocket; rcvBuffer = new byte[256]; rcvPacket = new DatagramPacket(rcvBuffer, rcvBuffer.length); bcastAddress = InetAddress.getByName(BCAST_ADDRESS); bcastBuffer = POLL_MESSAGE.getBytes(StandardCharsets.US_ASCII); bcastPacket = new DatagramPacket(bcastBuffer, bcastBuffer.length, bcastAddress, BAF_PORT); - } catch (UnknownHostException uhe) { - logger.warn("UnknownHostException sending poll request for fans: {}", uhe.getMessage(), uhe); + } catch (UnknownHostException | SocketException | SecurityException e) { + logger.warn("Unexpected exception sending poll request for fans: {}", e.getMessage(), e); } } public BigAssFanDevice waitForMessage() throws IOException, SocketTimeoutException { // Wait to receive a packet - rcvPacket.setLength(rcvBuffer.length); - dSocket.receive(rcvPacket); + DatagramPacket localPacket = rcvPacket; + DatagramSocket localDatagramSocket = dSocket; - // Process the received packet - device.reset(); - device.setIpAddress(rcvPacket.getAddress().getHostAddress()); - String message = (new String(rcvBuffer, 0, rcvPacket.getLength())); - device.setDiscoveryMessage(message); - logger.debug("RECEIVED packet of length {} from {}: {}", message.length(), device.getIpAddress(), message); + if (localPacket != null) { + localPacket.setLength(rcvBuffer.length); + } + + if (localDatagramSocket != null && localPacket != null) { + localDatagramSocket.receive(localPacket); + + // Process the received packet + device.reset(); + + String address = localPacket.getAddress().getHostAddress(); + device.setIpAddress(address != null ? address : ""); + + String message = (new String(rcvBuffer, 0, localPacket.getLength())); + device.setDiscoveryMessage(message); + logger.debug("RECEIVED packet of length {} from {}: {}", message.length(), device.getIpAddress(), message); + } return device; } public void pollForDevices() { - if (dSocket == null) { + DatagramSocket localDatagramSocket = dSocket; + if (localDatagramSocket == null) { logger.debug("Socket is null in discoveryListener.pollForDevices()"); return; } logger.debug("Sending poll request for fans: {}", POLL_MESSAGE); try { - dSocket.send(bcastPacket); - } catch (IOException ioe) { - logger.warn("IOException sending poll request for fans: {}", ioe.getMessage(), ioe); + localDatagramSocket.send(bcastPacket); + } catch (IllegalArgumentException | SecurityException | IOException e) { + logger.warn("Unexpected exception while sending poll request for fans: {}", e.getMessage(), e); } } public void shutdown() { logger.debug("DiscoveryListener closing socket"); - if (dSocket != null) { - dSocket.close(); + DatagramSocket localDatagramSocket = dSocket; + if (localDatagramSocket != null) { + localDatagramSocket.close(); dSocket = null; } } diff --git a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/handler/BigAssFanHandler.java b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/handler/BigAssFanHandler.java index 31bafe70639..dbb6485f28d 100644 --- a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/handler/BigAssFanHandler.java +++ b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/handler/BigAssFanHandler.java @@ -23,6 +23,7 @@ import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; import java.nio.BufferOverflowException; +import java.nio.channels.IllegalBlockingModeException; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.time.ZoneId; @@ -40,6 +41,8 @@ import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.bigassfan.internal.BigAssFanConfig; import org.openhab.binding.bigassfan.internal.utils.BigAssFanConverter; import org.openhab.core.common.ThreadPoolManager; @@ -65,6 +68,7 @@ import org.slf4j.LoggerFactory; * * @author Mark Hilbush - Initial contribution */ +@NonNullByDefault public class BigAssFanHandler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(BigAssFanHandler.class); @@ -75,16 +79,15 @@ public class BigAssFanHandler extends BaseThingHandler { private static final StringType COOLING = new StringType("COOLING"); private static final StringType HEATING = new StringType("HEATING"); - private BigAssFanConfig config; - private String label = null; - private String ipAddress = null; - private String macAddress = null; + private String label = ""; + private String ipAddress = ""; + private String macAddress = ""; - private FanListener fanListener; + private final FanListener fanListener; - protected Map fanStateMap = Collections.synchronizedMap(new HashMap<>()); + protected final Map fanStateMap = Collections.synchronizedMap(new HashMap<>()); - public BigAssFanHandler(Thing thing, String ipv4Address) { + public BigAssFanHandler(Thing thing, @Nullable String ipv4Address) { super(thing); this.thing = thing; @@ -96,18 +99,19 @@ public class BigAssFanHandler extends BaseThingHandler { public void initialize() { logger.debug("BigAssFanHandler for {} is initializing", thing.getUID()); - config = getConfig().as(BigAssFanConfig.class); - logger.debug("BigAssFanHandler config for {} is {}", thing.getUID(), config); + BigAssFanConfig configuration = getConfig().as(BigAssFanConfig.class); + logger.debug("BigAssFanHandler config for {} is {}", thing.getUID(), configuration); - if (!config.isValid()) { + if (!configuration.isValid()) { logger.debug("BigAssFanHandler config of {} is invalid. Check configuration", thing.getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid BigAssFan config. Check configuration."); return; } - label = config.getLabel(); - ipAddress = config.getIpAddress(); - macAddress = config.getMacAddress(); + + label = configuration.getLabel(); + ipAddress = configuration.getIpAddress(); + macAddress = configuration.getMacAddress(); fanListener.startFanListener(); } @@ -312,8 +316,9 @@ public class BigAssFanHandler extends BaseThingHandler { private void adjustMaxSpeed(PercentType command, String channelId, String commandFragment) { int newMin = command.intValue(); int currentMax = PercentType.ZERO.intValue(); - if (fanStateMap.get(channelId) != null) { - currentMax = ((PercentType) fanStateMap.get(channelId)).intValue(); + State fanState = fanStateMap.get(channelId); + if (fanState != null) { + currentMax = ((PercentType) fanState).intValue(); } if (newMin > currentMax) { updateState(CHANNEL_FAN_SPEED_MAX, command); @@ -324,8 +329,9 @@ public class BigAssFanHandler extends BaseThingHandler { private void adjustMinSpeed(PercentType command, String channelId, String commandFragment) { int newMax = command.intValue(); int currentMin = PercentType.HUNDRED.intValue(); - if (fanStateMap.get(channelId) != null) { - currentMin = ((PercentType) fanStateMap.get(channelId)).intValue(); + State fanSate = fanStateMap.get(channelId); + if (fanSate != null) { + currentMin = ((PercentType) fanSate).intValue(); } if (newMax < currentMin) { updateState(channelId, command); @@ -449,8 +455,9 @@ public class BigAssFanHandler extends BaseThingHandler { private void adjustMaxLevel(PercentType command) { int newMin = command.intValue(); int currentMax = PercentType.ZERO.intValue(); - if (fanStateMap.get(CHANNEL_LIGHT_LEVEL_MAX) != null) { - currentMax = ((PercentType) fanStateMap.get(CHANNEL_LIGHT_LEVEL_MAX)).intValue(); + State fanState = fanStateMap.get(CHANNEL_LIGHT_LEVEL_MAX); + if (fanState != null) { + currentMax = ((PercentType) fanState).intValue(); } if (newMin > currentMax) { updateState(CHANNEL_LIGHT_LEVEL_MAX, command); @@ -461,8 +468,9 @@ public class BigAssFanHandler extends BaseThingHandler { private void adjustMinLevel(PercentType command) { int newMax = command.intValue(); int currentMin = PercentType.HUNDRED.intValue(); - if (fanStateMap.get(CHANNEL_LIGHT_LEVEL_MIN) != null) { - currentMin = ((PercentType) fanStateMap.get(CHANNEL_LIGHT_LEVEL_MIN)).intValue(); + State fanState = fanStateMap.get(CHANNEL_LIGHT_LEVEL_MIN); + if (fanState != null) { + currentMin = ((PercentType) fanState).intValue(); } if (newMax < currentMin) { updateState(CHANNEL_LIGHT_LEVEL_MIN, command); @@ -483,11 +491,6 @@ public class BigAssFanHandler extends BaseThingHandler { * Send a command to the fan */ private void sendCommand(String mac, String commandFragment) { - if (fanListener == null) { - logger.error("Unable to send message to {} because fanListener object is null!", thing.getUID()); - return; - } - StringBuilder sb = new StringBuilder(); sb.append("<").append(mac).append(commandFragment).append(">"); String message = sb.toString(); @@ -519,7 +522,7 @@ public class BigAssFanHandler extends BaseThingHandler { } } - private void markOfflineWithMessage(ThingStatusDetail statusDetail, String statusMessage) { + private void markOfflineWithMessage(ThingStatusDetail statusDetail, @Nullable String statusMessage) { // If it's offline with no detail or if it's not offline, mark it offline with detailed status if ((isOffline() && getDetail() == ThingStatusDetail.NONE) || !isOffline()) { logger.debug("Changing status of {} from {}({}) to OFFLINE({})", thing.getUID(), getStatus(), getDetail(), @@ -556,9 +559,9 @@ public class BigAssFanHandler extends BaseThingHandler { // Our own thread pool for the long-running listener job private ScheduledExecutorService scheduledExecutorService = ThreadPoolManager .getScheduledPool("bigassfanHandler" + "-" + thing.getUID()); - private ScheduledFuture listenerJob; + private @Nullable ScheduledFuture listenerJob; - private final long FAN_LISTENER_DELAY = 2L; + private static final long FAN_LISTENER_DELAY = 2L; private boolean terminate; private final Pattern messagePattern = Pattern.compile("[(](.*)"); @@ -573,7 +576,7 @@ public class BigAssFanHandler extends BaseThingHandler { } }; - public FanListener(String ipv4Address) { + public FanListener(@Nullable String ipv4Address) { conn = new ConnectionManager(ipv4Address); } @@ -590,12 +593,14 @@ public class BigAssFanHandler extends BaseThingHandler { } public void stopFanListener() { - if (listenerJob != null) { + ScheduledFuture localListenerJob = listenerJob; + if (localListenerJob != null) { logger.debug("Stopping listener for {} at {}", thing.getUID(), ipAddress); terminate = true; - listenerJob.cancel(true); - listenerJob = null; + localListenerJob.cancel(true); + this.listenerJob = null; } + conn.cancelConnectionMonitorJob(); conn.disconnect(); } @@ -636,7 +641,7 @@ public class BigAssFanHandler extends BaseThingHandler { logger.debug("Fan listener thread is exiting for {} at {}", thing.getUID(), ipAddress); } - private String waitForMessage() throws IOException { + private @Nullable String waitForMessage() throws IOException { if (!conn.isConnected()) { if (logger.isTraceEnabled()) { logger.trace("FanListener for {} can't receive message. No connection to fan", thing.getUID()); @@ -650,7 +655,7 @@ public class BigAssFanHandler extends BaseThingHandler { return readMessage(); } - private String readMessage() { + private @Nullable String readMessage() { logger.trace("Waiting for message from {} at {}", thing.getUID(), ipAddress); String message = conn.read(); if (message != null) { @@ -660,7 +665,7 @@ public class BigAssFanHandler extends BaseThingHandler { return message; } - private void processMessage(String incomingMessage) { + private void processMessage(@Nullable String incomingMessage) { if (incomingMessage == null || incomingMessage.isEmpty()) { return; } @@ -739,10 +744,7 @@ public class BigAssFanHandler extends BaseThingHandler { return true; } // Didn't match MAC address, check match for label - if (label.equalsIgnoreCase(idFromDevice)) { - return true; - } - return false; + return label.equalsIgnoreCase(idFromDevice); } private void updateFanPower(String[] messageParts) { @@ -1003,27 +1005,28 @@ public class BigAssFanHandler extends BaseThingHandler { private boolean deviceIsConnected; - private InetAddress ifAddress; - private Socket fanSocket; - private Scanner fanScanner; - private DataOutputStream fanWriter; - private final int SOCKET_CONNECT_TIMEOUT = 1500; + private @Nullable InetAddress ifAddress; + private @Nullable Socket fanSocket; + private @Nullable Scanner fanScanner; + private @Nullable DataOutputStream fanWriter; + private static final int SOCKET_CONNECT_TIMEOUT = 1500; - ScheduledFuture connectionMonitorJob; - private final long CONNECTION_MONITOR_FREQ = 120L; - private final long CONNECTION_MONITOR_DELAY = 30L; + private @Nullable ScheduledFuture connectionMonitorJob; + private static final long CONNECTION_MONITOR_FREQ = 120L; + private static final long CONNECTION_MONITOR_DELAY = 30L; Runnable connectionMonitorRunnable = () -> { logger.trace("Performing connection check for {} at IP {}", thing.getUID(), ipAddress); checkConnection(); }; - public ConnectionManager(String ipv4Address) { + public ConnectionManager(@Nullable String ipv4Address) { deviceIsConnected = false; try { ifAddress = InetAddress.getByName(ipv4Address); - logger.debug("Handler for {} using address {} on network interface {}", thing.getUID(), - ifAddress.getHostAddress(), NetworkInterface.getByInetAddress(ifAddress).getName()); + + logger.debug("Handler for {} using address {} on network interface {}", thing.getUID(), ipv4Address, + NetworkInterface.getByInetAddress(ifAddress).getName()); } catch (UnknownHostException e) { logger.warn("Handler for {} got UnknownHostException getting local IPv4 net interface: {}", thing.getUID(), e.getMessage(), e); @@ -1045,13 +1048,15 @@ public class BigAssFanHandler extends BaseThingHandler { } logger.trace("Connecting to {} at {}", thing.getUID(), ipAddress); + Socket localFanSocket = new Socket(); + fanSocket = localFanSocket; // Open socket try { - fanSocket = new Socket(); - fanSocket.bind(new InetSocketAddress(ifAddress, 0)); - fanSocket.connect(new InetSocketAddress(ipAddress, BAF_PORT), SOCKET_CONNECT_TIMEOUT); - } catch (IOException e) { - logger.debug("IOException connecting to {} at {}: {}", thing.getUID(), ipAddress, e.getMessage()); + localFanSocket.bind(new InetSocketAddress(ifAddress, 0)); + localFanSocket.connect(new InetSocketAddress(ipAddress, BAF_PORT), SOCKET_CONNECT_TIMEOUT); + } catch (SecurityException | IllegalArgumentException | IOException e) { + logger.debug("Unexpected exception connecting to {} at {}: {}", thing.getUID(), ipAddress, + e.getMessage(), e); markOfflineWithMessage(ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage()); disconnect(); return; @@ -1059,12 +1064,12 @@ public class BigAssFanHandler extends BaseThingHandler { // Create streams try { - fanWriter = new DataOutputStream(fanSocket.getOutputStream()); - fanScanner = new Scanner(fanSocket.getInputStream()); - fanScanner.useDelimiter("[)]"); - } catch (IOException e) { - logger.warn("IOException getting streams for {} at {}: {}", thing.getUID(), ipAddress, e.getMessage(), - e); + fanWriter = new DataOutputStream(localFanSocket.getOutputStream()); + Scanner localFanScanner = new Scanner(localFanSocket.getInputStream()); + localFanScanner.useDelimiter("[)]"); + fanScanner = localFanScanner; + } catch (IllegalBlockingModeException | IOException e) { + logger.warn("Exception getting streams for {} at {}: {}", thing.getUID(), ipAddress, e.getMessage(), e); markOfflineWithMessage(ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage()); disconnect(); return; @@ -1081,17 +1086,22 @@ public class BigAssFanHandler extends BaseThingHandler { logger.debug("Disconnecting from {} at {}", thing.getUID(), ipAddress); try { - if (fanWriter != null) { - fanWriter.close(); + DataOutputStream localFanWriter = fanWriter; + if (localFanWriter != null) { + localFanWriter.close(); + fanWriter = null; } - if (fanScanner != null) { - fanScanner.close(); + Scanner localFanScanner = fanScanner; + if (localFanScanner != null) { + localFanScanner.close(); } - if (fanSocket != null) { - fanSocket.close(); + Socket localFanSocket = fanSocket; + if (localFanSocket != null) { + localFanSocket.close(); + fanSocket = null; } - } catch (IOException e) { - logger.warn("IOException closing connection to {} at {}: {}", thing.getUID(), ipAddress, e.getMessage(), + } catch (IllegalStateException | IOException e) { + logger.warn("Exception closing connection to {} at {}: {}", thing.getUID(), ipAddress, e.getMessage(), e); } deviceIsConnected = false; @@ -1101,15 +1111,18 @@ public class BigAssFanHandler extends BaseThingHandler { markOffline(); } - public String read() { + public @Nullable String read() { if (fanScanner == null) { logger.warn("Scanner for {} is null when trying to scan from {}!", thing.getUID(), ipAddress); return null; } - String nextToken; + String nextToken = null; try { - nextToken = fanScanner.next(); + Scanner localFanScanner = fanScanner; + if (localFanScanner != null) { + nextToken = localFanScanner.next(); + } } catch (NoSuchElementException e) { logger.debug("Scanner for {} threw NoSuchElementException; stream possibly closed", thing.getUID()); // Force a reconnect to the device @@ -1126,11 +1139,13 @@ public class BigAssFanHandler extends BaseThingHandler { } public void write(byte[] buffer) throws IOException { - if (fanWriter == null) { + DataOutputStream localFanWriter = fanWriter; + if (localFanWriter == null) { logger.warn("fanWriter for {} is null when trying to write to {}!!!", thing.getUID(), ipAddress); return; + } else { + localFanWriter.write(buffer, 0, buffer.length); } - fanWriter.write(buffer, 0, buffer.length); } private boolean isConnected() { @@ -1140,7 +1155,7 @@ public class BigAssFanHandler extends BaseThingHandler { /* * Periodically validate the command connection to the device by executing a getversion command. */ - private void scheduleConnectionMonitorJob() { + private synchronized void scheduleConnectionMonitorJob() { if (connectionMonitorJob == null) { logger.debug("Starting connection monitor job in {} seconds for {} at {}", CONNECTION_MONITOR_DELAY, thing.getUID(), ipAddress); @@ -1150,9 +1165,10 @@ public class BigAssFanHandler extends BaseThingHandler { } private void cancelConnectionMonitorJob() { - if (connectionMonitorJob != null) { + ScheduledFuture localConnectionMonitorJob = connectionMonitorJob; + if (localConnectionMonitorJob != null) { logger.debug("Canceling connection monitor job for {} at {}", thing.getUID(), ipAddress); - connectionMonitorJob.cancel(true); + localConnectionMonitorJob.cancel(true); connectionMonitorJob = null; } } diff --git a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/utils/BigAssFanConverter.java b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/utils/BigAssFanConverter.java index a79ab81c296..2266ddae58f 100644 --- a/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/utils/BigAssFanConverter.java +++ b/bundles/org.openhab.binding.bigassfan/src/main/java/org/openhab/binding/bigassfan/internal/utils/BigAssFanConverter.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.bigassfan.internal.utils; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.library.types.PercentType; /** @@ -21,6 +22,7 @@ import org.openhab.core.library.types.PercentType; * * @author Mark Hilbush - Initial contribution */ +@NonNullByDefault public class BigAssFanConverter { /* * Conversion factor for fan range (0-7) to dimmer range (0-100).