[novafinedust] Optimizations on access to the serial port (#10005)
* [novafinedust] Test for optimizations on access to the serial port - retry logic if port does not yet exist on startup - do not write sleep command on shutdown if port has issues - no not register data listener on port but wait for data instead to be compatible with RFC2217 serial over network implementation - ignore all buffered data from device during initialization to get the device into a defined state * Adress review comments - moved most "normal" logging to TRACE level - used lambda function * Improve error messages as requested in the review Signed-off-by: Stefan Triller <github@stefantriller.de>
This commit is contained in:
parent
40dbd50943
commit
00d2aabcb5
|
@ -52,6 +52,7 @@ import org.slf4j.LoggerFactory;
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SDS011Handler extends BaseThingHandler {
|
public class SDS011Handler extends BaseThingHandler {
|
||||||
private static final Duration CONNECTION_MONITOR_START_DELAY_OFFSET = Duration.ofSeconds(10);
|
private static final Duration CONNECTION_MONITOR_START_DELAY_OFFSET = Duration.ofSeconds(10);
|
||||||
|
private static final Duration RETRY_INIT_DELAY = Duration.ofSeconds(10);
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(SDS011Handler.class);
|
private final Logger logger = LoggerFactory.getLogger(SDS011Handler.class);
|
||||||
private final SerialPortManager serialPortManager;
|
private final SerialPortManager serialPortManager;
|
||||||
|
@ -59,8 +60,9 @@ public class SDS011Handler extends BaseThingHandler {
|
||||||
private NovaFineDustConfiguration config = new NovaFineDustConfiguration();
|
private NovaFineDustConfiguration config = new NovaFineDustConfiguration();
|
||||||
private @Nullable SDS011Communicator communicator;
|
private @Nullable SDS011Communicator communicator;
|
||||||
|
|
||||||
private @Nullable ScheduledFuture<?> pollingJob;
|
private @Nullable ScheduledFuture<?> dataReadJob;
|
||||||
private @Nullable ScheduledFuture<?> connectionMonitor;
|
private @Nullable ScheduledFuture<?> connectionMonitor;
|
||||||
|
private @Nullable ScheduledFuture<?> retryInitJob;
|
||||||
|
|
||||||
private ZonedDateTime lastCommunication = ZonedDateTime.now();
|
private ZonedDateTime lastCommunication = ZonedDateTime.now();
|
||||||
|
|
||||||
|
@ -100,14 +102,16 @@ public class SDS011Handler extends BaseThingHandler {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse ports and if the port is found, initialize the reader
|
// parse port and if the port is found, initialize the reader
|
||||||
SerialPortIdentifier portId = serialPortManager.getIdentifier(config.port);
|
SerialPortIdentifier portId = serialPortManager.getIdentifier(config.port);
|
||||||
if (portId == null) {
|
if (portId == null) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port is not known!");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port is not known!");
|
||||||
|
logger.debug("Serial port {} was not found, retrying in {}.", config.port, RETRY_INIT_DELAY);
|
||||||
|
retryInitJob = scheduler.schedule(this::initialize, RETRY_INIT_DELAY.getSeconds(), TimeUnit.SECONDS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.communicator = new SDS011Communicator(this, portId);
|
this.communicator = new SDS011Communicator(this, portId, scheduler);
|
||||||
|
|
||||||
if (config.reporting) {
|
if (config.reporting) {
|
||||||
timeBetweenDataShouldArrive = Duration.ofMinutes(config.reportingInterval);
|
timeBetweenDataShouldArrive = Duration.ofMinutes(config.reportingInterval);
|
||||||
|
@ -116,37 +120,24 @@ public class SDS011Handler extends BaseThingHandler {
|
||||||
timeBetweenDataShouldArrive = Duration.ofSeconds(config.pollingInterval);
|
timeBetweenDataShouldArrive = Duration.ofSeconds(config.pollingInterval);
|
||||||
scheduler.submit(() -> initializeCommunicator(WorkMode.POLLING, timeBetweenDataShouldArrive));
|
scheduler.submit(() -> initializeCommunicator(WorkMode.POLLING, timeBetweenDataShouldArrive));
|
||||||
}
|
}
|
||||||
|
|
||||||
Duration connectionMonitorStartDelay = timeBetweenDataShouldArrive.plus(CONNECTION_MONITOR_START_DELAY_OFFSET);
|
|
||||||
connectionMonitor = scheduler.scheduleWithFixedDelay(this::verifyIfStillConnected,
|
|
||||||
connectionMonitorStartDelay.getSeconds(), timeBetweenDataShouldArrive.getSeconds(), TimeUnit.SECONDS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeCommunicator(WorkMode mode, Duration interval) {
|
private void initializeCommunicator(WorkMode mode, Duration interval) {
|
||||||
SDS011Communicator localCommunicator = communicator;
|
SDS011Communicator localCommunicator = communicator;
|
||||||
if (localCommunicator == null) {
|
if (localCommunicator == null) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||||
"Could not create communicator instance");
|
"Communicator instance is null in initializeCommunicator()");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean initSuccessful = false;
|
boolean initSuccessful = false;
|
||||||
try {
|
int retryInit = 3;
|
||||||
initSuccessful = localCommunicator.initialize(mode, interval);
|
int retryCount = 0;
|
||||||
} catch (final IOException ex) {
|
// sometimes the device is a little difficult and needs multiple configuration attempts
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "I/O error!");
|
while (!initSuccessful && retryCount < retryInit) {
|
||||||
return;
|
logger.trace("Trying to initialize device attempt={}", retryCount);
|
||||||
} catch (PortInUseException e) {
|
initSuccessful = doInit(localCommunicator, mode, interval);
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Port is in use!");
|
retryCount++;
|
||||||
return;
|
|
||||||
} catch (TooManyListenersException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
|
||||||
"Cannot attach listener to port!");
|
|
||||||
return;
|
|
||||||
} catch (UnsupportedCommOperationException e) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
|
||||||
"Cannot set serial port parameters");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initSuccessful) {
|
if (initSuccessful) {
|
||||||
|
@ -154,7 +145,7 @@ public class SDS011Handler extends BaseThingHandler {
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
|
||||||
if (mode == WorkMode.POLLING) {
|
if (mode == WorkMode.POLLING) {
|
||||||
pollingJob = scheduler.scheduleWithFixedDelay(() -> {
|
dataReadJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||||
try {
|
try {
|
||||||
localCommunicator.requestSensorData();
|
localCommunicator.requestSensorData();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -162,15 +153,57 @@ public class SDS011Handler extends BaseThingHandler {
|
||||||
"Cannot query data from device");
|
"Cannot query data from device");
|
||||||
}
|
}
|
||||||
}, 2, config.pollingInterval, TimeUnit.SECONDS);
|
}, 2, config.pollingInterval, TimeUnit.SECONDS);
|
||||||
|
} else {
|
||||||
|
// start a job that reads the port until data arrives
|
||||||
|
int reportingReadStartDelay = 10;
|
||||||
|
int startReadBeforeDataArrives = 5;
|
||||||
|
long readReportedDataInterval = (config.reportingInterval * 60) - reportingReadStartDelay
|
||||||
|
- startReadBeforeDataArrives;
|
||||||
|
logger.trace("Scheduling job to receive reported values");
|
||||||
|
dataReadJob = scheduler.scheduleWithFixedDelay(() -> {
|
||||||
|
try {
|
||||||
|
localCommunicator.readSensorData();
|
||||||
|
} catch (IOException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||||
|
"Cannot query data from device, because: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}, reportingReadStartDelay, readReportedDataInterval, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Duration connectionMonitorStartDelay = timeBetweenDataShouldArrive
|
||||||
|
.plus(CONNECTION_MONITOR_START_DELAY_OFFSET);
|
||||||
|
connectionMonitor = scheduler.scheduleWithFixedDelay(this::verifyIfStillConnected,
|
||||||
|
connectionMonitorStartDelay.getSeconds(), timeBetweenDataShouldArrive.getSeconds(),
|
||||||
|
TimeUnit.SECONDS);
|
||||||
} else {
|
} else {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||||
"Commands and replies from the device don't seem to match");
|
"Commands and replies from the device don't seem to match");
|
||||||
logger.debug("Could not configure sensor -> setting Thing to OFFLINE and disposing the handler");
|
logger.debug(
|
||||||
dispose();
|
"Could not configure sensor -> setting Thing to OFFLINE, disposing the handler and reschedule initialize in {} seconds",
|
||||||
|
RETRY_INIT_DELAY);
|
||||||
|
doDispose(false);
|
||||||
|
retryInitJob = scheduler.schedule(this::initialize, RETRY_INIT_DELAY.getSeconds(), TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean doInit(SDS011Communicator localCommunicator, WorkMode mode, Duration interval) {
|
||||||
|
boolean initSuccessful = false;
|
||||||
|
try {
|
||||||
|
initSuccessful = localCommunicator.initialize(mode, interval);
|
||||||
|
} catch (final IOException ex) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "I/O error!");
|
||||||
|
} catch (PortInUseException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Port is in use!");
|
||||||
|
} catch (TooManyListenersException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||||
|
"Cannot attach listener to port, because there are too many listeners!");
|
||||||
|
} catch (UnsupportedCommOperationException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
|
||||||
|
"Cannot set serial port parameters");
|
||||||
|
}
|
||||||
|
return initSuccessful;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean validateConfiguration() {
|
private boolean validateConfiguration() {
|
||||||
if (config.port.isEmpty()) {
|
if (config.port.isEmpty()) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!");
|
||||||
|
@ -194,10 +227,14 @@ public class SDS011Handler extends BaseThingHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
ScheduledFuture<?> localPollingJob = this.pollingJob;
|
doDispose(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doDispose(boolean sendDeviceToSleep) {
|
||||||
|
ScheduledFuture<?> localPollingJob = this.dataReadJob;
|
||||||
if (localPollingJob != null) {
|
if (localPollingJob != null) {
|
||||||
localPollingJob.cancel(true);
|
localPollingJob.cancel(true);
|
||||||
this.pollingJob = null;
|
this.dataReadJob = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScheduledFuture<?> localConnectionMonitor = this.connectionMonitor;
|
ScheduledFuture<?> localConnectionMonitor = this.connectionMonitor;
|
||||||
|
@ -206,9 +243,15 @@ public class SDS011Handler extends BaseThingHandler {
|
||||||
this.connectionMonitor = null;
|
this.connectionMonitor = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ScheduledFuture<?> localRetryOpenPortJob = this.retryInitJob;
|
||||||
|
if (localRetryOpenPortJob != null) {
|
||||||
|
localRetryOpenPortJob.cancel(true);
|
||||||
|
this.retryInitJob = null;
|
||||||
|
}
|
||||||
|
|
||||||
SDS011Communicator localCommunicator = this.communicator;
|
SDS011Communicator localCommunicator = this.communicator;
|
||||||
if (localCommunicator != null) {
|
if (localCommunicator != null) {
|
||||||
localCommunicator.dispose();
|
localCommunicator.dispose(sendDeviceToSleep);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.statePM10 = UnDefType.UNDEF;
|
this.statePM10 = UnDefType.UNDEF;
|
||||||
|
@ -248,7 +291,7 @@ public class SDS011Handler extends BaseThingHandler {
|
||||||
"Check connection cable and afterwards disable and enable this thing to make it work again");
|
"Check connection cable and afterwards disable and enable this thing to make it work again");
|
||||||
// in case someone has pulled the plug, we dispose ourselves and the user has to deactivate/activate the
|
// in case someone has pulled the plug, we dispose ourselves and the user has to deactivate/activate the
|
||||||
// thing once the cable is plugged in again
|
// thing once the cable is plugged in again
|
||||||
dispose();
|
doDispose(false);
|
||||||
} else {
|
} else {
|
||||||
logger.trace("Check Alive timer: All OK: lastCommunication={}, interval={}, tollerance={}",
|
logger.trace("Check Alive timer: All OK: lastCommunication={}, interval={}, tollerance={}",
|
||||||
lastCommunication, timeBetweenDataShouldArrive, dataCanBeLateTolerance);
|
lastCommunication, timeBetweenDataShouldArrive, dataCanBeLateTolerance);
|
||||||
|
|
|
@ -18,6 +18,11 @@ import java.io.OutputStream;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.TooManyListenersException;
|
import java.util.TooManyListenersException;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
@ -32,8 +37,6 @@ import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SleepRe
|
||||||
import org.openhab.binding.novafinedust.internal.sds011protocol.messages.WorkingPeriodReply;
|
import org.openhab.binding.novafinedust.internal.sds011protocol.messages.WorkingPeriodReply;
|
||||||
import org.openhab.core.io.transport.serial.PortInUseException;
|
import org.openhab.core.io.transport.serial.PortInUseException;
|
||||||
import org.openhab.core.io.transport.serial.SerialPort;
|
import org.openhab.core.io.transport.serial.SerialPort;
|
||||||
import org.openhab.core.io.transport.serial.SerialPortEvent;
|
|
||||||
import org.openhab.core.io.transport.serial.SerialPortEventListener;
|
|
||||||
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
import org.openhab.core.io.transport.serial.SerialPortIdentifier;
|
||||||
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
|
||||||
import org.openhab.core.util.HexUtils;
|
import org.openhab.core.util.HexUtils;
|
||||||
|
@ -47,7 +50,9 @@ import org.slf4j.LoggerFactory;
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SDS011Communicator implements SerialPortEventListener {
|
public class SDS011Communicator {
|
||||||
|
|
||||||
|
private static final int MAX_SENDOR_REPORTINGS_UNTIL_EXPECTED_REPLY = 20;
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(SDS011Communicator.class);
|
private final Logger logger = LoggerFactory.getLogger(SDS011Communicator.class);
|
||||||
|
|
||||||
|
@ -57,10 +62,13 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
|
|
||||||
private @Nullable OutputStream outputStream;
|
private @Nullable OutputStream outputStream;
|
||||||
private @Nullable InputStream inputStream;
|
private @Nullable InputStream inputStream;
|
||||||
|
private @Nullable ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
public SDS011Communicator(SDS011Handler thingHandler, SerialPortIdentifier portId) {
|
public SDS011Communicator(SDS011Handler thingHandler, SerialPortIdentifier portId,
|
||||||
|
ScheduledExecutorService scheduler) {
|
||||||
this.thingHandler = thingHandler;
|
this.thingHandler = thingHandler;
|
||||||
this.portId = portId;
|
this.portId = portId;
|
||||||
|
this.scheduler = scheduler;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,8 +86,12 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
throws PortInUseException, TooManyListenersException, IOException, UnsupportedCommOperationException {
|
throws PortInUseException, TooManyListenersException, IOException, UnsupportedCommOperationException {
|
||||||
boolean initSuccessful = true;
|
boolean initSuccessful = true;
|
||||||
|
|
||||||
|
logger.trace("Initializing with mode={}, interval={}", mode, interval);
|
||||||
|
|
||||||
SerialPort localSerialPort = portId.open(thingHandler.getThing().getUID().toString(), 2000);
|
SerialPort localSerialPort = portId.open(thingHandler.getThing().getUID().toString(), 2000);
|
||||||
|
logger.trace("Port opened, object is={}", localSerialPort);
|
||||||
localSerialPort.setSerialPortParams(9600, 8, 1, 0);
|
localSerialPort.setSerialPortParams(9600, 8, 1, 0);
|
||||||
|
logger.trace("Serial parameters set on port");
|
||||||
|
|
||||||
outputStream = localSerialPort.getOutputStream();
|
outputStream = localSerialPort.getOutputStream();
|
||||||
inputStream = localSerialPort.getInputStream();
|
inputStream = localSerialPort.getInputStream();
|
||||||
|
@ -87,24 +99,27 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
if (inputStream == null || outputStream == null) {
|
if (inputStream == null || outputStream == null) {
|
||||||
throw new IOException("Could not create input or outputstream for the port");
|
throw new IOException("Could not create input or outputstream for the port");
|
||||||
}
|
}
|
||||||
|
logger.trace("Input and Outputstream opened for the port");
|
||||||
|
|
||||||
// wake up the device
|
// wake up the device
|
||||||
initSuccessful &= sendSleep(false);
|
initSuccessful &= sendSleep(false);
|
||||||
|
logger.trace("Wake up call done, initSuccessful={}", initSuccessful);
|
||||||
initSuccessful &= getFirmware();
|
initSuccessful &= getFirmware();
|
||||||
|
logger.trace("Firmware requested, initSuccessful={}", initSuccessful);
|
||||||
|
|
||||||
if (mode == WorkMode.POLLING) {
|
if (mode == WorkMode.POLLING) {
|
||||||
initSuccessful &= setMode(WorkMode.POLLING);
|
initSuccessful &= setMode(WorkMode.POLLING);
|
||||||
|
logger.trace("Polling mode set, initSuccessful={}", initSuccessful);
|
||||||
initSuccessful &= setWorkingPeriod((byte) 0);
|
initSuccessful &= setWorkingPeriod((byte) 0);
|
||||||
|
logger.trace("Working period for polling set, initSuccessful={}", initSuccessful);
|
||||||
} else {
|
} else {
|
||||||
// reporting
|
// reporting
|
||||||
initSuccessful &= setWorkingPeriod((byte) interval.toMinutes());
|
initSuccessful &= setWorkingPeriod((byte) interval.toMinutes());
|
||||||
|
logger.trace("Working period for reporting set, initSuccessful={}", initSuccessful);
|
||||||
initSuccessful &= setMode(WorkMode.REPORTING);
|
initSuccessful &= setMode(WorkMode.REPORTING);
|
||||||
|
logger.trace("Reporting mode set, initSuccessful={}", initSuccessful);
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable listeners only after we have configured the sensor above because for configuring we send and read data
|
|
||||||
// sequentially
|
|
||||||
localSerialPort.notifyOnDataAvailable(true);
|
|
||||||
localSerialPort.addEventListener(this);
|
|
||||||
this.serialPort = localSerialPort;
|
this.serialPort = localSerialPort;
|
||||||
|
|
||||||
return initSuccessful;
|
return initSuccessful;
|
||||||
|
@ -116,7 +131,12 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
logger.debug("Will send command: {} ({})", HexUtils.bytesToHex(commandData), Arrays.toString(commandData));
|
logger.debug("Will send command: {} ({})", HexUtils.bytesToHex(commandData), Arrays.toString(commandData));
|
||||||
}
|
}
|
||||||
|
|
||||||
write(commandData);
|
try {
|
||||||
|
write(commandData);
|
||||||
|
} catch (IOException ioex) {
|
||||||
|
logger.debug("Got an exception while writing a command, will not try to fetch a reply for it.", ioex);
|
||||||
|
throw ioex;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Give the sensor some time to handle the command
|
// Give the sensor some time to handle the command
|
||||||
|
@ -127,9 +147,13 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
}
|
}
|
||||||
SensorReply reply = readReply();
|
SensorReply reply = readReply();
|
||||||
// in case there is still another reporting active, we want to discard the sensor data and read the reply to our
|
// in case there is still another reporting active, we want to discard the sensor data and read the reply to our
|
||||||
// command again
|
// command again, this might happen more often in case the sensor has buffered some data
|
||||||
if (reply instanceof SensorMeasuredDataReply) {
|
for (int i = 0; i < MAX_SENDOR_REPORTINGS_UNTIL_EXPECTED_REPLY; i++) {
|
||||||
reply = readReply();
|
if (reply instanceof SensorMeasuredDataReply) {
|
||||||
|
reply = readReply();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return reply;
|
return reply;
|
||||||
}
|
}
|
||||||
|
@ -218,7 +242,7 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request data from the device, they will be returned via the serialEvent callback
|
* Request data from the device
|
||||||
*
|
*
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
|
@ -229,6 +253,13 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
logger.debug("Requesting sensor data, will send: {}", HexUtils.bytesToHex(data));
|
logger.debug("Requesting sensor data, will send: {}", HexUtils.bytesToHex(data));
|
||||||
}
|
}
|
||||||
write(data);
|
write(data);
|
||||||
|
try {
|
||||||
|
Thread.sleep(200); // give the device some time to handle the command
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.warn("Interrupted while waiting before reading a reply to our rquest data command.");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
readSensorData();
|
||||||
}
|
}
|
||||||
|
|
||||||
private @Nullable SensorReply readReply() throws IOException {
|
private @Nullable SensorReply readReply() throws IOException {
|
||||||
|
@ -237,7 +268,8 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
InputStream localInpuStream = inputStream;
|
InputStream localInpuStream = inputStream;
|
||||||
|
|
||||||
int b = -1;
|
int b = -1;
|
||||||
if (localInpuStream != null && localInpuStream.available() > 0) {
|
if (localInpuStream != null) {
|
||||||
|
logger.trace("Reading for reply until first byte is found");
|
||||||
while ((b = localInpuStream.read()) != Constants.MESSAGE_START_AS_INT) {
|
while ((b = localInpuStream.read()) != Constants.MESSAGE_START_AS_INT) {
|
||||||
logger.debug("Trying to find first reply byte now...");
|
logger.debug("Trying to find first reply byte now...");
|
||||||
}
|
}
|
||||||
|
@ -252,25 +284,16 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public void readSensorData() throws IOException {
|
||||||
* Data from the device is arriving and will be parsed accordingly
|
logger.trace("readSensorData() called");
|
||||||
*/
|
SensorReply reply = readReply();
|
||||||
@Override
|
logger.trace("readSensorData(): Read reply={}", reply);
|
||||||
public void serialEvent(SerialPortEvent event) {
|
if (reply instanceof SensorMeasuredDataReply) {
|
||||||
if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
|
SensorMeasuredDataReply sensorData = (SensorMeasuredDataReply) reply;
|
||||||
// we get here if data has been received
|
logger.trace("We received sensor data");
|
||||||
SensorReply reply = null;
|
if (sensorData.isValidData()) {
|
||||||
try {
|
logger.trace("Sensor data is valid => updating channels");
|
||||||
reply = readReply();
|
thingHandler.updateChannels(sensorData);
|
||||||
logger.debug("Got data from sensor: {}", reply);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("Could not read available data from the serial port: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
if (reply instanceof SensorMeasuredDataReply) {
|
|
||||||
SensorMeasuredDataReply sensorData = (SensorMeasuredDataReply) reply;
|
|
||||||
if (sensorData.isValidData()) {
|
|
||||||
thingHandler.updateChannels(sensorData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,38 +301,46 @@ public class SDS011Communicator implements SerialPortEventListener {
|
||||||
/**
|
/**
|
||||||
* Shutdown the communication, i.e. send the device to sleep and close the serial port
|
* Shutdown the communication, i.e. send the device to sleep and close the serial port
|
||||||
*/
|
*/
|
||||||
public void dispose() {
|
public void dispose(boolean sendtoSleep) {
|
||||||
SerialPort localSerialPort = serialPort;
|
SerialPort localSerialPort = serialPort;
|
||||||
if (localSerialPort != null) {
|
if (localSerialPort != null) {
|
||||||
|
if (sendtoSleep) {
|
||||||
|
sendDeviceToSleepOnDispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug("Closing the port now");
|
||||||
|
localSerialPort.close();
|
||||||
|
|
||||||
|
serialPort = null;
|
||||||
|
}
|
||||||
|
this.scheduler = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendDeviceToSleepOnDispose() {
|
||||||
|
@Nullable
|
||||||
|
ScheduledExecutorService localScheduler = scheduler;
|
||||||
|
if (localScheduler != null) {
|
||||||
|
Future<?> sleepJob = null;
|
||||||
try {
|
try {
|
||||||
// send the device to sleep to preserve power and extend the lifetime of the sensor
|
sleepJob = localScheduler.submit(() -> {
|
||||||
sendSleep(true);
|
try {
|
||||||
} catch (IOException e) {
|
sendSleep(true);
|
||||||
// ignore because we are shutting down anyway
|
} catch (IOException e) {
|
||||||
logger.debug("Exception while disposing communicator (will ignore it)", e);
|
logger.debug("Exception while sending sleep on disposing the communicator (will ignore it)", e);
|
||||||
} finally {
|
}
|
||||||
localSerialPort.removeEventListener();
|
});
|
||||||
localSerialPort.close();
|
sleepJob.get(5, TimeUnit.SECONDS);
|
||||||
serialPort = null;
|
} catch (TimeoutException e) {
|
||||||
|
logger.warn("Could not send device to sleep, because command takes longer than 5 seconds.");
|
||||||
|
sleepJob.cancel(true);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
logger.debug("Could not execute sleep command.", e);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
logger.debug("Sending device to sleep was interrupted.");
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
logger.debug("Scheduler was null, could not send device to sleep.");
|
||||||
try {
|
|
||||||
InputStream localInputStream = inputStream;
|
|
||||||
if (localInputStream != null) {
|
|
||||||
localInputStream.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.debug("Error while closing the input stream: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
OutputStream localOutputStream = outputStream;
|
|
||||||
if (localOutputStream != null) {
|
|
||||||
localOutputStream.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.debug("Error while closing the output stream: {}", e.getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue