From bf0810c7b031185b426267d586b8cee193dfeeff Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Fri, 1 May 2020 19:07:51 +0200 Subject: [PATCH 01/13] Nova Fine Dust binding for SDS011 sensors Closes #7527 Signed-off-by: Stefan Triller --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + .../.classpath | 32 ++ .../org.openhab.binding.novafinedust/.project | 23 ++ .../org.openhab.binding.novafinedust/NOTICE | 13 + .../README.md | 78 +++++ .../org.openhab.binding.novafinedust/pom.xml | 16 + .../src/main/feature/feature.xml | 10 + .../NovaFineDustBindingConstants.java | 35 +++ .../internal/NovaFineDustConfiguration.java | 32 ++ .../internal/NovaFineDustHandlerFactory.java | 69 +++++ .../novafinedust/internal/SDS011Handler.java | 244 +++++++++++++++ .../internal/sds011protocol/Command.java | 35 +++ .../internal/sds011protocol/Helper.java | 42 +++ .../internal/sds011protocol/ReplyFactory.java | 71 +++++ .../sds011protocol/SDS011Communicator.java | 287 ++++++++++++++++++ .../internal/sds011protocol/WorkMode.java | 24 ++ .../messages/CommandMessage.java | 97 ++++++ .../sds011protocol/messages/Constants.java | 37 +++ .../sds011protocol/messages/ModeReply.java | 63 ++++ .../messages/SensorFirmwareReply.java | 51 ++++ .../messages/SensorMeasuredDataReply.java | 79 +++++ .../sds011protocol/messages/SensorReply.java | 89 ++++++ .../sds011protocol/messages/SleepReply.java | 58 ++++ .../messages/WorkingPeriodReply.java | 58 ++++ .../resources/ESH-INF/binding/binding.xml | 10 + .../resources/ESH-INF/thing/thing-types.xml | 57 ++++ bundles/pom.xml | 1 + 28 files changed, 1617 insertions(+) create mode 100644 bundles/org.openhab.binding.novafinedust/.classpath create mode 100644 bundles/org.openhab.binding.novafinedust/.project create mode 100644 bundles/org.openhab.binding.novafinedust/NOTICE create mode 100644 bundles/org.openhab.binding.novafinedust/README.md create mode 100644 bundles/org.openhab.binding.novafinedust/pom.xml create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustBindingConstants.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustConfiguration.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustHandlerFactory.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Command.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Helper.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/ReplyFactory.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/WorkMode.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/Constants.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/ModeReply.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorFirmwareReply.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SleepReply.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/WorkingPeriodReply.java create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml diff --git a/CODEOWNERS b/CODEOWNERS index 331807f714838..347e30a9e7927 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -133,6 +133,7 @@ /bundles/org.openhab.binding.nibeuplink/ @alexf2015 /bundles/org.openhab.binding.nikobus/ @crnjan /bundles/org.openhab.binding.nikohomecontrol/ @mherwege +/bundles/org.openhab.binding.novafinedust/ @t2000 /bundles/org.openhab.binding.ntp/ @marcelrv /bundles/org.openhab.binding.nuki/ @mkatter /bundles/org.openhab.binding.oceanic/ @kgoderis diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 13d710177515d..ac88aeb864b93 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -659,6 +659,11 @@ org.openhab.binding.nikohomecontrol ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.novafinedust + ${project.version} + org.openhab.addons.bundles org.openhab.binding.ntp diff --git a/bundles/org.openhab.binding.novafinedust/.classpath b/bundles/org.openhab.binding.novafinedust/.classpath new file mode 100644 index 0000000000000..a5d95095ccaaf --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/.classpath @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.novafinedust/.project b/bundles/org.openhab.binding.novafinedust/.project new file mode 100644 index 0000000000000..ce6cbf0e75027 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/.project @@ -0,0 +1,23 @@ + + + org.openhab.binding.novafinedust + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/bundles/org.openhab.binding.novafinedust/NOTICE b/bundles/org.openhab.binding.novafinedust/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.novafinedust/README.md b/bundles/org.openhab.binding.novafinedust/README.md new file mode 100644 index 0000000000000..8bdcb0be539b4 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/README.md @@ -0,0 +1,78 @@ +# NovaFineDust Binding + +This binding is for the fine dust sensor (PM Sensor) from Nova Fitness. Currently only one model is supported, the SDS011. + +It basically implements the protocol specified in [this document](https://cdn.sparkfun.com/assets/parts/1/2/2/7/5/Laser_Dust_Sensor_Control_Protocol_V1.3.pdf). +One can measure the PM 2.5 and PM 10 values with this device. It comes very handy for detecting air pollution like neighbors firing their oven with wet wood etc. so one can deactivate the ventilation system. + +## Supported Things + +There is only one Thing for this binding is `SDS011`. + +## Discovery + +There is no automatic discovery. The Thing has to be added manually via the `.things` configuration file or via (Paper) UI. + +## Thing Configuration + +There are 2 different working modes for the `SDS011` thing: Reporting and Polling. + +### Reporting + +This is the preferred mode and thus also configured as a default. In this mode the sensor wakes up every `reportingInterval` minutes, performs a measurement for 30 seconds and sleeps for `reportingInterval` minus 30 seconds. Remember: According to the [datasheet](https://www-sd-nf.oss-cn-beijing.aliyuncs.com/%E5%AE%98%E7%BD%91%E4%B8%8B%E8%BD%BD/SDS011%20laser%20PM2.5%20sensor%20specification-V1.4.pdf) the sensor has a lifetime of 8000 hours. Using a 0 as `reportingInterval` will make the sensor report its data as fast as possible. + +### Polling + +If one needs data in different intervals, i.e. not as fast as possible and not in intervals that are a multiple of full minutes, polling can be configured. The `pollingInterval` parameter specifies the time in seconds when data will be polled from the sensor. + +In addition to the mode one has to provide the port to which the device is connected. + +A full overview about the parameters of the `SDS011` thing is given in the following table: + +| parameter name | mandatory | description | +|-------------------|-----------|---------------------------------------------------------------------------------------| +| port | yes | the port the sensor is connected to, i.e. /detv/ttyUSB0. | +| reporting | yes | whether the reporting mode (value=true) or polling mode should be used. | +| reportingInterval | no | the time in minutes between reportings from the sensor (default=1, min=0, max=30). | +| pollingInterval | no | the time in seconds between data polls from the device. (default=10, min=3, max=3600) | + +## Channels + +Since the supported device is a sensor, both channels are read-only channels. + +| channel | type | description | +|----------|----------------|-------------------------------| +| pm25 | Number:Density | This provides the PM2.5 value | +| pm10 | Number:Density | This provides the PM10 value | + +## Full Example + +demo.things: + +``` +Thing novafinedust:SDS011:mySDS011Report "My SDS011 Fine Dust Sensor with reporting" [ port="/dev/ttyUSB0", reporting=true, reportingInterval=1 ] +Thing novafinedust:SDS011:mySDS011Poll "My SDS011 Fine Dust Sensor with polling" [ port="/dev/ttyUSB0", reporting=false, pollingInterval=10 ] +``` + +demo.items: + +``` +Number:Density PM25 "My PM 2.5 value" { channel="novafinedust:SDS011:mySDS011Report:pm25" } +Number:Density PM10 "My PM 10 value" { channel="novafinedust:SDS011:mySDS011Report:pm10" } +``` + +demo.sitemap: + +``` +sitemap demo label="Main Menu" +{ + Frame { + Text item=PM25 label="My PM 2.5 value" + Text item=PM10 label="My PM 10 value" + } +} +``` + +## Limitations + +In theory once can have multiple sensors connected and distinguish them via their device ID. However, this is currently not implemented and the binding always configures any device and accepts data reportings from any device too. diff --git a/bundles/org.openhab.binding.novafinedust/pom.xml b/bundles/org.openhab.binding.novafinedust/pom.xml new file mode 100644 index 0000000000000..66de5f6112bca --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/pom.xml @@ -0,0 +1,16 @@ + + + + 4.0.0 + + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 2.5.5-SNAPSHOT + + + org.openhab.binding.novafinedust + + openHAB Add-ons :: Bundles :: NovaFineDust Binding + + diff --git a/bundles/org.openhab.binding.novafinedust/src/main/feature/feature.xml b/bundles/org.openhab.binding.novafinedust/src/main/feature/feature.xml new file mode 100644 index 0000000000000..0c485318fc7c4 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/feature/feature.xml @@ -0,0 +1,10 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + + openhab-runtime-base + openhab-transport-serial + mvn:org.openhab.addons.bundles/org.openhab.binding.novafinedust/${project.version} + + diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustBindingConstants.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustBindingConstants.java new file mode 100644 index 0000000000000..e73629672c43d --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustBindingConstants.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.thing.ThingTypeUID; + +/** + * The {@link NovaFineDustBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Stefan Triller - Initial contribution + */ +@NonNullByDefault +public class NovaFineDustBindingConstants { + + private static final String BINDING_ID = "novafinedust"; + + // List of all Thing Type UIDs + public static final ThingTypeUID THING_TYPE_SDS011 = new ThingTypeUID(BINDING_ID, "SDS011"); + + // List of all Channel ids + public static final String CHANNEL_PM25 = "pm25"; + public static final String CHANNEL_PM10 = "pm10"; +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustConfiguration.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustConfiguration.java new file mode 100644 index 0000000000000..08c52a09b32b8 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustConfiguration.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link NovaFineDustConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Stefan Triller - Initial contribution + */ +@NonNullByDefault +public class NovaFineDustConfiguration { + + /** + * USB port of the device + */ + public String port = ""; + public boolean reporting = true; + public int reportingInterval = 1; + public int pollingInterval = 10; +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustHandlerFactory.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustHandlerFactory.java new file mode 100644 index 0000000000000..f5ff831998fd2 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustHandlerFactory.java @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal; + +import static org.openhab.binding.novafinedust.internal.NovaFineDustBindingConstants.THING_TYPE_SDS011; + +import java.util.Collections; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingTypeUID; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandlerFactory; +import org.eclipse.smarthome.core.thing.binding.ThingHandler; +import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; +import org.eclipse.smarthome.io.transport.serial.SerialPortManager; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link NovaFineDustHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Stefan Triller - Initial contribution + */ +@NonNullByDefault +@Component(configurationPid = "binding.novafinedust", service = ThingHandlerFactory.class) +public class NovaFineDustHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_SDS011); + + private @NonNullByDefault({}) SerialPortManager serialPortManager; + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + + if (THING_TYPE_SDS011.equals(thingTypeUID)) { + return new SDS011Handler(thing, serialPortManager); + } + + return null; + } + + @Reference + protected void setSerialPortManager(final SerialPortManager serialPortManager) { + this.serialPortManager = serialPortManager; + } + + protected void unsetSerialPortManager(final SerialPortManager serialPortManager) { + this.serialPortManager = null; + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java new file mode 100644 index 0000000000000..f5ae09596f21c --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java @@ -0,0 +1,244 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal; + +import java.io.IOException; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.chrono.ChronoZonedDateTime; +import java.time.temporal.Temporal; +import java.util.TooManyListenersException; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.library.dimension.Density; +import org.eclipse.smarthome.core.library.types.QuantityType; +import org.eclipse.smarthome.core.library.unit.SmartHomeUnits; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.Thing; +import org.eclipse.smarthome.core.thing.ThingStatus; +import org.eclipse.smarthome.core.thing.ThingStatusDetail; +import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; +import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.io.transport.serial.PortInUseException; +import org.eclipse.smarthome.io.transport.serial.SerialPortIdentifier; +import org.eclipse.smarthome.io.transport.serial.SerialPortManager; +import org.eclipse.smarthome.io.transport.serial.UnsupportedCommOperationException; +import org.openhab.binding.novafinedust.internal.sds011protocol.SDS011Communicator; +import org.openhab.binding.novafinedust.internal.sds011protocol.WorkMode; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SensorMeasuredDataReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link SDS011Handler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Stefan Triller - Initial contribution + */ +@NonNullByDefault +public class SDS011Handler extends BaseThingHandler { + private static final Duration CONNECTION_MONITOR_START_DELAY_OFFSET = Duration.ofSeconds(10); + + private final Logger logger = LoggerFactory.getLogger(SDS011Handler.class); + private final SerialPortManager serialPortManager; + + private @NonNullByDefault({}) NovaFineDustConfiguration config; + private @NonNullByDefault({}) SDS011Communicator communicator; + + private @Nullable ScheduledFuture pollingJob; + private @Nullable ScheduledFuture connectionMonitor; + + private Temporal lastCommunication = ZonedDateTime.now(); + + // initialize timeBetweenDataShouldArrive with a large number + private Duration timeBetweenDataShouldArrive = Duration.ofDays(1); + private final Duration dataCanBeLateTollerance = Duration.ofSeconds(5); + + public SDS011Handler(Thing thing, SerialPortManager serialPortManager) { + super(thing); + this.serialPortManager = serialPortManager; + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + // we do not support refreshing as values are either reported by the device or polled from the device in fixed + // intervals + } + + @Override + public void initialize() { + updateStatus(ThingStatus.UNKNOWN); + + config = getConfigAs(NovaFineDustConfiguration.class); + + if (!validateConfiguration()) { + return; + } + + // parse ports and if the port is found, initialize the reader + SerialPortIdentifier portId = serialPortManager.getIdentifier(config.port); + if (portId == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port is not known!"); + return; + } + + this.communicator = new SDS011Communicator(this, portId); + + if (config.reporting) { + timeBetweenDataShouldArrive = Duration.ofMinutes(config.reportingInterval); + scheduler.schedule(() -> initializeCommunicator(WorkMode.REPORTING, timeBetweenDataShouldArrive), 0, + TimeUnit.SECONDS); + } else { + timeBetweenDataShouldArrive = Duration.ofSeconds(config.pollingInterval); + scheduler.schedule(() -> initializeCommunicator(WorkMode.POLLING, timeBetweenDataShouldArrive), 0, + TimeUnit.SECONDS); + } + + 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) { + boolean initSuccessful = false; + try { + initSuccessful = communicator.initialize(mode, interval); + } catch (final IOException ex) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "I/O error!"); + return; + } catch (PortInUseException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Port is in use!"); + 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) { + lastCommunication = ZonedDateTime.now(); + updateStatus(ThingStatus.ONLINE); + + if (mode == WorkMode.POLLING) { + pollingJob = scheduler.scheduleWithFixedDelay(() -> { + try { + communicator.requestSensorData(); + } catch (IOException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + "Cannot query data from device"); + } + }, 2, config.pollingInterval, TimeUnit.SECONDS); + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + "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"); + dispose(); + } + } + + private boolean validateConfiguration() { + if (config.port == null || config.port.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!"); + return false; + } + + if (config.reporting) { + if (config.reportingInterval < 0 || config.reportingInterval > 30) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Reporting interval has to be between 0 and 30 minutes"); + return false; + } + } else { + if (config.pollingInterval < 3 || config.pollingInterval > 3600) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Polling interval has to be between 3 and 3600 seconds"); + return false; + } + } + return true; + } + + @Override + public void dispose() { + if (pollingJob != null && !pollingJob.isCancelled()) { + pollingJob.cancel(true); + pollingJob = null; + } + + if (connectionMonitor != null && !connectionMonitor.isCancelled()) { + connectionMonitor.cancel(true); + connectionMonitor = null; + } + + if (communicator != null) { + scheduler.schedule(() -> communicator.dispose(), 0, TimeUnit.SECONDS); + } + } + + /** + * Pass the data from the device to the Thing channels + * + * @param sensorData the parsed data from the sensor + */ + public void updateChannels(SensorMeasuredDataReply sensorData) { + if (sensorData.isValidData()) { + logger.debug("Updating channels with data: {}", sensorData); + + QuantityType statePM10 = new QuantityType<>(sensorData.getPm10(), + SmartHomeUnits.MICROGRAM_PER_CUBICMETRE); + updateState(NovaFineDustBindingConstants.CHANNEL_PM10, statePM10); + + QuantityType statePM25 = new QuantityType<>(sensorData.getPm25(), + SmartHomeUnits.MICROGRAM_PER_CUBICMETRE); + updateState(NovaFineDustBindingConstants.CHANNEL_PM25, statePM25); + + updateStatus(ThingStatus.ONLINE); + } + // there was a communication, even if the data was not valid, thus resetting the value here + lastCommunication = ZonedDateTime.now(); + } + + private void verifyIfStillConnected() { + ZonedDateTime now = ZonedDateTime.now(); + Temporal lastData = lastCommunication.plus(timeBetweenDataShouldArrive).plus(dataCanBeLateTollerance); + if (now.isAfter((ChronoZonedDateTime) lastData)) { + logger.debug("Check Alive timer: Timeout: lastCommunication={}, interval={}, tollerance={}", + lastCommunication, timeBetweenDataShouldArrive, dataCanBeLateTollerance); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + "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 + // thing once the cable is plugged in again + dispose(); + } else { + logger.trace("Check Alive timer: All OK: lastCommunication={}, interval={}, tollerance={}", + lastCommunication, timeBetweenDataShouldArrive, dataCanBeLateTollerance); + } + } + + /** + * Set the firmware property on the Thing + * + * @param firmwareVersion the firmware version as a String + */ + public void setFirmware(String firmwareVersion) { + updateProperty(Thing.PROPERTY_FIRMWARE_VERSION, firmwareVersion); + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Command.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Command.java new file mode 100644 index 0000000000000..c86af3d705bc3 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Command.java @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Class holding the command constants to be send to the sensor in the first data byte + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class Command { + + private Command() { + } + + public static final byte MODE = 2; + public static final byte REQUEST_DATA = 4; + public static final byte HARDWARE_ID = 5; + public static final byte SLEEP = 6; + public static final byte FIRMWARE = 7; + public static final byte WORKING_PERIOD = 8; +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Helper.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Helper.java new file mode 100644 index 0000000000000..6e8fbe67e7dd7 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Helper.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Class with useful utility functions + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class Helper { + + private Helper() { + } + + /** + * Converts a byte array to a hexadecimal string, handy for printing + * + * @param bytes the byte array to be converted + * @return a String describing the byte array in hexadecimal values + */ + public static String toHexString(byte[] bytes) { + StringBuilder sb = new StringBuilder(); + for (byte b : bytes) { + sb.append(String.format("%02X ", b)); + } + return sb.toString(); + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/ReplyFactory.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/ReplyFactory.java new file mode 100644 index 0000000000000..44132bea4411c --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/ReplyFactory.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.ModeReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SensorFirmwareReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SensorMeasuredDataReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SensorReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SleepReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.WorkingPeriodReply; + +/** + * Factory for creating the specific reply instances for data received from the sensor + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class ReplyFactory { + + private static final byte COMMAND_REPLY = (byte) 0xC5; + private static final byte DATA_REPLY = (byte) 0xC0; + + private ReplyFactory() { + } + + /** + * Creates the specific reply message according to the commandID and first data byte + * + * @param bytes the received message + * @return a specific instance of a sensor reply message + */ + public static @Nullable SensorReply create(byte[] bytes) { + if (bytes.length != 10) { + return null; + } + + byte commandID = bytes[1]; + byte firstDataByte = bytes[2]; + + if (commandID == COMMAND_REPLY) { + switch (firstDataByte) { + case Command.FIRMWARE: + return new SensorFirmwareReply(bytes); + case Command.WORKING_PERIOD: + return new WorkingPeriodReply(bytes); + case Command.MODE: + return new ModeReply(bytes); + case Command.SLEEP: + return new SleepReply(bytes); + default: + return new SensorReply(bytes); + } + } else if (commandID == DATA_REPLY) { + return new SensorMeasuredDataReply(bytes); + } + return null; + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java new file mode 100644 index 0000000000000..8a1dc4f8063e2 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java @@ -0,0 +1,287 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.time.Duration; +import java.util.Arrays; +import java.util.TooManyListenersException; + +import org.apache.commons.io.IOUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.io.transport.serial.PortInUseException; +import org.eclipse.smarthome.io.transport.serial.SerialPort; +import org.eclipse.smarthome.io.transport.serial.SerialPortEvent; +import org.eclipse.smarthome.io.transport.serial.SerialPortEventListener; +import org.eclipse.smarthome.io.transport.serial.SerialPortIdentifier; +import org.eclipse.smarthome.io.transport.serial.UnsupportedCommOperationException; +import org.openhab.binding.novafinedust.internal.SDS011Handler; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.CommandMessage; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.Constants; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.ModeReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SensorFirmwareReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SensorMeasuredDataReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SensorReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.SleepReply; +import org.openhab.binding.novafinedust.internal.sds011protocol.messages.WorkingPeriodReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Central instance to communicate with the device, i.e. receive data from it and send commands to it + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class SDS011Communicator implements SerialPortEventListener { + + private final Logger logger = LoggerFactory.getLogger(SDS011Communicator.class); + + private SerialPortIdentifier portId; + private SDS011Handler thingHandler; + private @NonNullByDefault({}) SerialPort serialPort; + + private @NonNullByDefault({}) OutputStream outputStream; + private @NonNullByDefault({}) InputStream inputStream; + + public SDS011Communicator(SDS011Handler thingHandler, SerialPortIdentifier portId) { + this.thingHandler = thingHandler; + this.portId = portId; + } + + /** + * Initialize the communication with the device, i.e. open the serial port etc. + * + * @param mode the {@link WorkMode} if we want to use polling or reporting + * @param interval the time between polling or reportings + * @return {@code true} if we can communicate with the device + * @throws PortInUseException + * @throws TooManyListenersException + * @throws IOException + * @throws UnsupportedCommOperationException + */ + public boolean initialize(WorkMode mode, Duration interval) + throws PortInUseException, TooManyListenersException, IOException, UnsupportedCommOperationException { + boolean initSuccessful = true; + + SerialPort localSerialPort = portId.open(thingHandler.getThing().getUID().toString(), 2000); + localSerialPort.setSerialPortParams(9600, 8, 1, 0); + + outputStream = localSerialPort.getOutputStream(); + inputStream = localSerialPort.getInputStream(); + + if (inputStream == null || outputStream == null) { + throw new IOException("Could not create input or outputstream for the port"); + } + + // wake up the device + initSuccessful &= sendSleep(false); + initSuccessful &= getFirmware(); + + if (mode == WorkMode.POLLING) { + initSuccessful &= setMode(WorkMode.POLLING); + initSuccessful &= setWorkingPeriod((byte) 0); + } else { + // reporting + initSuccessful &= setWorkingPeriod((byte) interval.toMinutes()); + initSuccessful &= setMode(WorkMode.REPORTING); + } + + // 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; + + return initSuccessful; + } + + private @Nullable SensorReply sendCommand(CommandMessage message) throws IOException { + byte[] commandData = message.getBytes(); + if (logger.isDebugEnabled()) { + logger.debug("Will send command: {} ({})", Helper.toHexString(commandData), Arrays.toString(commandData)); + } + outputStream.write(commandData, 0, commandData.length); + outputStream.flush(); + try { + // Give the sensor some time to handle the command + Thread.sleep(500); + } catch (InterruptedException e) { + logger.warn("Problem while waiting for reading a reply to our command."); + Thread.currentThread().interrupt(); + } + SensorReply reply = readReply(); + // in case there is still another reporting active, we want to discard the sensor data and read the reply to our + // command again + if (reply instanceof SensorMeasuredDataReply) { + reply = readReply(); + } + return reply; + } + + private boolean setWorkingPeriod(byte period) throws IOException { + CommandMessage m = new CommandMessage(Command.WORKING_PERIOD, new byte[] { Constants.SET_ACTION, period }); + logger.debug("Sending work period: {}", period); + SensorReply reply = sendCommand(m); + logger.debug("Got reply to setWorkingPeriod command: {}", reply); + if (reply instanceof WorkingPeriodReply) { + WorkingPeriodReply wpReply = (WorkingPeriodReply) reply; + if (wpReply.getPeriod() == period && wpReply.getActionType() == Constants.SET_ACTION) { + return true; + } + } + return false; + } + + private boolean setMode(WorkMode workMode) throws IOException { + byte haveToRequestData = 0; + if (workMode == WorkMode.POLLING) { + haveToRequestData = 1; + } + + CommandMessage m = new CommandMessage(Command.MODE, new byte[] { Constants.SET_ACTION, haveToRequestData }); + logger.debug("Sending mode: {}", workMode); + SensorReply reply = sendCommand(m); + logger.debug("Got reply to setMode command: {}", reply); + if (reply instanceof ModeReply) { + ModeReply mr = (ModeReply) reply; + if (mr.getActionType() == Constants.SET_ACTION && mr.getMode() == workMode) { + return true; + } + } + return false; + } + + private boolean sendSleep(boolean doSleep) throws IOException { + byte payload = (byte) 1; + if (doSleep) { + payload = (byte) 0; + } + + CommandMessage m = new CommandMessage(Command.SLEEP, new byte[] { Constants.SET_ACTION, payload }); + logger.debug("Sending doSleep: {}", doSleep); + SensorReply reply = sendCommand(m); + logger.debug("Got reply to sendSleep command: {}", reply); + + if (!doSleep) { + // sometimes the sensor does not wakeup on the first attempt, thus we try again + for (int i = 0; reply == null && i < 3; i++) { + reply = sendCommand(m); + logger.debug("Got reply to sendSleep command after retry#{}: {}", i + 1, reply); + } + } + + if (reply instanceof SleepReply) { + SleepReply sr = (SleepReply) reply; + if (sr.getActionType() == Constants.SET_ACTION && sr.getSleep() == payload) { + return true; + } + } + return false; + } + + private boolean getFirmware() throws IOException { + CommandMessage m = new CommandMessage(Command.FIRMWARE, new byte[] {}); + logger.debug("Sending get firmware request"); + SensorReply reply = sendCommand(m); + logger.debug("Got reply to getFirmware command: {}", reply); + + if (reply instanceof SensorFirmwareReply) { + SensorFirmwareReply fwReply = (SensorFirmwareReply) reply; + thingHandler.setFirmware(fwReply.getFirmware()); + return true; + } + return false; + } + + /** + * Request data from the device, they will be returned via the serialEvent callback + * + * @throws IOException + */ + public void requestSensorData() throws IOException { + CommandMessage m = new CommandMessage(Command.REQUEST_DATA, new byte[] {}); + byte[] data = m.getBytes(); + if (logger.isDebugEnabled()) { + logger.debug("Requesting sensor data, will send: {}", Helper.toHexString(data)); + } + outputStream.write(data, 0, data.length); + } + + private @Nullable SensorReply readReply() throws IOException { + byte[] readBuffer = new byte[Constants.REPLY_LENGTH]; + + int b = -1; + if (inputStream.available() > 0) { + while ((b = inputStream.read()) != Constants.MESSAGE_START_AS_INT) { + logger.debug("Trying to find first reply byte now..."); + } + readBuffer[0] = (byte) b; + int remainingBytesRead = inputStream.read(readBuffer, 1, Constants.REPLY_LENGTH - 1); + if (logger.isDebugEnabled()) { + logger.debug("Read remaining bytes: {}, full reply={}", remainingBytesRead, + Helper.toHexString(readBuffer)); + } + return ReplyFactory.create(readBuffer); + } + return null; + } + + /** + * Data from the device is arriving and will be parsed accordingly + */ + @Override + public void serialEvent(SerialPortEvent event) { + if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) { + // we get here if data has been received + SensorReply reply = null; + try { + reply = readReply(); + logger.debug("Got data from sensor: {}", reply); + } catch (IOException e) { + logger.error("Could not read available data from the serial port", e); + } + if (reply instanceof SensorMeasuredDataReply) { + SensorMeasuredDataReply sensorData = (SensorMeasuredDataReply) reply; + if (sensorData.isValidData()) { + thingHandler.updateChannels(sensorData); + } + } + } + } + + /** + * Shutdown the communication, i.e. send the device to sleep and close the serial port + */ + public void dispose() { + if (serialPort != null) { + try { + // send the device to sleep to preserve power and extend the lifetime of the sensor + sendSleep(true); + } catch (IOException e) { + // ignore because we are shutting down anyway + logger.debug("Exception while disposing communicator (will ignore it)", e); + } finally { + serialPort.removeEventListener(); + serialPort.close(); + serialPort = null; + } + } + IOUtils.closeQuietly(inputStream); + IOUtils.closeQuietly(outputStream); + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/WorkMode.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/WorkMode.java new file mode 100644 index 0000000000000..9b92bb62b1261 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/WorkMode.java @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol; + +/** + * Enum for the different sensor modes + * + * @author Stefan Triller - Initial contribution + * + */ +public enum WorkMode { + REPORTING, + POLLING +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java new file mode 100644 index 0000000000000..ce725d93353e5 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol.messages; + +import java.io.ByteArrayOutputStream; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.novafinedust.internal.sds011protocol.Helper; + +/** + * Message to be send to the device + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class CommandMessage { + private static final byte HEAD = -86; // AA + private static final byte COMMAND_ID = -76; // B4 + private static final byte TAIL = -85; // AB + + private static final int DATA_BYTES_AFTER_FIRST_DATA_BYTE = 12; + + private byte firstDataByte; + private byte[] payLoad = new byte[DATA_BYTES_AFTER_FIRST_DATA_BYTE]; + private byte[] targetDevice = new byte[] { -1, -1 }; // FF FF = all devices + + public CommandMessage(byte command, byte[] payLoad) { + this.firstDataByte = command; + this.payLoad = payLoad; + } + + public CommandMessage(byte command, byte[] payLoad, byte[] targetDevice) { + this.firstDataByte = command; + this.payLoad = payLoad; + this.targetDevice = targetDevice; + } + + /** + * Get the raw bytes to be send out to the device + * + * @return ByteArray containing the bytes for a message to the device + */ + public byte[] getBytes() { + ByteArrayOutputStream message = new ByteArrayOutputStream(19); + + message.write(HEAD); + message.write(COMMAND_ID); + message.write(firstDataByte); + + for (byte b : payLoad) { + message.write(b); + } + int padding = DATA_BYTES_AFTER_FIRST_DATA_BYTE - payLoad.length; + for (int i = 0; i < padding; i++) { + message.write(0x00); + } + + for (byte b : targetDevice) { + message.write(b); + } + message.write(calculateCheckSum(message.toByteArray())); + message.write(TAIL); + + return message.toByteArray(); + } + + private byte calculateCheckSum(byte[] data) { + int checksum = 0; + for (int i = 2; i <= 14; i++) { + checksum += data[i]; + } + checksum = (checksum - 2) % 256; + + return (byte) checksum; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Message: "); + sb.append("Command=" + firstDataByte); + sb.append(" Target Device=" + Helper.toHexString(targetDevice)); + sb.append(" Payload=" + Helper.toHexString(payLoad)); + return sb.toString(); + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/Constants.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/Constants.java new file mode 100644 index 0000000000000..8ed17a7ec04ed --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/Constants.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol.messages; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Constants for sensor messages + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class Constants { + + private Constants() { + } + + public static final byte MESSAGE_START = (byte) 0xAA; + public static final int MESSAGE_START_AS_INT = 170; + public static final byte MESSAGE_END = (byte) 0xAB; + + public static final int REPLY_LENGTH = 10; + + public static final byte QUERY_ACTION = (byte) 0x00; + public static final byte SET_ACTION = (byte) 0x01; +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/ModeReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/ModeReply.java new file mode 100644 index 0000000000000..dad004c87a33f --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/ModeReply.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol.messages; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.novafinedust.internal.sds011protocol.WorkMode; + +/** + * Reply from sensor to a set mode command + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class ModeReply extends SensorReply { + + private byte actionType; + private WorkMode mode; + + public ModeReply(byte[] bytes) { + super(bytes); + + this.actionType = bytes[3]; + if (bytes[4] == (byte) 1) { + this.mode = WorkMode.POLLING; + } else { + this.mode = WorkMode.REPORTING; + } + } + + /** + * Get the type of action + * + * @return 0 = query 1 = set mode + */ + public byte getActionType() { + return actionType; + } + + /** + * Get the set work mode + * + * @return work mode set on the sensor + */ + public WorkMode getMode() { + return mode; + } + + @Override + public String toString() { + return "ModeReply: [mode=" + mode + "]"; + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorFirmwareReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorFirmwareReply.java new file mode 100644 index 0000000000000..5ed27d1c2a87a --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorFirmwareReply.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol.messages; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Data from the sensor containing information about the installed firmware + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class SensorFirmwareReply extends SensorReply { + + private byte year; + private byte month; + private byte day; + + public SensorFirmwareReply(byte[] receivedData) { + super(receivedData); + this.year = receivedData[3]; + this.month = receivedData[4]; + this.day = receivedData[5]; + } + + /** + * Gets the firmware of the sensor as a String + * + * @return firmware of the sensor formatted as YY-MM-DD + */ + public String getFirmware() { + String firmware = year + "-" + month + "-" + day; + return firmware; + } + + @Override + public String toString() { + return "FirmwareReply: [firmware=" + getFirmware() + "]"; + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java new file mode 100644 index 0000000000000..8908c1a1fa08d --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java @@ -0,0 +1,79 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol.messages; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.novafinedust.internal.sds011protocol.Helper; + +/** + * Class containing the actual measured values from the sensor + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class SensorMeasuredDataReply extends SensorReply { + private byte pm25lowByte; + private byte pm25highByte; + private byte pm10lowByte; + private byte pm10highByte; + + /** + * Create a new instance by parsing the given 10 bytes. + * + */ + public SensorMeasuredDataReply(byte[] bytes) { + super(bytes); + pm25lowByte = bytes[2]; + pm25highByte = bytes[3]; + pm10lowByte = bytes[4]; + pm10highByte = bytes[5]; + } + + /** + * Check if data is valid by checking header, commanderNo, messageTail and checksum. + */ + public boolean isValidData() { + return header == Constants.MESSAGE_START && commandID == (byte) 0xC0 && messageTail == Constants.MESSAGE_END + && checksum == calculateChecksum(); + } + + /** + * Get the measured PM2.5 value + * + * @return the measured PM2.5 value + */ + public float getPm25() { + int shiftedValue = (pm25highByte << 8 & 0xFF) | pm25lowByte & 0xFF; + return ((float) shiftedValue) / 10; + } + + /** + * Get the measured PM10 value + * + * @return the measured PM10 value + */ + public float getPm10() { + int shiftedValue = (pm10highByte << 8 & 0xFF) | pm10lowByte & 0xFF; + return ((float) shiftedValue) / 10; + } + + @Override + public String toString() { + return String.format( + "SensorMeasuredDataReply: [valid=%s, PM 2.5=%.1f, PM 10=%.1f, sourceDevice=%s, pm25lowHigh=(%s) pm10lowHigh=(%s)]", + isValidData(), getPm25(), getPm10(), Helper.toHexString(new byte[] { deviceID[0], deviceID[1] }), + Helper.toHexString(new byte[] { pm25lowByte, pm25highByte }), + Helper.toHexString(new byte[] { pm10lowByte, pm10highByte })); + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java new file mode 100644 index 0000000000000..f5ccf09c53d51 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol.messages; + +import java.util.Arrays; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.novafinedust.internal.sds011protocol.Helper; + +/** + * Base class holding information sent by the sensor to us + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class SensorReply { + + protected byte header; + protected byte commandID; + protected byte[] payLoad = new byte[4]; + protected byte[] deviceID = new byte[2]; + protected byte checksum; + protected byte messageTail; + + /** + * Creates the container for data received from the sensor + * + * @param bytes the data received from the sensor + * @throws IllegalArgumentException Is thrown if less than 10 bytes are provided. + */ + public SensorReply(byte[] bytes) { + if (bytes.length != 10) { + throw new IllegalArgumentException("was expecting 10 bytes, but received " + bytes.length); + } + this.header = bytes[0]; + this.commandID = bytes[1]; + this.payLoad = Arrays.copyOfRange(bytes, 2, 6); + this.deviceID = Arrays.copyOfRange(bytes, 6, 8); + this.checksum = bytes[8]; + this.messageTail = bytes[9]; + } + + /** + * Gets the commandID byte. However there is the first data byte which holds a kind of "sub command" that has to be + * evaluated too + * + * @return byte representing the commandID + */ + public byte getCommandID() { + return this.commandID; + } + + /** + * Gets the first byte from the data bytes (usually holds the {@link Command}) as a form of some sub command + * + * @return first byte from the data section of a reply + */ + public byte getFirstDataByte() { + return this.payLoad[0]; + } + + protected byte calculateChecksum() { + byte sum = 0; + for (byte b : payLoad) { + sum += b; + } + for (byte b : deviceID) { + sum += b; + } + return sum; + } + + @Override + public String toString() { + return String.format("GeneralReply: [head=%x, commandID=%x, payload=%s, deviceID=%s, checksum=%s, tail=%x", + header, commandID, Helper.toHexString(payLoad), Helper.toHexString(deviceID), checksum, messageTail); + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SleepReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SleepReply.java new file mode 100644 index 0000000000000..2d60dd31735fe --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SleepReply.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol.messages; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Reply from sensor to a set sleep command + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class SleepReply extends SensorReply { + + private byte actionType; + private byte sleep; + + public SleepReply(byte[] bytes) { + super(bytes); + + this.actionType = bytes[3]; + this.sleep = bytes[4]; + } + + /** + * Get the type of action + * + * @return 0 = query 1 = set mode + */ + public byte getActionType() { + return actionType; + } + + /** + * Get the info whether this is a sleep or wakeup reply + * + * @return 0 = sleep 1 = work + */ + public byte getSleep() { + return sleep; + } + + @Override + public String toString() { + return "SleepReply: [sleep=" + sleep + "]"; + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/WorkingPeriodReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/WorkingPeriodReply.java new file mode 100644 index 0000000000000..96b3e3d497527 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/WorkingPeriodReply.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2020 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.novafinedust.internal.sds011protocol.messages; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Reply from sensor to a set working period command + * + * @author Stefan Triller - Initial contribution + * + */ +@NonNullByDefault +public class WorkingPeriodReply extends SensorReply { + + private byte actionType; + private byte period; + + public WorkingPeriodReply(byte[] bytes) { + super(bytes); + + this.actionType = bytes[3]; + this.period = bytes[4]; + } + + /** + * Get the type of action + * + * @return 0 = query 1 = set mode + */ + public byte getActionType() { + return actionType; + } + + /** + * Get the set working period + * + * @return working period set on the sensor + */ + public byte getPeriod() { + return period; + } + + @Override + public String toString() { + return "WorkingPeriodReply: [Period=" + this.period + "]"; + } +} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/binding/binding.xml b/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/binding/binding.xml new file mode 100644 index 0000000000000..2761c8a57e4e1 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/binding/binding.xml @@ -0,0 +1,10 @@ + + + + NovaFineDust Binding + This is the binding for Nova Fitness Fine Dust SDS011 sensor. + Stefan Triller + + diff --git a/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml new file mode 100644 index 0000000000000..1c35f5ea4e237 --- /dev/null +++ b/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml @@ -0,0 +1,57 @@ + + + + + + Nova SDS011 Fine Dust Sensor connected via USB + + + + + + + + + serial-port + + USB port the device is connected to i.e. /dev/ttyUSB0 + + + true + + Reporting is strongly recommended to increase sensor lifetime + + + 1 + true + + Device will report every x minutes and sleep for x*60 - 30 seconds afterwards, 0 = as fast as possible without sleep + + + 10 + true + + Device will be polled every x seconds (polling is not recommended) + + + + + + + Number:Density + + The PM 2.5 value + + + + + Number:Density + + The PM 10 value + + + + diff --git a/bundles/pom.xml b/bundles/pom.xml index 5f4cf3f12b6e7..0aa769c804550 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -168,6 +168,7 @@ org.openhab.binding.nibeuplink org.openhab.binding.nikobus org.openhab.binding.nikohomecontrol + org.openhab.binding.novafinedust org.openhab.binding.ntp org.openhab.binding.nuki org.openhab.binding.oceanic From bde3d2b7dce707a863eafa496bd10e55b6dc7cd2 Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Sat, 23 May 2020 15:01:50 +0200 Subject: [PATCH 02/13] Address review comments Signed-off-by: Stefan Triller --- .../README.md | 2 +- .../org.openhab.binding.novafinedust/pom.xml | 2 +- .../novafinedust/internal/SDS011Handler.java | 28 ++++--------------- .../sds011protocol/SDS011Communicator.java | 17 ++++++++--- 4 files changed, 21 insertions(+), 28 deletions(-) diff --git a/bundles/org.openhab.binding.novafinedust/README.md b/bundles/org.openhab.binding.novafinedust/README.md index 8bdcb0be539b4..51426f2b807d2 100644 --- a/bundles/org.openhab.binding.novafinedust/README.md +++ b/bundles/org.openhab.binding.novafinedust/README.md @@ -31,7 +31,7 @@ A full overview about the parameters of the `SDS011` thing is given in the follo | parameter name | mandatory | description | |-------------------|-----------|---------------------------------------------------------------------------------------| -| port | yes | the port the sensor is connected to, i.e. /detv/ttyUSB0. | +| port | yes | the port the sensor is connected to, i.e. /dev/ttyUSB0. | | reporting | yes | whether the reporting mode (value=true) or polling mode should be used. | | reportingInterval | no | the time in minutes between reportings from the sensor (default=1, min=0, max=30). | | pollingInterval | no | the time in seconds between data polls from the device. (default=10, min=3, max=3600) | diff --git a/bundles/org.openhab.binding.novafinedust/pom.xml b/bundles/org.openhab.binding.novafinedust/pom.xml index 66de5f6112bca..06f7b28af079a 100644 --- a/bundles/org.openhab.binding.novafinedust/pom.xml +++ b/bundles/org.openhab.binding.novafinedust/pom.xml @@ -6,7 +6,7 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 2.5.5-SNAPSHOT + 2.5.6-SNAPSHOT org.openhab.binding.novafinedust diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java index f5ae09596f21c..8be18dc43bfd6 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java @@ -15,8 +15,6 @@ import java.io.IOException; import java.time.Duration; import java.time.ZonedDateTime; -import java.time.chrono.ChronoZonedDateTime; -import java.time.temporal.Temporal; import java.util.TooManyListenersException; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; @@ -61,11 +59,11 @@ public class SDS011Handler extends BaseThingHandler { private @Nullable ScheduledFuture pollingJob; private @Nullable ScheduledFuture connectionMonitor; - private Temporal lastCommunication = ZonedDateTime.now(); + private ZonedDateTime lastCommunication = ZonedDateTime.now(); // initialize timeBetweenDataShouldArrive with a large number private Duration timeBetweenDataShouldArrive = Duration.ofDays(1); - private final Duration dataCanBeLateTollerance = Duration.ofSeconds(5); + private final Duration dataCanBeLateTolerance = Duration.ofSeconds(5); public SDS011Handler(Thing thing, SerialPortManager serialPortManager) { super(thing); @@ -159,20 +157,6 @@ private boolean validateConfiguration() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!"); return false; } - - if (config.reporting) { - if (config.reportingInterval < 0 || config.reportingInterval > 30) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, - "Reporting interval has to be between 0 and 30 minutes"); - return false; - } - } else { - if (config.pollingInterval < 3 || config.pollingInterval > 3600) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, - "Polling interval has to be between 3 and 3600 seconds"); - return false; - } - } return true; } @@ -218,10 +202,10 @@ public void updateChannels(SensorMeasuredDataReply sensorData) { private void verifyIfStillConnected() { ZonedDateTime now = ZonedDateTime.now(); - Temporal lastData = lastCommunication.plus(timeBetweenDataShouldArrive).plus(dataCanBeLateTollerance); - if (now.isAfter((ChronoZonedDateTime) lastData)) { + ZonedDateTime lastData = lastCommunication.plus(timeBetweenDataShouldArrive).plus(dataCanBeLateTolerance); + if (now.isAfter(lastData)) { logger.debug("Check Alive timer: Timeout: lastCommunication={}, interval={}, tollerance={}", - lastCommunication, timeBetweenDataShouldArrive, dataCanBeLateTollerance); + lastCommunication, timeBetweenDataShouldArrive, dataCanBeLateTolerance); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "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 @@ -229,7 +213,7 @@ private void verifyIfStillConnected() { dispose(); } else { logger.trace("Check Alive timer: All OK: lastCommunication={}, interval={}, tollerance={}", - lastCommunication, timeBetweenDataShouldArrive, dataCanBeLateTollerance); + lastCommunication, timeBetweenDataShouldArrive, dataCanBeLateTolerance); } } diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java index 8a1dc4f8063e2..6463a69890db7 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java @@ -19,7 +19,6 @@ import java.util.Arrays; import java.util.TooManyListenersException; -import org.apache.commons.io.IOUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.smarthome.io.transport.serial.PortInUseException; @@ -253,7 +252,7 @@ public void serialEvent(SerialPortEvent event) { reply = readReply(); logger.debug("Got data from sensor: {}", reply); } catch (IOException e) { - logger.error("Could not read available data from the serial port", e); + logger.warn("Could not read available data from the serial port", e); } if (reply instanceof SensorMeasuredDataReply) { SensorMeasuredDataReply sensorData = (SensorMeasuredDataReply) reply; @@ -281,7 +280,17 @@ public void dispose() { serialPort = null; } } - IOUtils.closeQuietly(inputStream); - IOUtils.closeQuietly(outputStream); + + try { + inputStream.close(); + } catch (IOException e) { + logger.debug("Error while closing the input stream: {}", e.getMessage()); + } + + try { + outputStream.close(); + } catch (IOException e) { + logger.debug("Error while closing the output stream: {}", e.getMessage()); + } } } From 0d2971b437b5c6dffb681d35908a946d857530be Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Sat, 23 May 2020 18:51:08 +0200 Subject: [PATCH 03/13] Address further review comments Signed-off-by: Stefan Triller --- .../internal/sds011protocol/SDS011Communicator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java index 6463a69890db7..a65c8ee434f96 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java @@ -252,7 +252,7 @@ public void serialEvent(SerialPortEvent event) { reply = readReply(); logger.debug("Got data from sensor: {}", reply); } catch (IOException e) { - logger.warn("Could not read available data from the serial port", e); + logger.warn("Could not read available data from the serial port: {}", e.getMessage()); } if (reply instanceof SensorMeasuredDataReply) { SensorMeasuredDataReply sensorData = (SensorMeasuredDataReply) reply; From 127335070aeacdb456c41b6c1ddc1c3ba4a27b6e Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Mon, 25 May 2020 18:05:38 +0200 Subject: [PATCH 04/13] Address review comments from second reviewer Signed-off-by: Stefan Triller --- .../internal/NovaFineDustHandlerFactory.java | 17 ++++---- .../novafinedust/internal/SDS011Handler.java | 25 +++++++++-- .../internal/sds011protocol/Helper.java | 42 ------------------- .../sds011protocol/SDS011Communicator.java | 11 ++--- .../internal/sds011protocol/WorkMode.java | 3 ++ .../messages/CommandMessage.java | 6 +-- .../messages/SensorMeasuredDataReply.java | 8 ++-- .../sds011protocol/messages/SensorReply.java | 4 +- .../resources/ESH-INF/thing/thing-types.xml | 10 ++--- 9 files changed, 51 insertions(+), 75 deletions(-) delete mode 100644 bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Helper.java diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustHandlerFactory.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustHandlerFactory.java index f5ff831998fd2..860c4617acb99 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustHandlerFactory.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/NovaFineDustHandlerFactory.java @@ -25,6 +25,7 @@ import org.eclipse.smarthome.core.thing.binding.ThingHandler; import org.eclipse.smarthome.core.thing.binding.ThingHandlerFactory; import org.eclipse.smarthome.io.transport.serial.SerialPortManager; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -40,7 +41,12 @@ public class NovaFineDustHandlerFactory extends BaseThingHandlerFactory { private static final Set SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_SDS011); - private @NonNullByDefault({}) SerialPortManager serialPortManager; + private final SerialPortManager serialPortManager; + + @Activate + public NovaFineDustHandlerFactory(@Reference SerialPortManager serialPortManager) { + this.serialPortManager = serialPortManager; + } @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -57,13 +63,4 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return null; } - - @Reference - protected void setSerialPortManager(final SerialPortManager serialPortManager) { - this.serialPortManager = serialPortManager; - } - - protected void unsetSerialPortManager(final SerialPortManager serialPortManager) { - this.serialPortManager = null; - } } diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java index 8be18dc43bfd6..b43c28dbd7107 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java @@ -30,6 +30,7 @@ import org.eclipse.smarthome.core.thing.ThingStatusDetail; import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.RefreshType; import org.eclipse.smarthome.io.transport.serial.PortInUseException; import org.eclipse.smarthome.io.transport.serial.SerialPortIdentifier; import org.eclipse.smarthome.io.transport.serial.SerialPortManager; @@ -65,6 +66,10 @@ public class SDS011Handler extends BaseThingHandler { private Duration timeBetweenDataShouldArrive = Duration.ofDays(1); private final Duration dataCanBeLateTolerance = Duration.ofSeconds(5); + // cached values fro refresh command + private @Nullable QuantityType statePM10; + private @Nullable QuantityType statePM25; + public SDS011Handler(Thing thing, SerialPortManager serialPortManager) { super(thing); this.serialPortManager = serialPortManager; @@ -72,8 +77,15 @@ public SDS011Handler(Thing thing, SerialPortManager serialPortManager) { @Override public void handleCommand(ChannelUID channelUID, Command command) { - // we do not support refreshing as values are either reported by the device or polled from the device in fixed - // intervals + // refresh channels with last received values from cache + if (RefreshType.REFRESH.equals(command)) { + if (NovaFineDustBindingConstants.CHANNEL_PM25.equals(channelUID.getId()) && statePM25 != null) { + updateState(NovaFineDustBindingConstants.CHANNEL_PM25, statePM25); + } + if (NovaFineDustBindingConstants.CHANNEL_PM10.equals(channelUID.getId()) && statePM10 != null) { + updateState(NovaFineDustBindingConstants.CHANNEL_PM10, statePM10); + } + } } @Override @@ -162,12 +174,12 @@ private boolean validateConfiguration() { @Override public void dispose() { - if (pollingJob != null && !pollingJob.isCancelled()) { + if (pollingJob != null) { pollingJob.cancel(true); pollingJob = null; } - if (connectionMonitor != null && !connectionMonitor.isCancelled()) { + if (connectionMonitor != null) { connectionMonitor.cancel(true); connectionMonitor = null; } @@ -175,6 +187,9 @@ public void dispose() { if (communicator != null) { scheduler.schedule(() -> communicator.dispose(), 0, TimeUnit.SECONDS); } + + this.statePM10 = null; + this.statePM25 = null; } /** @@ -189,10 +204,12 @@ public void updateChannels(SensorMeasuredDataReply sensorData) { QuantityType statePM10 = new QuantityType<>(sensorData.getPm10(), SmartHomeUnits.MICROGRAM_PER_CUBICMETRE); updateState(NovaFineDustBindingConstants.CHANNEL_PM10, statePM10); + this.statePM10 = statePM10; QuantityType statePM25 = new QuantityType<>(sensorData.getPm25(), SmartHomeUnits.MICROGRAM_PER_CUBICMETRE); updateState(NovaFineDustBindingConstants.CHANNEL_PM25, statePM25); + this.statePM25 = statePM25; updateStatus(ThingStatus.ONLINE); } diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Helper.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Helper.java deleted file mode 100644 index 6e8fbe67e7dd7..0000000000000 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/Helper.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2010-2020 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.novafinedust.internal.sds011protocol; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * Class with useful utility functions - * - * @author Stefan Triller - Initial contribution - * - */ -@NonNullByDefault -public class Helper { - - private Helper() { - } - - /** - * Converts a byte array to a hexadecimal string, handy for printing - * - * @param bytes the byte array to be converted - * @return a String describing the byte array in hexadecimal values - */ - public static String toHexString(byte[] bytes) { - StringBuilder sb = new StringBuilder(); - for (byte b : bytes) { - sb.append(String.format("%02X ", b)); - } - return sb.toString(); - } -} diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java index a65c8ee434f96..3bccda15bacb1 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.util.HexUtils; import org.eclipse.smarthome.io.transport.serial.PortInUseException; import org.eclipse.smarthome.io.transport.serial.SerialPort; import org.eclipse.smarthome.io.transport.serial.SerialPortEvent; @@ -54,8 +55,8 @@ public class SDS011Communicator implements SerialPortEventListener { private SDS011Handler thingHandler; private @NonNullByDefault({}) SerialPort serialPort; - private @NonNullByDefault({}) OutputStream outputStream; - private @NonNullByDefault({}) InputStream inputStream; + private @Nullable OutputStream outputStream; + private @Nullable InputStream inputStream; public SDS011Communicator(SDS011Handler thingHandler, SerialPortIdentifier portId) { this.thingHandler = thingHandler; @@ -112,7 +113,7 @@ public boolean initialize(WorkMode mode, Duration interval) private @Nullable SensorReply sendCommand(CommandMessage message) throws IOException { byte[] commandData = message.getBytes(); if (logger.isDebugEnabled()) { - logger.debug("Will send command: {} ({})", Helper.toHexString(commandData), Arrays.toString(commandData)); + logger.debug("Will send command: {} ({})", HexUtils.bytesToHex(commandData), Arrays.toString(commandData)); } outputStream.write(commandData, 0, commandData.length); outputStream.flush(); @@ -216,7 +217,7 @@ public void requestSensorData() throws IOException { CommandMessage m = new CommandMessage(Command.REQUEST_DATA, new byte[] {}); byte[] data = m.getBytes(); if (logger.isDebugEnabled()) { - logger.debug("Requesting sensor data, will send: {}", Helper.toHexString(data)); + logger.debug("Requesting sensor data, will send: {}", HexUtils.bytesToHex(data)); } outputStream.write(data, 0, data.length); } @@ -233,7 +234,7 @@ public void requestSensorData() throws IOException { int remainingBytesRead = inputStream.read(readBuffer, 1, Constants.REPLY_LENGTH - 1); if (logger.isDebugEnabled()) { logger.debug("Read remaining bytes: {}, full reply={}", remainingBytesRead, - Helper.toHexString(readBuffer)); + HexUtils.bytesToHex(readBuffer)); } return ReplyFactory.create(readBuffer); } diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/WorkMode.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/WorkMode.java index 9b92bb62b1261..2b4eaa30a318a 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/WorkMode.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/WorkMode.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.novafinedust.internal.sds011protocol; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * Enum for the different sensor modes * * @author Stefan Triller - Initial contribution * */ +@NonNullByDefault public enum WorkMode { REPORTING, POLLING diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java index ce725d93353e5..44a94c6b5fc79 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java @@ -15,7 +15,7 @@ import java.io.ByteArrayOutputStream; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.novafinedust.internal.sds011protocol.Helper; +import org.eclipse.smarthome.core.util.HexUtils; /** * Message to be send to the device @@ -90,8 +90,8 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Message: "); sb.append("Command=" + firstDataByte); - sb.append(" Target Device=" + Helper.toHexString(targetDevice)); - sb.append(" Payload=" + Helper.toHexString(payLoad)); + sb.append(" Target Device=" + HexUtils.bytesToHex(targetDevice)); + sb.append(" Payload=" + HexUtils.bytesToHex(payLoad)); return sb.toString(); } } diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java index 8908c1a1fa08d..2ecb732819026 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java @@ -13,7 +13,7 @@ package org.openhab.binding.novafinedust.internal.sds011protocol.messages; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.novafinedust.internal.sds011protocol.Helper; +import org.eclipse.smarthome.core.util.HexUtils; /** * Class containing the actual measured values from the sensor @@ -72,8 +72,8 @@ public float getPm10() { public String toString() { return String.format( "SensorMeasuredDataReply: [valid=%s, PM 2.5=%.1f, PM 10=%.1f, sourceDevice=%s, pm25lowHigh=(%s) pm10lowHigh=(%s)]", - isValidData(), getPm25(), getPm10(), Helper.toHexString(new byte[] { deviceID[0], deviceID[1] }), - Helper.toHexString(new byte[] { pm25lowByte, pm25highByte }), - Helper.toHexString(new byte[] { pm10lowByte, pm10highByte })); + isValidData(), getPm25(), getPm10(), HexUtils.bytesToHex(new byte[] { deviceID[0], deviceID[1] }), + HexUtils.bytesToHex(new byte[] { pm25lowByte, pm25highByte }), + HexUtils.bytesToHex(new byte[] { pm10lowByte, pm10highByte })); } } diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java index f5ccf09c53d51..08dd2bc033a81 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java @@ -15,7 +15,7 @@ import java.util.Arrays; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.novafinedust.internal.sds011protocol.Helper; +import org.eclipse.smarthome.core.util.HexUtils; /** * Base class holding information sent by the sensor to us @@ -84,6 +84,6 @@ protected byte calculateChecksum() { @Override public String toString() { return String.format("GeneralReply: [head=%x, commandID=%x, payload=%s, deviceID=%s, checksum=%s, tail=%x", - header, commandID, Helper.toHexString(payLoad), Helper.toHexString(deviceID), checksum, messageTail); + header, commandID, HexUtils.bytesToHex(payLoad), HexUtils.bytesToHex(deviceID), checksum, messageTail); } } diff --git a/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml index 1c35f5ea4e237..5d1ba91a40029 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml @@ -21,19 +21,19 @@ true - + Reporting is strongly recommended to increase sensor lifetime - + 1 true - + Device will report every x minutes and sleep for x*60 - 30 seconds afterwards, 0 = as fast as possible without sleep - + 10 true - + Device will be polled every x seconds (polling is not recommended) From 78275e3df58da1567b75c9b1b2b6ef36af1bc404 Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Mon, 25 May 2020 18:26:13 +0200 Subject: [PATCH 05/13] Fix compilation error Signed-off-by: Stefan Triller --- .../novafinedust/internal/SDS011Handler.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java index b43c28dbd7107..512a9127e7e3e 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java @@ -31,6 +31,8 @@ import org.eclipse.smarthome.core.thing.binding.BaseThingHandler; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.core.types.RefreshType; +import org.eclipse.smarthome.core.types.State; +import org.eclipse.smarthome.core.types.UnDefType; import org.eclipse.smarthome.io.transport.serial.PortInUseException; import org.eclipse.smarthome.io.transport.serial.SerialPortIdentifier; import org.eclipse.smarthome.io.transport.serial.SerialPortManager; @@ -67,8 +69,8 @@ public class SDS011Handler extends BaseThingHandler { private final Duration dataCanBeLateTolerance = Duration.ofSeconds(5); // cached values fro refresh command - private @Nullable QuantityType statePM10; - private @Nullable QuantityType statePM25; + private State statePM10 = UnDefType.UNDEF; + private State statePM25 = UnDefType.UNDEF; public SDS011Handler(Thing thing, SerialPortManager serialPortManager) { super(thing); @@ -79,10 +81,10 @@ public SDS011Handler(Thing thing, SerialPortManager serialPortManager) { public void handleCommand(ChannelUID channelUID, Command command) { // refresh channels with last received values from cache if (RefreshType.REFRESH.equals(command)) { - if (NovaFineDustBindingConstants.CHANNEL_PM25.equals(channelUID.getId()) && statePM25 != null) { + if (NovaFineDustBindingConstants.CHANNEL_PM25.equals(channelUID.getId()) && statePM25 != UnDefType.UNDEF) { updateState(NovaFineDustBindingConstants.CHANNEL_PM25, statePM25); } - if (NovaFineDustBindingConstants.CHANNEL_PM10.equals(channelUID.getId()) && statePM10 != null) { + if (NovaFineDustBindingConstants.CHANNEL_PM10.equals(channelUID.getId()) && statePM10 != UnDefType.UNDEF) { updateState(NovaFineDustBindingConstants.CHANNEL_PM10, statePM10); } } @@ -188,8 +190,8 @@ public void dispose() { scheduler.schedule(() -> communicator.dispose(), 0, TimeUnit.SECONDS); } - this.statePM10 = null; - this.statePM25 = null; + this.statePM10 = UnDefType.UNDEF; + this.statePM25 = UnDefType.UNDEF; } /** From 3b594394ec2080ca6bb8de2fd3710720612a5d20 Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Tue, 26 May 2020 18:21:26 +0200 Subject: [PATCH 06/13] Revert config validation Signed-off-by: Stefan Triller --- .../novafinedust/internal/SDS011Handler.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java index 512a9127e7e3e..74665ca9b2886 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java @@ -171,6 +171,19 @@ private boolean validateConfiguration() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!"); return false; } + if (config.reporting) { + if (config.reportingInterval < 0 || config.reportingInterval > 30) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Reporting interval has to be between 0 and 30 minutes"); + return false; + } + } else { + if (config.pollingInterval < 3 || config.pollingInterval > 3600) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Polling interval has to be between 3 and 3600 seconds"); + return false; + } + } return true; } From 9ffd0c160059738fff9299756eeb48e25f349c93 Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Wed, 27 May 2020 17:46:32 +0200 Subject: [PATCH 07/13] Address further review comments Signed-off-by: Stefan Triller --- .../internal/sds011protocol/SDS011Communicator.java | 5 +++-- .../sds011protocol/messages/CommandMessage.java | 2 +- .../messages/SensorMeasuredDataReply.java | 8 ++++---- .../sds011protocol/messages/SensorReply.java | 12 ++++++------ 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java index 3bccda15bacb1..a8116f71e999f 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java @@ -115,7 +115,7 @@ public boolean initialize(WorkMode mode, Duration interval) if (logger.isDebugEnabled()) { logger.debug("Will send command: {} ({})", HexUtils.bytesToHex(commandData), Arrays.toString(commandData)); } - outputStream.write(commandData, 0, commandData.length); + outputStream.write(commandData); outputStream.flush(); try { // Give the sensor some time to handle the command @@ -219,7 +219,8 @@ public void requestSensorData() throws IOException { if (logger.isDebugEnabled()) { logger.debug("Requesting sensor data, will send: {}", HexUtils.bytesToHex(data)); } - outputStream.write(data, 0, data.length); + outputStream.write(data); + outputStream.flush(); } private @Nullable SensorReply readReply() throws IOException { diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java index 44a94c6b5fc79..d0e0caff34506 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/CommandMessage.java @@ -31,7 +31,7 @@ public class CommandMessage { private static final int DATA_BYTES_AFTER_FIRST_DATA_BYTE = 12; - private byte firstDataByte; + private final byte firstDataByte; private byte[] payLoad = new byte[DATA_BYTES_AFTER_FIRST_DATA_BYTE]; private byte[] targetDevice = new byte[] { -1, -1 }; // FF FF = all devices diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java index 2ecb732819026..05ee374e4c4eb 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorMeasuredDataReply.java @@ -23,10 +23,10 @@ */ @NonNullByDefault public class SensorMeasuredDataReply extends SensorReply { - private byte pm25lowByte; - private byte pm25highByte; - private byte pm10lowByte; - private byte pm10highByte; + private final byte pm25lowByte; + private final byte pm25highByte; + private final byte pm10lowByte; + private final byte pm10highByte; /** * Create a new instance by parsing the given 10 bytes. diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java index 08dd2bc033a81..f2f0d23e916e8 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorReply.java @@ -26,12 +26,12 @@ @NonNullByDefault public class SensorReply { - protected byte header; - protected byte commandID; - protected byte[] payLoad = new byte[4]; - protected byte[] deviceID = new byte[2]; - protected byte checksum; - protected byte messageTail; + protected final byte header; + protected final byte commandID; + protected final byte[] payLoad; + protected final byte[] deviceID; + protected final byte checksum; + protected final byte messageTail; /** * Creates the container for data received from the sensor From 57ed8c622051f16ef63902c4af6a810b1908ee83 Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Wed, 27 May 2020 23:22:10 +0200 Subject: [PATCH 08/13] Addressed further review comments Signed-off-by: Stefan Triller --- .../binding/novafinedust/internal/SDS011Handler.java | 4 +--- .../internal/sds011protocol/messages/ModeReply.java | 4 ++-- .../sds011protocol/messages/SensorFirmwareReply.java | 6 +++--- .../internal/sds011protocol/messages/SleepReply.java | 4 ++-- .../sds011protocol/messages/WorkingPeriodReply.java | 4 ++-- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java index 74665ca9b2886..c415f54e9bf3e 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java @@ -199,9 +199,7 @@ public void dispose() { connectionMonitor = null; } - if (communicator != null) { - scheduler.schedule(() -> communicator.dispose(), 0, TimeUnit.SECONDS); - } + communicator.dispose(); this.statePM10 = UnDefType.UNDEF; this.statePM25 = UnDefType.UNDEF; diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/ModeReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/ModeReply.java index dad004c87a33f..9c52d14807498 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/ModeReply.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/ModeReply.java @@ -24,8 +24,8 @@ @NonNullByDefault public class ModeReply extends SensorReply { - private byte actionType; - private WorkMode mode; + private final byte actionType; + private final WorkMode mode; public ModeReply(byte[] bytes) { super(bytes); diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorFirmwareReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorFirmwareReply.java index 5ed27d1c2a87a..3e15c541c9030 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorFirmwareReply.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SensorFirmwareReply.java @@ -23,9 +23,9 @@ @NonNullByDefault public class SensorFirmwareReply extends SensorReply { - private byte year; - private byte month; - private byte day; + private final byte year; + private final byte month; + private final byte day; public SensorFirmwareReply(byte[] receivedData) { super(receivedData); diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SleepReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SleepReply.java index 2d60dd31735fe..19b8d250c7ba7 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SleepReply.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/SleepReply.java @@ -23,8 +23,8 @@ @NonNullByDefault public class SleepReply extends SensorReply { - private byte actionType; - private byte sleep; + private final byte actionType; + private final byte sleep; public SleepReply(byte[] bytes) { super(bytes); diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/WorkingPeriodReply.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/WorkingPeriodReply.java index 96b3e3d497527..b882243edfbff 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/WorkingPeriodReply.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/messages/WorkingPeriodReply.java @@ -23,8 +23,8 @@ @NonNullByDefault public class WorkingPeriodReply extends SensorReply { - private byte actionType; - private byte period; + private final byte actionType; + private final byte period; public WorkingPeriodReply(byte[] bytes) { super(bytes); From 36cfe9f558a43f29e09c5da567dc9c0f3479d728 Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Fri, 29 May 2020 08:49:54 +0200 Subject: [PATCH 09/13] More review comments Signed-off-by: Stefan Triller --- .../novafinedust/internal/SDS011Handler.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java index c415f54e9bf3e..0979b047fc0ea 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java @@ -56,8 +56,8 @@ public class SDS011Handler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(SDS011Handler.class); private final SerialPortManager serialPortManager; - private @NonNullByDefault({}) NovaFineDustConfiguration config; - private @NonNullByDefault({}) SDS011Communicator communicator; + private @Nullable NovaFineDustConfiguration config; + private @Nullable SDS011Communicator communicator; private @Nullable ScheduledFuture pollingJob; private @Nullable ScheduledFuture connectionMonitor; @@ -167,6 +167,12 @@ private void initializeCommunicator(WorkMode mode, Duration interval) { } private boolean validateConfiguration() { + if (config == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, + "Configuration could not be parsed"); + return false; + } + if (config.port == null || config.port.isEmpty()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!"); return false; @@ -199,7 +205,9 @@ public void dispose() { connectionMonitor = null; } - communicator.dispose(); + if (communicator != null) { + communicator.dispose(); + } this.statePM10 = UnDefType.UNDEF; this.statePM25 = UnDefType.UNDEF; From e685099b38e6dda40cf3326a1182d394983790ac Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Sun, 31 May 2020 12:37:38 +0200 Subject: [PATCH 10/13] Make null analysis happy Signed-off-by: Stefan Triller --- .../novafinedust/internal/SDS011Handler.java | 42 ++++++++++-------- .../sds011protocol/SDS011Communicator.java | 43 +++++++++++++------ 2 files changed, 53 insertions(+), 32 deletions(-) diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java index 0979b047fc0ea..adaafa13864e8 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java @@ -56,7 +56,7 @@ public class SDS011Handler extends BaseThingHandler { private final Logger logger = LoggerFactory.getLogger(SDS011Handler.class); private final SerialPortManager serialPortManager; - private @Nullable NovaFineDustConfiguration config; + private NovaFineDustConfiguration config = new NovaFineDustConfiguration(); private @Nullable SDS011Communicator communicator; private @Nullable ScheduledFuture pollingJob; @@ -68,7 +68,7 @@ public class SDS011Handler extends BaseThingHandler { private Duration timeBetweenDataShouldArrive = Duration.ofDays(1); private final Duration dataCanBeLateTolerance = Duration.ofSeconds(5); - // cached values fro refresh command + // cached values for refresh command private State statePM10 = UnDefType.UNDEF; private State statePM25 = UnDefType.UNDEF; @@ -125,9 +125,16 @@ public void initialize() { } private void initializeCommunicator(WorkMode mode, Duration interval) { + SDS011Communicator localCommunicator = communicator; + if (localCommunicator == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, + "Could not create communicator instance"); + return; + } + boolean initSuccessful = false; try { - initSuccessful = communicator.initialize(mode, interval); + initSuccessful = localCommunicator.initialize(mode, interval); } catch (final IOException ex) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "I/O error!"); return; @@ -151,7 +158,7 @@ private void initializeCommunicator(WorkMode mode, Duration interval) { if (mode == WorkMode.POLLING) { pollingJob = scheduler.scheduleWithFixedDelay(() -> { try { - communicator.requestSensorData(); + localCommunicator.requestSensorData(); } catch (IOException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Cannot query data from device"); @@ -167,13 +174,7 @@ private void initializeCommunicator(WorkMode mode, Duration interval) { } private boolean validateConfiguration() { - if (config == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, - "Configuration could not be parsed"); - return false; - } - - if (config.port == null || config.port.isEmpty()) { + if (config.port.isEmpty()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.CONFIGURATION_ERROR, "Port must be set!"); return false; } @@ -195,18 +196,21 @@ private boolean validateConfiguration() { @Override public void dispose() { - if (pollingJob != null) { - pollingJob.cancel(true); - pollingJob = null; + ScheduledFuture localPollingJob = this.pollingJob; + if (localPollingJob != null) { + localPollingJob.cancel(true); + this.pollingJob = null; } - if (connectionMonitor != null) { - connectionMonitor.cancel(true); - connectionMonitor = null; + ScheduledFuture localConnectionMonitor = this.connectionMonitor; + if (localConnectionMonitor != null) { + localConnectionMonitor.cancel(true); + this.connectionMonitor = null; } - if (communicator != null) { - communicator.dispose(); + SDS011Communicator localCommunicator = this.communicator; + if (localCommunicator != null) { + localCommunicator.dispose(); } this.statePM10 = UnDefType.UNDEF; diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java index a8116f71e999f..9e3fd9b960cb5 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/sds011protocol/SDS011Communicator.java @@ -53,7 +53,7 @@ public class SDS011Communicator implements SerialPortEventListener { private SerialPortIdentifier portId; private SDS011Handler thingHandler; - private @NonNullByDefault({}) SerialPort serialPort; + private @Nullable SerialPort serialPort; private @Nullable OutputStream outputStream; private @Nullable InputStream inputStream; @@ -115,8 +115,9 @@ public boolean initialize(WorkMode mode, Duration interval) if (logger.isDebugEnabled()) { logger.debug("Will send command: {} ({})", HexUtils.bytesToHex(commandData), Arrays.toString(commandData)); } - outputStream.write(commandData); - outputStream.flush(); + + write(commandData); + try { // Give the sensor some time to handle the command Thread.sleep(500); @@ -133,6 +134,14 @@ public boolean initialize(WorkMode mode, Duration interval) return reply; } + private void write(byte[] commandData) throws IOException { + OutputStream localOutputStream = outputStream; + if (localOutputStream != null) { + localOutputStream.write(commandData); + localOutputStream.flush(); + } + } + private boolean setWorkingPeriod(byte period) throws IOException { CommandMessage m = new CommandMessage(Command.WORKING_PERIOD, new byte[] { Constants.SET_ACTION, period }); logger.debug("Sending work period: {}", period); @@ -219,20 +228,21 @@ public void requestSensorData() throws IOException { if (logger.isDebugEnabled()) { logger.debug("Requesting sensor data, will send: {}", HexUtils.bytesToHex(data)); } - outputStream.write(data); - outputStream.flush(); + write(data); } private @Nullable SensorReply readReply() throws IOException { byte[] readBuffer = new byte[Constants.REPLY_LENGTH]; + InputStream localInpuStream = inputStream; + int b = -1; - if (inputStream.available() > 0) { - while ((b = inputStream.read()) != Constants.MESSAGE_START_AS_INT) { + if (localInpuStream != null && localInpuStream.available() > 0) { + while ((b = localInpuStream.read()) != Constants.MESSAGE_START_AS_INT) { logger.debug("Trying to find first reply byte now..."); } readBuffer[0] = (byte) b; - int remainingBytesRead = inputStream.read(readBuffer, 1, Constants.REPLY_LENGTH - 1); + int remainingBytesRead = localInpuStream.read(readBuffer, 1, Constants.REPLY_LENGTH - 1); if (logger.isDebugEnabled()) { logger.debug("Read remaining bytes: {}, full reply={}", remainingBytesRead, HexUtils.bytesToHex(readBuffer)); @@ -269,7 +279,8 @@ public void serialEvent(SerialPortEvent event) { * Shutdown the communication, i.e. send the device to sleep and close the serial port */ public void dispose() { - if (serialPort != null) { + SerialPort localSerialPort = serialPort; + if (localSerialPort != null) { try { // send the device to sleep to preserve power and extend the lifetime of the sensor sendSleep(true); @@ -277,20 +288,26 @@ public void dispose() { // ignore because we are shutting down anyway logger.debug("Exception while disposing communicator (will ignore it)", e); } finally { - serialPort.removeEventListener(); - serialPort.close(); + localSerialPort.removeEventListener(); + localSerialPort.close(); serialPort = null; } } try { - inputStream.close(); + InputStream localInputStream = inputStream; + if (localInputStream != null) { + localInputStream.close(); + } } catch (IOException e) { logger.debug("Error while closing the input stream: {}", e.getMessage()); } try { - outputStream.close(); + OutputStream localOutputStream = outputStream; + if (localOutputStream != null) { + localOutputStream.close(); + } } catch (IOException e) { logger.debug("Error while closing the output stream: {}", e.getMessage()); } From d94ddb4ba25efe4248d2a174b64f1be2db773484 Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Tue, 2 Jun 2020 20:34:46 +0200 Subject: [PATCH 11/13] Small review changes Signed-off-by: Stefan Triller --- .../binding/novafinedust/internal/SDS011Handler.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java index adaafa13864e8..bf82a39267c17 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java +++ b/bundles/org.openhab.binding.novafinedust/src/main/java/org/openhab/binding/novafinedust/internal/SDS011Handler.java @@ -111,12 +111,10 @@ public void initialize() { if (config.reporting) { timeBetweenDataShouldArrive = Duration.ofMinutes(config.reportingInterval); - scheduler.schedule(() -> initializeCommunicator(WorkMode.REPORTING, timeBetweenDataShouldArrive), 0, - TimeUnit.SECONDS); + scheduler.submit(() -> initializeCommunicator(WorkMode.REPORTING, timeBetweenDataShouldArrive)); } else { timeBetweenDataShouldArrive = Duration.ofSeconds(config.pollingInterval); - scheduler.schedule(() -> initializeCommunicator(WorkMode.POLLING, timeBetweenDataShouldArrive), 0, - TimeUnit.SECONDS); + scheduler.submit(() -> initializeCommunicator(WorkMode.POLLING, timeBetweenDataShouldArrive)); } Duration connectionMonitorStartDelay = timeBetweenDataShouldArrive.plus(CONNECTION_MONITOR_START_DELAY_OFFSET); From 00642f2333a2ad2766f8a29175e21ba142b3beca Mon Sep 17 00:00:00 2001 From: t2000 Date: Sun, 14 Jun 2020 11:45:29 +0200 Subject: [PATCH 12/13] Apply suggestions from code review Thanks Kai for your suggestions, I have accepted them all and will take care of the few others in an additional commit. Co-authored-by: Kai Kreuzer Signed-off-by: Stefan Triller --- bundles/org.openhab.binding.novafinedust/README.md | 9 +++++---- .../src/main/resources/ESH-INF/thing/thing-types.xml | 12 ++++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.novafinedust/README.md b/bundles/org.openhab.binding.novafinedust/README.md index 51426f2b807d2..845638ccf4952 100644 --- a/bundles/org.openhab.binding.novafinedust/README.md +++ b/bundles/org.openhab.binding.novafinedust/README.md @@ -1,17 +1,18 @@ # NovaFineDust Binding -This binding is for the fine dust sensor (PM Sensor) from Nova Fitness. Currently only one model is supported, the SDS011. +This binding is for the fine dust sensor (PM Sensor) from Nova Fitness. +Currently only one model is supported, the SDS011. It basically implements the protocol specified in [this document](https://cdn.sparkfun.com/assets/parts/1/2/2/7/5/Laser_Dust_Sensor_Control_Protocol_V1.3.pdf). One can measure the PM 2.5 and PM 10 values with this device. It comes very handy for detecting air pollution like neighbors firing their oven with wet wood etc. so one can deactivate the ventilation system. ## Supported Things -There is only one Thing for this binding is `SDS011`. +There is only one Thing type for this binding, which is `SDS011`. ## Discovery -There is no automatic discovery. The Thing has to be added manually via the `.things` configuration file or via (Paper) UI. +There is no automatic discovery. ## Thing Configuration @@ -75,4 +76,4 @@ sitemap demo label="Main Menu" ## Limitations -In theory once can have multiple sensors connected and distinguish them via their device ID. However, this is currently not implemented and the binding always configures any device and accepts data reportings from any device too. +In theory one can have multiple sensors connected and distinguish them via their device ID. However, this is currently not implemented and the binding always configures any device and accepts data reportings from any device too. diff --git a/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml b/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml index 5d1ba91a40029..b44f880cf0483 100644 --- a/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml +++ b/bundles/org.openhab.binding.novafinedust/src/main/resources/ESH-INF/thing/thing-types.xml @@ -21,19 +21,23 @@ true - + + + + + Reporting is strongly recommended to increase sensor lifetime 1 true - + Device will report every x minutes and sleep for x*60 - 30 seconds afterwards, 0 = as fast as possible without sleep 10 true - + Device will be polled every x seconds (polling is not recommended) @@ -49,7 +53,7 @@ Number:Density - + The PM 10 value From 4abf72848a9ffffb7e922053865ccb11260c113b Mon Sep 17 00:00:00 2001 From: Stefan Triller Date: Sun, 14 Jun 2020 11:57:09 +0200 Subject: [PATCH 13/13] README clarifications Signed-off-by: Stefan Triller --- bundles/org.openhab.binding.novafinedust/README.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.novafinedust/README.md b/bundles/org.openhab.binding.novafinedust/README.md index 845638ccf4952..b5b1e8ea0b593 100644 --- a/bundles/org.openhab.binding.novafinedust/README.md +++ b/bundles/org.openhab.binding.novafinedust/README.md @@ -4,7 +4,8 @@ This binding is for the fine dust sensor (PM Sensor) from Nova Fitness. Currently only one model is supported, the SDS011. It basically implements the protocol specified in [this document](https://cdn.sparkfun.com/assets/parts/1/2/2/7/5/Laser_Dust_Sensor_Control_Protocol_V1.3.pdf). -One can measure the PM 2.5 and PM 10 values with this device. It comes very handy for detecting air pollution like neighbors firing their oven with wet wood etc. so one can deactivate the ventilation system. +One can measure the PM 2.5 and PM 10 values with this device. +It comes very handy for detecting air pollution like neighbors firing their oven with wet wood etc. so one can deactivate the ventilation system. ## Supported Things @@ -20,11 +21,14 @@ There are 2 different working modes for the `SDS011` thing: Reporting and Pollin ### Reporting -This is the preferred mode and thus also configured as a default. In this mode the sensor wakes up every `reportingInterval` minutes, performs a measurement for 30 seconds and sleeps for `reportingInterval` minus 30 seconds. Remember: According to the [datasheet](https://www-sd-nf.oss-cn-beijing.aliyuncs.com/%E5%AE%98%E7%BD%91%E4%B8%8B%E8%BD%BD/SDS011%20laser%20PM2.5%20sensor%20specification-V1.4.pdf) the sensor has a lifetime of 8000 hours. Using a 0 as `reportingInterval` will make the sensor report its data as fast as possible. +This is the preferred mode and thus also configured as a default. +In this mode the sensor wakes up every `reportingInterval` minutes, performs a measurement for 30 seconds and sleeps for `reportingInterval` minus 30 seconds. +Remember: According to the [datasheet](https://www-sd-nf.oss-cn-beijing.aliyuncs.com/%E5%AE%98%E7%BD%91%E4%B8%8B%E8%BD%BD/SDS011%20laser%20PM2.5%20sensor%20specification-V1.4.pdf) the sensor has a lifetime of 8000 hours. Using a 0 as `reportingInterval` will make the sensor report its data as fast as possible. ### Polling -If one needs data in different intervals, i.e. not as fast as possible and not in intervals that are a multiple of full minutes, polling can be configured. The `pollingInterval` parameter specifies the time in seconds when data will be polled from the sensor. +If one needs data in different intervals, i.e. not as fast as possible and not in intervals that are a multiple of full minutes, polling can be configured. +The `pollingInterval` parameter specifies the time in seconds when data will be polled from the sensor. In addition to the mode one has to provide the port to which the device is connected. @@ -33,7 +37,7 @@ A full overview about the parameters of the `SDS011` thing is given in the follo | parameter name | mandatory | description | |-------------------|-----------|---------------------------------------------------------------------------------------| | port | yes | the port the sensor is connected to, i.e. /dev/ttyUSB0. | -| reporting | yes | whether the reporting mode (value=true) or polling mode should be used. | +| reporting | no | whether the reporting mode (value=true) or polling mode should be used. | | reportingInterval | no | the time in minutes between reportings from the sensor (default=1, min=0, max=30). | | pollingInterval | no | the time in seconds between data polls from the device. (default=10, min=3, max=3600) | @@ -77,3 +81,4 @@ sitemap demo label="Main Menu" ## Limitations In theory one can have multiple sensors connected and distinguish them via their device ID. However, this is currently not implemented and the binding always configures any device and accepts data reportings from any device too. +However, it is implemented that one can attach one sensor to one serial port, like `/dev/ttyUSB0` and a second sensor on a different serial port, like `/dev/ttyUSB1`.