[bigassfan] Null annotations (#13903)

* Null annotations and some refactoring
* Fix synchronized block
* Fix remaining warnings

Signed-off-by: Leo Siepel <leosiepel@gmail.com>
pull/14168/head
lsiepel 2023-01-05 23:08:07 +01:00 committed by GitHub
parent b91fc94bdb
commit cb460657eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 199 additions and 157 deletions

View File

@ -12,26 +12,29 @@
*/ */
package org.openhab.binding.bigassfan.internal; package org.openhab.binding.bigassfan.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link BigAssFanConfig} is responsible for storing the BigAssFan thing configuration. * The {@link BigAssFanConfig} is responsible for storing the BigAssFan thing configuration.
* *
* @author Mark Hilbush - Initial contribution * @author Mark Hilbush - Initial contribution
*/ */
@NonNullByDefault
public class BigAssFanConfig { public class BigAssFanConfig {
/** /**
* Name of the device * Name of the device
*/ */
private String label; private String label = "";
/** /**
* IP address of the device * IP address of the device
*/ */
private String ipAddress; private String ipAddress = "";
/** /**
* MAC address of the device * MAC address of the device
*/ */
private String macAddress; private String macAddress = "";
public String getLabel() { public String getLabel() {
return label; return label;
@ -58,16 +61,7 @@ public class BigAssFanConfig {
} }
public boolean isValid() { public boolean isValid() {
if (label == null || label.isBlank()) { return !label.isBlank() && !ipAddress.isBlank() && !macAddress.isBlank();
return false;
}
if (ipAddress == null || ipAddress.isBlank()) {
return false;
}
if (macAddress == null || macAddress.isBlank()) {
return false;
}
return true;
} }
@Override @Override

View File

@ -12,41 +12,44 @@
*/ */
package org.openhab.binding.bigassfan.internal.discovery; package org.openhab.binding.bigassfan.internal.discovery;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link BigAssFanDevice} is responsible for storing information about a fan. * The {@link BigAssFanDevice} is responsible for storing information about a fan.
* *
* @author Mark Hilbush - Initial contribution * @author Mark Hilbush - Initial contribution
*/ */
@NonNullByDefault
public class BigAssFanDevice { public class BigAssFanDevice {
/** /**
* Name of device (e.g. Master Bedroom Fan) * Name of device (e.g. Master Bedroom Fan)
*/ */
private String label; private String label = "";
/** /**
* IP address of the device extracted from UDP packet * IP address of the device extracted from UDP packet
*/ */
private String ipAddress; private String ipAddress = "";
/** /**
* MAC address of the device extracted from discovery message * 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) * 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) * Model of device extracted from discovery message (e.g. HSERIES)
*/ */
private String model; private String model = "";
/** /**
* The raw discovery message * The raw discovery message
*/ */
private String discoveryMessage; private String discoveryMessage = "";
public String getLabel() { public String getLabel() {
return label; return label;

View File

@ -27,6 +27,8 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.config.discovery.DiscoveryService;
@ -44,6 +46,7 @@ import org.slf4j.LoggerFactory;
* *
* @author Mark Hilbush - Initial contribution * @author Mark Hilbush - Initial contribution
*/ */
@NonNullByDefault
@Component(service = DiscoveryService.class, configurationPid = "discovery.bigassfan") @Component(service = DiscoveryService.class, configurationPid = "discovery.bigassfan")
public class BigAssFanDiscoveryService extends AbstractDiscoveryService { public class BigAssFanDiscoveryService extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(BigAssFanDiscoveryService.class); 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 // Our own thread pool for the long-running listener job
private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
private ScheduledFuture<?> listenerJob; private @Nullable ScheduledFuture<?> listenerJob;
private @Nullable DiscoveryListener discoveryListener;
DiscoveryListener discoveryListener;
private boolean terminate; private boolean terminate;
private final Pattern announcementPattern = Pattern.compile("[(](.*);DEVICE;ID;(.*);(.*)[)]"); private final Pattern announcementPattern = Pattern.compile("[(](.*);DEVICE;ID;(.*);(.*)[)]");
private Runnable listenerRunnable = () -> { private Runnable listenerRunnable = () -> {
@ -70,9 +70,9 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService {
}; };
// Frequency (in seconds) with which we poll for new devices // Frequency (in seconds) with which we poll for new devices
private final long POLL_FREQ = 300L; private static final long POLL_FREQ = 300L;
private final long POLL_DELAY = 12L; private static final long POLL_DELAY = 12L;
private ScheduledFuture<?> pollJob; private @Nullable ScheduledFuture<?> pollJob;
public BigAssFanDiscoveryService() { public BigAssFanDiscoveryService() {
super(SUPPORTED_THING_TYPES_UIDS, 0, BACKGROUND_DISCOVERY_ENABLED); super(SUPPORTED_THING_TYPES_UIDS, 0, BACKGROUND_DISCOVERY_ENABLED);
@ -84,7 +84,7 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService {
} }
@Override @Override
protected void activate(Map<String, Object> configProperties) { protected void activate(@Nullable Map<String, Object> configProperties) {
super.activate(configProperties); super.activate(configProperties);
logger.trace("BigAssFan discovery service ACTIVATED"); logger.trace("BigAssFan discovery service ACTIVATED");
} }
@ -97,7 +97,7 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService {
@Override @Override
@Modified @Modified
protected void modified(Map<String, Object> configProperties) { protected void modified(@Nullable Map<String, Object> configProperties) {
super.modified(configProperties); super.modified(configProperties);
} }
@ -115,21 +115,22 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService {
cancelListenerJob(); cancelListenerJob();
} }
private void startListenerJob() { private synchronized void startListenerJob() {
if (listenerJob == null) { if (this.listenerJob == null) {
terminate = false;
logger.debug("Starting discovery listener job in {} seconds", BACKGROUND_DISCOVERY_DELAY); 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); TimeUnit.SECONDS);
} }
} }
private void cancelListenerJob() { private void cancelListenerJob() {
if (listenerJob != null) { ScheduledFuture<?> localListenerJob = this.listenerJob;
if (localListenerJob != null) {
logger.debug("Canceling discovery listener job"); logger.debug("Canceling discovery listener job");
listenerJob.cancel(true); localListenerJob.cancel(true);
terminate = true; terminate = true;
listenerJob = null; this.listenerJob = null;
} }
} }
@ -143,9 +144,11 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService {
private synchronized void listen() { private synchronized void listen() {
logger.info("BigAssFan discovery service is running"); logger.info("BigAssFan discovery service is running");
DiscoveryListener localDiscoveryListener;
try { try {
discoveryListener = new DiscoveryListener(); localDiscoveryListener = new DiscoveryListener();
discoveryListener = localDiscoveryListener;
} catch (SocketException se) { } catch (SocketException se) {
logger.warn("Got Socket exception creating multicast socket: {}", se.getMessage(), se); logger.warn("Got Socket exception creating multicast socket: {}", se.getMessage(), se);
return; return;
@ -158,7 +161,7 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService {
while (!terminate) { while (!terminate) {
try { try {
// Wait for a discovery message // Wait for a discovery message
processMessage(discoveryListener.waitForMessage()); processMessage(localDiscoveryListener.waitForMessage());
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException e) {
// Read on socket timed out; check for termination // Read on socket timed out; check for termination
continue; continue;
@ -167,14 +170,11 @@ public class BigAssFanDiscoveryService extends AbstractDiscoveryService {
break; break;
} }
} }
discoveryListener.shutdown(); localDiscoveryListener.shutdown();
logger.debug("DiscoveryListener job is exiting"); logger.debug("DiscoveryListener job is exiting");
} }
private void processMessage(BigAssFanDevice device) { private void processMessage(BigAssFanDevice device) {
if (device == null) {
return;
}
Matcher matcher = announcementPattern.matcher(device.getDiscoveryMessage()); Matcher matcher = announcementPattern.matcher(device.getDiscoveryMessage());
if (matcher.find()) { if (matcher.find()) {
logger.debug("Match: grp1={}, grp2={}, grp(3)={}", matcher.group(1), matcher.group(2), matcher.group(3)); 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()); .withRepresentationProperty(THING_PROPERTY_MAC).withLabel(device.getLabel()).build());
} }
private void schedulePollJob() { private synchronized void schedulePollJob() {
logger.debug("Scheduling discovery poll job to run every {} seconds starting in {} sec", POLL_FREQ, POLL_DELAY);
cancelPollJob(); cancelPollJob();
pollJob = scheduler.scheduleWithFixedDelay(() -> { if (this.pollJob == null) {
try { logger.debug("Scheduling discovery poll job to run every {} seconds starting in {} sec", POLL_FREQ,
discoveryListener.pollForDevices(); POLL_DELAY);
} catch (RuntimeException e) { pollJob = scheduler.scheduleWithFixedDelay(() -> {
logger.warn("Poll job got unexpected exception: {}", e.getMessage(), e); try {
} DiscoveryListener localListener = discoveryListener;
}, POLL_DELAY, POLL_FREQ, TimeUnit.SECONDS); 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() { private void cancelPollJob() {
if (pollJob != null) { ScheduledFuture<?> localPollJob = pollJob;
if (localPollJob != null) {
logger.debug("Canceling poll job"); logger.debug("Canceling poll job");
pollJob.cancel(true); localPollJob.cancel(true);
pollJob = null; this.pollJob = null;
} }
} }
} }

View File

@ -23,6 +23,8 @@ import java.net.SocketTimeoutException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -31,19 +33,23 @@ import org.slf4j.LoggerFactory;
* *
* @author Mark Hilbush - Initial contribution * @author Mark Hilbush - Initial contribution
*/ */
@NonNullByDefault
public class DiscoveryListener { public class DiscoveryListener {
private final Logger logger = LoggerFactory.getLogger(DiscoveryListener.class); private final Logger logger = LoggerFactory.getLogger(DiscoveryListener.class);
private final String BCAST_ADDRESS = "255.255.255.255"; private static final String BCAST_ADDRESS = "255.255.255.255";
private final int SOCKET_RECEIVE_TIMEOUT = 500; private static final int SOCKET_RECEIVE_TIMEOUT = 500;
private static final String POLL_MESSAGE = "<ALL;DEVICE;ID;GET>";
private final String POLL_MESSAGE = "<ALL;DEVICE;ID;GET>";
@Nullable
DatagramSocket dSocket; DatagramSocket dSocket;
@Nullable
DatagramPacket rcvPacket; DatagramPacket rcvPacket;
byte[] rcvBuffer; byte[] rcvBuffer = new byte[0];
@Nullable
InetAddress bcastAddress; InetAddress bcastAddress;
byte[] bcastBuffer; byte[] bcastBuffer = new byte[0];
@Nullable
DatagramPacket bcastPacket; DatagramPacket bcastPacket;
BigAssFanDevice device; BigAssFanDevice device;
@ -54,52 +60,66 @@ public class DiscoveryListener {
device = new BigAssFanDevice(); device = new BigAssFanDevice();
try { try {
// Create a socket on the UDP port and get send & receive buffers // Create a socket on the UDP port and get send & receive buffers
dSocket = new DatagramSocket(BAF_PORT); DatagramSocket localDatagramSocket = new DatagramSocket(BAF_PORT);
dSocket.setSoTimeout(SOCKET_RECEIVE_TIMEOUT); localDatagramSocket.setSoTimeout(SOCKET_RECEIVE_TIMEOUT);
dSocket.setBroadcast(true); localDatagramSocket.setBroadcast(true);
dSocket = localDatagramSocket;
rcvBuffer = new byte[256]; rcvBuffer = new byte[256];
rcvPacket = new DatagramPacket(rcvBuffer, rcvBuffer.length); rcvPacket = new DatagramPacket(rcvBuffer, rcvBuffer.length);
bcastAddress = InetAddress.getByName(BCAST_ADDRESS); bcastAddress = InetAddress.getByName(BCAST_ADDRESS);
bcastBuffer = POLL_MESSAGE.getBytes(StandardCharsets.US_ASCII); bcastBuffer = POLL_MESSAGE.getBytes(StandardCharsets.US_ASCII);
bcastPacket = new DatagramPacket(bcastBuffer, bcastBuffer.length, bcastAddress, BAF_PORT); bcastPacket = new DatagramPacket(bcastBuffer, bcastBuffer.length, bcastAddress, BAF_PORT);
} catch (UnknownHostException uhe) { } catch (UnknownHostException | SocketException | SecurityException e) {
logger.warn("UnknownHostException sending poll request for fans: {}", uhe.getMessage(), uhe); logger.warn("Unexpected exception sending poll request for fans: {}", e.getMessage(), e);
} }
} }
public BigAssFanDevice waitForMessage() throws IOException, SocketTimeoutException { public BigAssFanDevice waitForMessage() throws IOException, SocketTimeoutException {
// Wait to receive a packet // Wait to receive a packet
rcvPacket.setLength(rcvBuffer.length); DatagramPacket localPacket = rcvPacket;
dSocket.receive(rcvPacket); DatagramSocket localDatagramSocket = dSocket;
// Process the received packet if (localPacket != null) {
device.reset(); localPacket.setLength(rcvBuffer.length);
device.setIpAddress(rcvPacket.getAddress().getHostAddress()); }
String message = (new String(rcvBuffer, 0, rcvPacket.getLength()));
device.setDiscoveryMessage(message); if (localDatagramSocket != null && localPacket != null) {
logger.debug("RECEIVED packet of length {} from {}: {}", message.length(), device.getIpAddress(), message); 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; return device;
} }
public void pollForDevices() { public void pollForDevices() {
if (dSocket == null) { DatagramSocket localDatagramSocket = dSocket;
if (localDatagramSocket == null) {
logger.debug("Socket is null in discoveryListener.pollForDevices()"); logger.debug("Socket is null in discoveryListener.pollForDevices()");
return; return;
} }
logger.debug("Sending poll request for fans: {}", POLL_MESSAGE); logger.debug("Sending poll request for fans: {}", POLL_MESSAGE);
try { try {
dSocket.send(bcastPacket); localDatagramSocket.send(bcastPacket);
} catch (IOException ioe) { } catch (IllegalArgumentException | SecurityException | IOException e) {
logger.warn("IOException sending poll request for fans: {}", ioe.getMessage(), ioe); logger.warn("Unexpected exception while sending poll request for fans: {}", e.getMessage(), e);
} }
} }
public void shutdown() { public void shutdown() {
logger.debug("DiscoveryListener closing socket"); logger.debug("DiscoveryListener closing socket");
if (dSocket != null) { DatagramSocket localDatagramSocket = dSocket;
dSocket.close(); if (localDatagramSocket != null) {
localDatagramSocket.close();
dSocket = null; dSocket = null;
} }
} }

View File

@ -23,6 +23,7 @@ import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.BufferOverflowException; import java.nio.BufferOverflowException;
import java.nio.channels.IllegalBlockingModeException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
@ -40,6 +41,8 @@ import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; 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.BigAssFanConfig;
import org.openhab.binding.bigassfan.internal.utils.BigAssFanConverter; import org.openhab.binding.bigassfan.internal.utils.BigAssFanConverter;
import org.openhab.core.common.ThreadPoolManager; import org.openhab.core.common.ThreadPoolManager;
@ -65,6 +68,7 @@ import org.slf4j.LoggerFactory;
* *
* @author Mark Hilbush - Initial contribution * @author Mark Hilbush - Initial contribution
*/ */
@NonNullByDefault
public class BigAssFanHandler extends BaseThingHandler { public class BigAssFanHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(BigAssFanHandler.class); 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 COOLING = new StringType("COOLING");
private static final StringType HEATING = new StringType("HEATING"); private static final StringType HEATING = new StringType("HEATING");
private BigAssFanConfig config; private String label = "";
private String label = null; private String ipAddress = "";
private String ipAddress = null; private String macAddress = "";
private String macAddress = null;
private FanListener fanListener; private final FanListener fanListener;
protected Map<String, State> fanStateMap = Collections.synchronizedMap(new HashMap<>()); protected final Map<String, State> fanStateMap = Collections.synchronizedMap(new HashMap<>());
public BigAssFanHandler(Thing thing, String ipv4Address) { public BigAssFanHandler(Thing thing, @Nullable String ipv4Address) {
super(thing); super(thing);
this.thing = thing; this.thing = thing;
@ -96,18 +99,19 @@ public class BigAssFanHandler extends BaseThingHandler {
public void initialize() { public void initialize() {
logger.debug("BigAssFanHandler for {} is initializing", thing.getUID()); logger.debug("BigAssFanHandler for {} is initializing", thing.getUID());
config = getConfig().as(BigAssFanConfig.class); BigAssFanConfig configuration = getConfig().as(BigAssFanConfig.class);
logger.debug("BigAssFanHandler config for {} is {}", thing.getUID(), config); 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()); logger.debug("BigAssFanHandler config of {} is invalid. Check configuration", thing.getUID());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Invalid BigAssFan config. Check configuration."); "Invalid BigAssFan config. Check configuration.");
return; return;
} }
label = config.getLabel();
ipAddress = config.getIpAddress(); label = configuration.getLabel();
macAddress = config.getMacAddress(); ipAddress = configuration.getIpAddress();
macAddress = configuration.getMacAddress();
fanListener.startFanListener(); fanListener.startFanListener();
} }
@ -312,8 +316,9 @@ public class BigAssFanHandler extends BaseThingHandler {
private void adjustMaxSpeed(PercentType command, String channelId, String commandFragment) { private void adjustMaxSpeed(PercentType command, String channelId, String commandFragment) {
int newMin = command.intValue(); int newMin = command.intValue();
int currentMax = PercentType.ZERO.intValue(); int currentMax = PercentType.ZERO.intValue();
if (fanStateMap.get(channelId) != null) { State fanState = fanStateMap.get(channelId);
currentMax = ((PercentType) fanStateMap.get(channelId)).intValue(); if (fanState != null) {
currentMax = ((PercentType) fanState).intValue();
} }
if (newMin > currentMax) { if (newMin > currentMax) {
updateState(CHANNEL_FAN_SPEED_MAX, command); updateState(CHANNEL_FAN_SPEED_MAX, command);
@ -324,8 +329,9 @@ public class BigAssFanHandler extends BaseThingHandler {
private void adjustMinSpeed(PercentType command, String channelId, String commandFragment) { private void adjustMinSpeed(PercentType command, String channelId, String commandFragment) {
int newMax = command.intValue(); int newMax = command.intValue();
int currentMin = PercentType.HUNDRED.intValue(); int currentMin = PercentType.HUNDRED.intValue();
if (fanStateMap.get(channelId) != null) { State fanSate = fanStateMap.get(channelId);
currentMin = ((PercentType) fanStateMap.get(channelId)).intValue(); if (fanSate != null) {
currentMin = ((PercentType) fanSate).intValue();
} }
if (newMax < currentMin) { if (newMax < currentMin) {
updateState(channelId, command); updateState(channelId, command);
@ -449,8 +455,9 @@ public class BigAssFanHandler extends BaseThingHandler {
private void adjustMaxLevel(PercentType command) { private void adjustMaxLevel(PercentType command) {
int newMin = command.intValue(); int newMin = command.intValue();
int currentMax = PercentType.ZERO.intValue(); int currentMax = PercentType.ZERO.intValue();
if (fanStateMap.get(CHANNEL_LIGHT_LEVEL_MAX) != null) { State fanState = fanStateMap.get(CHANNEL_LIGHT_LEVEL_MAX);
currentMax = ((PercentType) fanStateMap.get(CHANNEL_LIGHT_LEVEL_MAX)).intValue(); if (fanState != null) {
currentMax = ((PercentType) fanState).intValue();
} }
if (newMin > currentMax) { if (newMin > currentMax) {
updateState(CHANNEL_LIGHT_LEVEL_MAX, command); updateState(CHANNEL_LIGHT_LEVEL_MAX, command);
@ -461,8 +468,9 @@ public class BigAssFanHandler extends BaseThingHandler {
private void adjustMinLevel(PercentType command) { private void adjustMinLevel(PercentType command) {
int newMax = command.intValue(); int newMax = command.intValue();
int currentMin = PercentType.HUNDRED.intValue(); int currentMin = PercentType.HUNDRED.intValue();
if (fanStateMap.get(CHANNEL_LIGHT_LEVEL_MIN) != null) { State fanState = fanStateMap.get(CHANNEL_LIGHT_LEVEL_MIN);
currentMin = ((PercentType) fanStateMap.get(CHANNEL_LIGHT_LEVEL_MIN)).intValue(); if (fanState != null) {
currentMin = ((PercentType) fanState).intValue();
} }
if (newMax < currentMin) { if (newMax < currentMin) {
updateState(CHANNEL_LIGHT_LEVEL_MIN, command); updateState(CHANNEL_LIGHT_LEVEL_MIN, command);
@ -483,11 +491,6 @@ public class BigAssFanHandler extends BaseThingHandler {
* Send a command to the fan * Send a command to the fan
*/ */
private void sendCommand(String mac, String commandFragment) { 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(); StringBuilder sb = new StringBuilder();
sb.append("<").append(mac).append(commandFragment).append(">"); sb.append("<").append(mac).append(commandFragment).append(">");
String message = sb.toString(); 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 it's offline with no detail or if it's not offline, mark it offline with detailed status
if ((isOffline() && getDetail() == ThingStatusDetail.NONE) || !isOffline()) { if ((isOffline() && getDetail() == ThingStatusDetail.NONE) || !isOffline()) {
logger.debug("Changing status of {} from {}({}) to OFFLINE({})", thing.getUID(), getStatus(), getDetail(), 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 // Our own thread pool for the long-running listener job
private ScheduledExecutorService scheduledExecutorService = ThreadPoolManager private ScheduledExecutorService scheduledExecutorService = ThreadPoolManager
.getScheduledPool("bigassfanHandler" + "-" + thing.getUID()); .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 boolean terminate;
private final Pattern messagePattern = Pattern.compile("[(](.*)"); 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); conn = new ConnectionManager(ipv4Address);
} }
@ -590,12 +593,14 @@ public class BigAssFanHandler extends BaseThingHandler {
} }
public void stopFanListener() { public void stopFanListener() {
if (listenerJob != null) { ScheduledFuture<?> localListenerJob = listenerJob;
if (localListenerJob != null) {
logger.debug("Stopping listener for {} at {}", thing.getUID(), ipAddress); logger.debug("Stopping listener for {} at {}", thing.getUID(), ipAddress);
terminate = true; terminate = true;
listenerJob.cancel(true); localListenerJob.cancel(true);
listenerJob = null; this.listenerJob = null;
} }
conn.cancelConnectionMonitorJob(); conn.cancelConnectionMonitorJob();
conn.disconnect(); conn.disconnect();
} }
@ -636,7 +641,7 @@ public class BigAssFanHandler extends BaseThingHandler {
logger.debug("Fan listener thread is exiting for {} at {}", thing.getUID(), ipAddress); 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 (!conn.isConnected()) {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("FanListener for {} can't receive message. No connection to fan", thing.getUID()); 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(); return readMessage();
} }
private String readMessage() { private @Nullable String readMessage() {
logger.trace("Waiting for message from {} at {}", thing.getUID(), ipAddress); logger.trace("Waiting for message from {} at {}", thing.getUID(), ipAddress);
String message = conn.read(); String message = conn.read();
if (message != null) { if (message != null) {
@ -660,7 +665,7 @@ public class BigAssFanHandler extends BaseThingHandler {
return message; return message;
} }
private void processMessage(String incomingMessage) { private void processMessage(@Nullable String incomingMessage) {
if (incomingMessage == null || incomingMessage.isEmpty()) { if (incomingMessage == null || incomingMessage.isEmpty()) {
return; return;
} }
@ -739,10 +744,7 @@ public class BigAssFanHandler extends BaseThingHandler {
return true; return true;
} }
// Didn't match MAC address, check match for label // Didn't match MAC address, check match for label
if (label.equalsIgnoreCase(idFromDevice)) { return label.equalsIgnoreCase(idFromDevice);
return true;
}
return false;
} }
private void updateFanPower(String[] messageParts) { private void updateFanPower(String[] messageParts) {
@ -1003,27 +1005,28 @@ public class BigAssFanHandler extends BaseThingHandler {
private boolean deviceIsConnected; private boolean deviceIsConnected;
private InetAddress ifAddress; private @Nullable InetAddress ifAddress;
private Socket fanSocket; private @Nullable Socket fanSocket;
private Scanner fanScanner; private @Nullable Scanner fanScanner;
private DataOutputStream fanWriter; private @Nullable DataOutputStream fanWriter;
private final int SOCKET_CONNECT_TIMEOUT = 1500; private static final int SOCKET_CONNECT_TIMEOUT = 1500;
ScheduledFuture<?> connectionMonitorJob; private @Nullable ScheduledFuture<?> connectionMonitorJob;
private final long CONNECTION_MONITOR_FREQ = 120L; private static final long CONNECTION_MONITOR_FREQ = 120L;
private final long CONNECTION_MONITOR_DELAY = 30L; private static final long CONNECTION_MONITOR_DELAY = 30L;
Runnable connectionMonitorRunnable = () -> { Runnable connectionMonitorRunnable = () -> {
logger.trace("Performing connection check for {} at IP {}", thing.getUID(), ipAddress); logger.trace("Performing connection check for {} at IP {}", thing.getUID(), ipAddress);
checkConnection(); checkConnection();
}; };
public ConnectionManager(String ipv4Address) { public ConnectionManager(@Nullable String ipv4Address) {
deviceIsConnected = false; deviceIsConnected = false;
try { try {
ifAddress = InetAddress.getByName(ipv4Address); 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) { } catch (UnknownHostException e) {
logger.warn("Handler for {} got UnknownHostException getting local IPv4 net interface: {}", logger.warn("Handler for {} got UnknownHostException getting local IPv4 net interface: {}",
thing.getUID(), e.getMessage(), e); thing.getUID(), e.getMessage(), e);
@ -1045,13 +1048,15 @@ public class BigAssFanHandler extends BaseThingHandler {
} }
logger.trace("Connecting to {} at {}", thing.getUID(), ipAddress); logger.trace("Connecting to {} at {}", thing.getUID(), ipAddress);
Socket localFanSocket = new Socket();
fanSocket = localFanSocket;
// Open socket // Open socket
try { try {
fanSocket = new Socket(); localFanSocket.bind(new InetSocketAddress(ifAddress, 0));
fanSocket.bind(new InetSocketAddress(ifAddress, 0)); localFanSocket.connect(new InetSocketAddress(ipAddress, BAF_PORT), SOCKET_CONNECT_TIMEOUT);
fanSocket.connect(new InetSocketAddress(ipAddress, BAF_PORT), SOCKET_CONNECT_TIMEOUT); } catch (SecurityException | IllegalArgumentException | IOException e) {
} catch (IOException e) { logger.debug("Unexpected exception connecting to {} at {}: {}", thing.getUID(), ipAddress,
logger.debug("IOException connecting to {} at {}: {}", thing.getUID(), ipAddress, e.getMessage()); e.getMessage(), e);
markOfflineWithMessage(ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage()); markOfflineWithMessage(ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
disconnect(); disconnect();
return; return;
@ -1059,12 +1064,12 @@ public class BigAssFanHandler extends BaseThingHandler {
// Create streams // Create streams
try { try {
fanWriter = new DataOutputStream(fanSocket.getOutputStream()); fanWriter = new DataOutputStream(localFanSocket.getOutputStream());
fanScanner = new Scanner(fanSocket.getInputStream()); Scanner localFanScanner = new Scanner(localFanSocket.getInputStream());
fanScanner.useDelimiter("[)]"); localFanScanner.useDelimiter("[)]");
} catch (IOException e) { fanScanner = localFanScanner;
logger.warn("IOException getting streams for {} at {}: {}", thing.getUID(), ipAddress, e.getMessage(), } catch (IllegalBlockingModeException | IOException e) {
e); logger.warn("Exception getting streams for {} at {}: {}", thing.getUID(), ipAddress, e.getMessage(), e);
markOfflineWithMessage(ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage()); markOfflineWithMessage(ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, e.getMessage());
disconnect(); disconnect();
return; return;
@ -1081,17 +1086,22 @@ public class BigAssFanHandler extends BaseThingHandler {
logger.debug("Disconnecting from {} at {}", thing.getUID(), ipAddress); logger.debug("Disconnecting from {} at {}", thing.getUID(), ipAddress);
try { try {
if (fanWriter != null) { DataOutputStream localFanWriter = fanWriter;
fanWriter.close(); if (localFanWriter != null) {
localFanWriter.close();
fanWriter = null;
} }
if (fanScanner != null) { Scanner localFanScanner = fanScanner;
fanScanner.close(); if (localFanScanner != null) {
localFanScanner.close();
} }
if (fanSocket != null) { Socket localFanSocket = fanSocket;
fanSocket.close(); if (localFanSocket != null) {
localFanSocket.close();
fanSocket = null;
} }
} catch (IOException e) { } catch (IllegalStateException | IOException e) {
logger.warn("IOException closing connection to {} at {}: {}", thing.getUID(), ipAddress, e.getMessage(), logger.warn("Exception closing connection to {} at {}: {}", thing.getUID(), ipAddress, e.getMessage(),
e); e);
} }
deviceIsConnected = false; deviceIsConnected = false;
@ -1101,15 +1111,18 @@ public class BigAssFanHandler extends BaseThingHandler {
markOffline(); markOffline();
} }
public String read() { public @Nullable String read() {
if (fanScanner == null) { if (fanScanner == null) {
logger.warn("Scanner for {} is null when trying to scan from {}!", thing.getUID(), ipAddress); logger.warn("Scanner for {} is null when trying to scan from {}!", thing.getUID(), ipAddress);
return null; return null;
} }
String nextToken; String nextToken = null;
try { try {
nextToken = fanScanner.next(); Scanner localFanScanner = fanScanner;
if (localFanScanner != null) {
nextToken = localFanScanner.next();
}
} catch (NoSuchElementException e) { } catch (NoSuchElementException e) {
logger.debug("Scanner for {} threw NoSuchElementException; stream possibly closed", thing.getUID()); logger.debug("Scanner for {} threw NoSuchElementException; stream possibly closed", thing.getUID());
// Force a reconnect to the device // Force a reconnect to the device
@ -1126,11 +1139,13 @@ public class BigAssFanHandler extends BaseThingHandler {
} }
public void write(byte[] buffer) throws IOException { 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); logger.warn("fanWriter for {} is null when trying to write to {}!!!", thing.getUID(), ipAddress);
return; return;
} else {
localFanWriter.write(buffer, 0, buffer.length);
} }
fanWriter.write(buffer, 0, buffer.length);
} }
private boolean isConnected() { 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. * Periodically validate the command connection to the device by executing a getversion command.
*/ */
private void scheduleConnectionMonitorJob() { private synchronized void scheduleConnectionMonitorJob() {
if (connectionMonitorJob == null) { if (connectionMonitorJob == null) {
logger.debug("Starting connection monitor job in {} seconds for {} at {}", CONNECTION_MONITOR_DELAY, logger.debug("Starting connection monitor job in {} seconds for {} at {}", CONNECTION_MONITOR_DELAY,
thing.getUID(), ipAddress); thing.getUID(), ipAddress);
@ -1150,9 +1165,10 @@ public class BigAssFanHandler extends BaseThingHandler {
} }
private void cancelConnectionMonitorJob() { private void cancelConnectionMonitorJob() {
if (connectionMonitorJob != null) { ScheduledFuture<?> localConnectionMonitorJob = connectionMonitorJob;
if (localConnectionMonitorJob != null) {
logger.debug("Canceling connection monitor job for {} at {}", thing.getUID(), ipAddress); logger.debug("Canceling connection monitor job for {} at {}", thing.getUID(), ipAddress);
connectionMonitorJob.cancel(true); localConnectionMonitorJob.cancel(true);
connectionMonitorJob = null; connectionMonitorJob = null;
} }
} }

View File

@ -12,6 +12,7 @@
*/ */
package org.openhab.binding.bigassfan.internal.utils; package org.openhab.binding.bigassfan.internal.utils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.library.types.PercentType; import org.openhab.core.library.types.PercentType;
/** /**
@ -21,6 +22,7 @@ import org.openhab.core.library.types.PercentType;
* *
* @author Mark Hilbush - Initial contribution * @author Mark Hilbush - Initial contribution
*/ */
@NonNullByDefault
public class BigAssFanConverter { public class BigAssFanConverter {
/* /*
* Conversion factor for fan range (0-7) to dimmer range (0-100). * Conversion factor for fan range (0-7) to dimmer range (0-100).