From c79fdc5c4cb02d07dd1fa3523b511aebbea5ac0a Mon Sep 17 00:00:00 2001 From: joerg1985 <16140691+joerg1985@users.noreply.github.com> Date: Thu, 30 Jan 2025 12:30:55 +0100 Subject: [PATCH] [radoneye] Fixed the binding for HW v1 and v2 (#18125) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörg Sautter --- .../README.md | 10 +- .../internal/AbstractRadoneyeHandler.java | 271 ++++-------------- .../internal/RadoneyeBindingConstants.java | 23 +- .../radoneye/internal/RadoneyeDataParser.java | 32 +-- .../RadoneyeDiscoveryParticipant.java | 15 +- .../radoneye/internal/RadoneyeHandler.java | 26 +- .../OH-INF/i18n/bluetooth.properties | 2 + .../main/resources/OH-INF/thing/radoneye.xml | 13 +- .../resources/OH-INF/update/instructions.xml | 15 + .../radoneye/RadoneyeParserTest.java | 18 +- 10 files changed, 152 insertions(+), 273 deletions(-) create mode 100644 bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/update/instructions.xml diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/README.md b/bundles/org.openhab.binding.bluetooth.radoneye/README.md index da4289c5318..c870a790e0f 100644 --- a/bundles/org.openhab.binding.bluetooth.radoneye/README.md +++ b/bundles/org.openhab.binding.bluetooth.radoneye/README.md @@ -28,9 +28,10 @@ Supported configuration parameters for the things: Following channels are supported for `RadonEye` thing: -| Channel ID | Item Type | Description | -| ------------------ | ------------------------ | ------------------------------------------- | -| radon | Number:Density | The measured radon level | +| Channel ID | Item Type | Description | +|------------|----------------------------------|----------------------------------------| +| radon | Number:RadiationSpecificActivity | The measured radon level | +| decay | Number:Dimensionless | The decay count in the last time frame | ## Example @@ -43,5 +44,6 @@ bluetooth:radoneye_rd200:adapter1:sensor1 "radoneye Wave Plus Sensor 1" (blueto radoneye.items: ```java -Number:Density radon "Radon level [%d %unit%]" { channel="bluetooth:radoneye_rd200:adapter1:sensor1:radon" } +Number:RadiationSpecificActivity radon "Radon level [%d %unit%]" { channel="bluetooth:radoneye_rd200:adapter1:sensor1:radon" } +Number:Dimensionless decay "Decay count [%d %unit%]" { channel="bluetooth:radoneye_rd200:adapter1:sensor1:decay" } ``` diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/AbstractRadoneyeHandler.java b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/AbstractRadoneyeHandler.java index 1e10fead720..1dbd63bdeed 100644 --- a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/AbstractRadoneyeHandler.java +++ b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/AbstractRadoneyeHandler.java @@ -15,18 +15,15 @@ package org.openhab.binding.bluetooth.radoneye.internal; import java.util.UUID; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.bluetooth.BeaconBluetoothHandler; import org.openhab.binding.bluetooth.BluetoothCharacteristic; import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState; -import org.openhab.binding.bluetooth.BluetoothUtils; +import org.openhab.binding.bluetooth.ConnectedBluetoothHandler; import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification; import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,39 +32,17 @@ import org.slf4j.LoggerFactory; * sent to one of the channels. * * @author Peter Obel - Initial contribution + * @author Jörg Sautter - Use the ConnectedBluetoothHandler the handle the connection state */ @NonNullByDefault -public abstract class AbstractRadoneyeHandler extends BeaconBluetoothHandler { - - private static final int CHECK_PERIOD_SEC = 10; +public abstract class AbstractRadoneyeHandler extends ConnectedBluetoothHandler { private final Logger logger = LoggerFactory.getLogger(AbstractRadoneyeHandler.class); + private final AtomicLong isNotifying = new AtomicLong(-1); - private AtomicInteger sinceLastReadSec = new AtomicInteger(); private RadoneyeConfiguration configuration = new RadoneyeConfiguration(); private @Nullable ScheduledFuture scheduledTask; - private volatile int errorConnectCounter; - private volatile int errorReadCounter; - private volatile int errorWriteCounter; - private volatile int errorDisconnectCounter; - private volatile int errorResolvingCounter; - - private volatile ServiceState serviceState = ServiceState.NOT_RESOLVED; - private volatile ReadState readState = ReadState.IDLE; - - private enum ServiceState { - NOT_RESOLVED, - RESOLVING, - RESOLVED - } - - private enum ReadState { - IDLE, - READING, - WRITING - } - public AbstractRadoneyeHandler(Thing thing) { super(thing); } @@ -80,216 +55,81 @@ public abstract class AbstractRadoneyeHandler extends BeaconBluetoothHandler { logger.debug("Using configuration: {}", configuration); cancelScheduledTask(); logger.debug("Start scheduled task to read device in every {} seconds", configuration.refreshInterval); - scheduledTask = scheduler.scheduleWithFixedDelay(this::executePeridioc, CHECK_PERIOD_SEC, CHECK_PERIOD_SEC, - TimeUnit.SECONDS); - - sinceLastReadSec.set(configuration.refreshInterval); // update immediately + scheduledTask = scheduler.scheduleWithFixedDelay(this::execute, configuration.refreshInterval, + configuration.refreshInterval, TimeUnit.SECONDS); } @Override public void dispose() { logger.debug("Dispose"); cancelScheduledTask(); - serviceState = ServiceState.NOT_RESOLVED; - readState = ReadState.IDLE; super.dispose(); } private void cancelScheduledTask() { - if (scheduledTask != null) { - scheduledTask.cancel(true); + ScheduledFuture task = scheduledTask; + if (task != null) { + task.cancel(false); scheduledTask = null; } } - private void executePeridioc() { - sinceLastReadSec.addAndGet(CHECK_PERIOD_SEC); - execute(); - } + private void execute() { + try { + long since = isNotifying.get(); - private synchronized void execute() { - ConnectionState connectionState = device.getConnectionState(); - logger.debug("Device {} state is {}, serviceState {}, readState {}", address, connectionState, serviceState, - readState); - - switch (connectionState) { - case DISCOVERING: - case DISCOVERED: - case DISCONNECTED: - if (isTimeToRead()) { - connect(); - } - break; - case CONNECTED: - read(); - break; - default: - break; - } - } - - private void connect() { - logger.debug("Connect to device {}...", address); - if (!device.connect()) { - errorConnectCounter++; - if (errorConnectCounter < 6) { - logger.debug("Connecting to device {} failed {} times", address, errorConnectCounter); - } else { - logger.debug("ERROR: Controller reset needed. Connecting to device {} failed {} times", address, - errorConnectCounter); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connecting to device failed"); - } - } else { - logger.debug("Connected to device {}", address); - errorConnectCounter = 0; - } - } - - private void disconnect() { - logger.debug("Disconnect from device {}...", address); - if (!device.disconnect()) { - errorDisconnectCounter++; - if (errorDisconnectCounter < 6) { - logger.debug("Disconnect from device {} failed {} times", address, errorDisconnectCounter); - } else { - logger.debug("ERROR: Controller reset needed. Disconnect from device {} failed {} times", address, - errorDisconnectCounter); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Disconnect from device failed"); - } - } else { - logger.debug("Disconnected from device {}", address); - errorDisconnectCounter = 0; - } - } - - private void read() { - switch (serviceState) { - case NOT_RESOLVED: - logger.debug("Discover services on device {}", address); - discoverServices(); - break; - case RESOLVED: - switch (readState) { - case IDLE: - if (getTriggerUUID() != null) { - logger.debug("Send trigger data to device {}...", address); - BluetoothCharacteristic characteristic = device.getCharacteristic(getTriggerUUID()); - if (characteristic != null) { - readState = ReadState.WRITING; - errorWriteCounter = 0; - device.writeCharacteristic(characteristic, getTriggerData()).whenComplete((v, ex) -> { - readSensorData(); - }); - } else { - errorWriteCounter++; - if (errorWriteCounter < 6) { - logger.debug("Read/write data from device {} failed {} times", address, - errorWriteCounter); - } else { - logger.debug( - "ERROR: Controller reset needed. Read/write data from device {} failed {} times", - address, errorWriteCounter); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Read/write data from device failed"); - } - disconnect(); - } - } else { - readSensorData(); - } - - break; - default: - logger.debug("Unhandled Resolved readState {} on device {}", readState, address); - break; - } - break; - default: // serviceState RESOLVING - errorResolvingCounter++; - if (errorResolvingCounter < 6) { - logger.debug("Unhandled serviceState {} on device {}", serviceState, address); - } else { - logger.debug("ERROR: Controller reset needed. Unhandled serviceState {} on device {}", - serviceState, address); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Service discovery for device failed"); - } - break; - } - } - - private void readSensorData() { - logger.debug("Read data from device {}...", address); - BluetoothCharacteristic characteristic = device.getCharacteristic(getDataUUID()); - if (characteristic != null) { - readState = ReadState.READING; - errorReadCounter = 0; - errorResolvingCounter = 0; - device.readCharacteristic(characteristic).whenComplete((data, ex) -> { - try { - logger.debug("Characteristic {} from device {}: {}", characteristic.getUuid(), address, data); - updateStatus(ThingStatus.ONLINE); - sinceLastReadSec.set(0); - updateChannels(BluetoothUtils.toIntArray(data)); - } finally { - readState = ReadState.IDLE; + if (since != -1) { + logger.debug("Send trigger data to device {}", address); + writeCharacteristic(getServiceUUID(), getTriggerUUID(), getTriggerData(), false).exceptionally((t) -> { + String message = "Failed to send trigger data to device " + address + ", disconnect"; + logger.warn(message, t); disconnect(); - } - }); - } else { - errorReadCounter++; - if (errorReadCounter < 6) { - logger.debug("Read data from device {} failed {} times", address, errorReadCounter); + return null; + }); + } else if (device.getConnectionState() == ConnectionState.CONNECTED && device.isServicesDiscovered()) { + // we can enable the notifications multiple times, this is handled internally + enableNotifications(getServiceUUID(), getDataUUID()).thenAccept((v) -> { + isNotifying.set(System.currentTimeMillis()); + }).exceptionally((t) -> { + String message = "Failed to enable notifications on device " + address + ", disconnect"; + logger.warn(message, t); + disconnect(); + return null; + }); } else { - logger.debug("ERROR: Controller reset needed. Read data from device {} failed {} times", address, - errorReadCounter); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Read data from device failed"); + logger.debug("Device {} state is {}, discovered {}", address, device.getConnectionState(), + device.isServicesDiscovered()); } - disconnect(); + } catch (Exception e) { + String message = "Failed to execute for device " + address; + logger.warn(message, e); } } - private void discoverServices() { - logger.debug("Discover services for device {}", address); - serviceState = ServiceState.RESOLVING; - device.discoverServices(); - } - @Override - public void onServicesDiscovered() { - serviceState = ServiceState.RESOLVED; - logger.debug("Service discovery completed for device {}", address); - printServices(); - execute(); - } + public void onCharacteristicUpdate(BluetoothCharacteristic characteristic, byte[] value) { + super.onCharacteristicUpdate(characteristic, value); - private void printServices() { - device.getServices().forEach(service -> logger.debug("Device {} Service '{}'", address, service)); + if (!getDataUUID().equals(characteristic.getUuid())) { + return; + } + + logger.debug("Characteristic {} from device {}: {}", characteristic.getUuid(), address, value); + updateChannels(value); } @Override public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) { - logger.debug("Connection State Change Event is {}", connectionNotification.getConnectionState()); - switch (connectionNotification.getConnectionState()) { - case DISCONNECTED: - if (serviceState == ServiceState.RESOLVING) { - serviceState = ServiceState.NOT_RESOLVED; - } - readState = ReadState.IDLE; - break; - default: - break; + super.onConnectionStateChange(connectionNotification); + // stop sending triggers to a probably broken connection + isNotifying.set(-1); + if (connectionNotification.getConnectionState() == ConnectionState.CONNECTED) { + // start discovering when super.onConnectionStateChange does not + if (device.isServicesDiscovered() && !device.discoverServices()) { + logger.debug("Error while discovering services"); + } } - execute(); - } - - private boolean isTimeToRead() { - int sinceLastRead = sinceLastReadSec.get(); - logger.debug("Time since last update: {} sec", sinceLastRead); - return sinceLastRead >= configuration.refreshInterval; } /** @@ -301,6 +141,13 @@ public abstract class AbstractRadoneyeHandler extends BeaconBluetoothHandler { return configuration.fwVersion; } + /** + * Provides the UUID of the service, which holds the characteristics + * + * @return the UUID of the data characteristic + */ + protected abstract UUID getServiceUUID(); + /** * Provides the UUID of the characteristic, which holds the sensor data * @@ -327,5 +174,5 @@ public abstract class AbstractRadoneyeHandler extends BeaconBluetoothHandler { * * @param is the content of the bluetooth characteristic */ - protected abstract void updateChannels(int[] is); + protected abstract void updateChannels(byte[] is); } diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeBindingConstants.java b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeBindingConstants.java index c049a89a6e0..40b03da6155 100644 --- a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeBindingConstants.java +++ b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeBindingConstants.java @@ -12,24 +12,12 @@ */ package org.openhab.binding.bluetooth.radoneye.internal; -import java.math.BigInteger; import java.util.Set; -import javax.measure.Unit; -import javax.measure.quantity.Dimensionless; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.bluetooth.BluetoothBindingConstants; -import org.openhab.core.library.dimension.Density; -import org.openhab.core.library.unit.SIUnits; -import org.openhab.core.library.unit.Units; import org.openhab.core.thing.ThingTypeUID; -import tech.units.indriya.format.SimpleUnitFormat; -import tech.units.indriya.function.MultiplyConverter; -import tech.units.indriya.unit.ProductUnit; -import tech.units.indriya.unit.TransformedUnit; - /** * The {@link RadoneyeBindingConstants} class defines common constants, which are * used across the whole binding. @@ -47,14 +35,5 @@ public class RadoneyeBindingConstants { // Channel IDs public static final String CHANNEL_ID_RADON = "radon"; - - public static final Unit PARTS_PER_BILLION = new TransformedUnit<>(Units.ONE, - MultiplyConverter.ofRational(BigInteger.ONE, BigInteger.valueOf(1000000000))); - public static final Unit BECQUEREL_PER_CUBIC_METRE = new ProductUnit<>( - Units.BECQUEREL.divide(SIUnits.CUBIC_METRE)); - - static { - SimpleUnitFormat.getInstance().label(PARTS_PER_BILLION, "ppb"); - SimpleUnitFormat.getInstance().label(BECQUEREL_PER_CUBIC_METRE, "Bq/m³"); - } + public static final String CHANNEL_ID_DECAY = "decay"; } diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeDataParser.java b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeDataParser.java index 3c469a34737..df82664d50d 100644 --- a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeDataParser.java +++ b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeDataParser.java @@ -14,12 +14,9 @@ package org.openhab.binding.bluetooth.radoneye.internal; import java.math.BigDecimal; import java.util.Arrays; -import java.util.HashMap; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link RadoneyeDataParser} is responsible for parsing data from Wave Plus device format. @@ -30,49 +27,40 @@ import org.slf4j.LoggerFactory; public class RadoneyeDataParser { public static final String RADON = "radon"; + public static final String DECAY = "decay"; + private static final int EXPECTED_DATA_LEN_V1 = 20; private static final int EXPECTED_DATA_LEN_V2 = 12; - private static final int EXPECTED_VER_PLUS = 1; - - private static final Logger LOGGER = LoggerFactory.getLogger(RadoneyeDataParser.class); private RadoneyeDataParser() { } - public static Map parseRd200Data(int fwVersion, int[] data) throws RadoneyeParserException { - LOGGER.debug("Parsed data length: {}", data.length); - LOGGER.debug("Parsed data: {}", data); - - final Map result = new HashMap<>(); - + public static Map parseRd200Data(int fwVersion, byte[] data) throws RadoneyeParserException { switch (fwVersion) { case 1: if (data.length != EXPECTED_DATA_LEN_V1) { throw new RadoneyeParserException(String.format("Illegal data structure length '%d'", data.length)); } - int[] radonArray = subArray(data, 2, 6); - result.put(RADON, new BigDecimal(readFloat(radonArray) * 37)); - break; + byte[] radonArray = subArray(data, 2, 6); + return Map.of(RADON, new BigDecimal(readFloat(radonArray) * 37)); case 2: if (data.length != EXPECTED_DATA_LEN_V2) { throw new RadoneyeParserException(String.format("Illegal data structure length '%d'", data.length)); } - result.put(RADON, intFromBytes(data[2], data[3])); - break; + return Map.of(RADON, intFromBytes(data[2], data[3]), DECAY, intFromBytes(data[10], data[11])); default: throw new UnsupportedOperationException("fwVersion: " + fwVersion + " is not implemented"); } - return result; } - private static int intFromBytes(int lowByte, int highByte) { + private static int intFromBytes(byte lowByte, byte highByte) { return (highByte & 0xFF) << 8 | (lowByte & 0xFF); } // Little endian - private static int fromByteArrayLE(int[] bytes) { + private static int fromByteArrayLE(byte[] bytes) { int result = 0; for (int i = 0; i < bytes.length; i++) { result |= (bytes[i] & 0xFF) << (8 * i); @@ -80,12 +68,12 @@ public class RadoneyeDataParser { return result; } - private static float readFloat(int[] bytes) { + private static float readFloat(byte[] bytes) { int i = fromByteArrayLE(bytes); return Float.intBitsToFloat(i); } - private static int[] subArray(int[] array, int beg, int end) { + private static byte[] subArray(byte[] array, int beg, int end) { return Arrays.copyOfRange(array, beg, end + 1); } } diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeDiscoveryParticipant.java b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeDiscoveryParticipant.java index 895ed84debe..8b39eab0de8 100644 --- a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeDiscoveryParticipant.java +++ b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeDiscoveryParticipant.java @@ -85,31 +85,40 @@ public class RadoneyeDiscoveryParticipant implements BluetoothDiscoveryParticipa private String getSerial(BluetoothDiscoveryDevice device) { String name = device.getName(); + if (name == null) { + return "N/A"; + } String[] parts = name.split(":"); if (parts.length == 3) { return parts[2]; } else { - return ""; + return "N/A"; } } private String getManufacturer(BluetoothDiscoveryDevice device) { String name = device.getName(); + if (name == null) { + return "N/A"; + } String[] parts = name.split(":"); if (parts.length == 3) { return parts[0]; } else { - return ""; + return "N/A"; } } private String getModel(BluetoothDiscoveryDevice device) { String name = device.getName(); + if (name == null) { + return "N/A"; + } String[] parts = name.split(":"); if (parts.length == 3) { return parts[1]; } else { - return ""; + return "N/A"; } } diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeHandler.java b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeHandler.java index 1368442410a..4190318ce38 100644 --- a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeHandler.java +++ b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/RadoneyeHandler.java @@ -19,6 +19,7 @@ import java.util.UUID; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Thing; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +34,7 @@ import org.slf4j.LoggerFactory; public class RadoneyeHandler extends AbstractRadoneyeHandler { private static final UUID SERVICE_UUID_V1 = UUID.fromString("00001523-1212-efde-1523-785feabcd123"); - private static final UUID SERVICE_UUID_V2 = UUID.fromString("00001524-0000-1000-8000-00805f9b34fb"); + private static final UUID SERVICE_UUID_V2 = UUID.fromString("00001523-0000-1000-8000-00805f9b34fb"); private static final UUID TRIGGER_UID_V1 = UUID.fromString("00001524-1212-efde-1523-785feabcd123"); private static final UUID TRIGGER_UID_V2 = UUID.fromString("00001524-0000-1000-8000-00805f9b34fb"); private static final UUID DATA_UUID_V1 = UUID.fromString("00001525-1212-efde-1523-785feabcd123"); @@ -48,21 +49,40 @@ public class RadoneyeHandler extends AbstractRadoneyeHandler { private final Logger logger = LoggerFactory.getLogger(RadoneyeHandler.class); @Override - protected void updateChannels(int[] is) { + protected void updateChannels(byte[] is) { Map data; try { + logger.debug("Try to parse input from device: {}", is); data = RadoneyeDataParser.parseRd200Data(getFwVersion(), is); logger.debug("Parsed data: {}", data); Number radon = data.get(RadoneyeDataParser.RADON); logger.debug("Parsed data radon number: {}", radon); if (radon != null) { - updateState(CHANNEL_ID_RADON, new QuantityType<>(radon, BECQUEREL_PER_CUBIC_METRE)); + updateState(CHANNEL_ID_RADON, new QuantityType<>(radon, Units.BECQUEREL_PER_CUBIC_METRE)); + } + Number decay = data.get(RadoneyeDataParser.DECAY); + logger.debug("Parsed data decay count: {}", decay); + if (decay != null) { + updateState(CHANNEL_ID_DECAY, new QuantityType<>(decay, Units.ONE)); } } catch (RadoneyeParserException e) { logger.error("Failed to parse data received from Radoneye sensor: {}", e.getMessage()); } } + @Override + protected UUID getServiceUUID() { + int fwVersion = getFwVersion(); + switch (fwVersion) { + case 1: + return SERVICE_UUID_V1; + case 2: + return SERVICE_UUID_V2; + default: + throw new UnsupportedOperationException("fwVersion: " + fwVersion + " is not implemented"); + } + } + @Override protected UUID getDataUUID() { int fwVersion = getFwVersion(); diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/i18n/bluetooth.properties b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/i18n/bluetooth.properties index 9aa5a913e94..8ea145d6fc4 100644 --- a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/i18n/bluetooth.properties +++ b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/i18n/bluetooth.properties @@ -14,5 +14,7 @@ thing-type.config.bluetooth.radoneye_rd200.refreshInterval.description = States # channel types +channel-type.bluetooth.radoneye_decay.label = Decay Counter +channel-type.bluetooth.radoneye_decay.description = The decay count in the last time frame channel-type.bluetooth.radoneye_radon.label = Radon Current Level channel-type.bluetooth.radoneye_radon.description = Radon gas level diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/thing/radoneye.xml b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/thing/radoneye.xml index ba46d39d734..7f0c48617ae 100644 --- a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/thing/radoneye.xml +++ b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/thing/radoneye.xml @@ -17,8 +17,13 @@ + + + 1 + + @@ -38,9 +43,15 @@ - Number:Density + Number:RadiationSpecificActivity Radon gas level + + Number:Dimensionless + + The decay count in the last time frame + + diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/update/instructions.xml b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/update/instructions.xml new file mode 100644 index 00000000000..3e5192d4f32 --- /dev/null +++ b/bundles/org.openhab.binding.bluetooth.radoneye/src/main/resources/OH-INF/update/instructions.xml @@ -0,0 +1,15 @@ + + + + + + bluetooth:radoneye_radon + + + bluetooth:radoneye_decay + + + + diff --git a/bundles/org.openhab.binding.bluetooth.radoneye/src/test/java/org/openhab/binding/bluetooth/radoneye/RadoneyeParserTest.java b/bundles/org.openhab.binding.bluetooth.radoneye/src/test/java/org/openhab/binding/bluetooth/radoneye/RadoneyeParserTest.java index 2f5bf30e5f6..eb20655bf26 100644 --- a/bundles/org.openhab.binding.bluetooth.radoneye/src/test/java/org/openhab/binding/bluetooth/radoneye/RadoneyeParserTest.java +++ b/bundles/org.openhab.binding.bluetooth.radoneye/src/test/java/org/openhab/binding/bluetooth/radoneye/RadoneyeParserTest.java @@ -31,29 +31,35 @@ public class RadoneyeParserTest { @Test public void testEmptyData() { - int[] data = {}; + byte[] data = {}; assertThrows(RadoneyeParserException.class, () -> RadoneyeDataParser.parseRd200Data(1, data)); } @Test public void testWrongDataLen() throws RadoneyeParserException { - int[] data = { 1, 55, 51, 0, 122, 0, 61, 0, 119, 9, 11, 194, 169, 2, 46, 0, 0 }; + byte[] data = { 1, 55, 51, 0, 122, 0, 61, 0, 119, 9, 11, 127, 127, 2, 46, 0, 0 }; assertThrows(RadoneyeParserException.class, () -> RadoneyeDataParser.parseRd200Data(1, data)); } @Test public void testParsingRd200v1() throws RadoneyeParserException { - int[] data = { 80, 16, 31, -123, 43, 64, 123, 20, 94, 64, 92, -113, -118, 64, 15, 0, 12, 0, 0, 0 }; + byte[] data = { 80, 16, 31, -123, 43, 64, 123, 20, 94, 64, 92, -113, -118, 64, 15, 0, 12, 0, 0, 0 }; Map result = RadoneyeDataParser.parseRd200Data(1, data); - assertEquals(99, result.get(RadoneyeDataParser.RADON).intValue()); + Number radon = assertInstanceOf(Number.class, result.get(RadoneyeDataParser.RADON)); + assertEquals(99, radon.intValue()); + assertFalse(result.containsKey(RadoneyeDataParser.DECAY)); } @Test public void testParsingRd200v2() throws RadoneyeParserException { - int[] data = { 0xff, 0xff, 0x5b, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + byte xFF = (byte) 0xff; + byte[] data = { xFF, xFF, 0x5b, 0x00, xFF, xFF, xFF, xFF, xFF, xFF, 0x42, 0x01 }; Map result = RadoneyeDataParser.parseRd200Data(2, data); - assertEquals(91, result.get(RadoneyeDataParser.RADON).intValue()); + Number radon = assertInstanceOf(Number.class, result.get(RadoneyeDataParser.RADON)); + assertEquals(91, radon.intValue()); + Number decay = assertInstanceOf(Number.class, result.get(RadoneyeDataParser.DECAY)); + assertEquals(322, decay.intValue()); } }