Support for RadonEye with v2.x.x firmware (#14549)
Signed-off-by: Jörg Sautter <joerg.sautter@gmx.net>
This commit is contained in:
parent
59b3ed33df
commit
709dc49fbe
|
@ -18,10 +18,11 @@ As any other Bluetooth device, RadonEye devices are discovered automatically by
|
||||||
|
|
||||||
Supported configuration parameters for the things:
|
Supported configuration parameters for the things:
|
||||||
|
|
||||||
| Property | Type | Default | Required | Description |
|
| Property | Type | Default | Required | Description |
|
||||||
|---------------------------------|---------|---------|----------|-----------------------------------------------------------------|
|
|-----------------|---------|---------|----------|-----------------------------------------------------------------|
|
||||||
| address | String | | Yes | Bluetooth address of the device (in format "XX:XX:XX:XX:XX:XX") |
|
| address | String | | Yes | Bluetooth address of the device (in format "XX:XX:XX:XX:XX:XX") |
|
||||||
| refreshInterval | Integer | 300 | No | How often a refresh shall occur in seconds |
|
| fwVersion | Integer | 1 | No | The major version of the firmware on the device |
|
||||||
|
| refreshInterval | Integer | 300 | No | How often a refresh shall occur in seconds |
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
*/
|
*/
|
||||||
package org.openhab.binding.bluetooth.radoneye.internal;
|
package org.openhab.binding.bluetooth.radoneye.internal;
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
@ -45,10 +44,9 @@ abstract public class AbstractRadoneyeHandler extends BeaconBluetoothHandler {
|
||||||
private final Logger logger = LoggerFactory.getLogger(AbstractRadoneyeHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(AbstractRadoneyeHandler.class);
|
||||||
|
|
||||||
private AtomicInteger sinceLastReadSec = new AtomicInteger();
|
private AtomicInteger sinceLastReadSec = new AtomicInteger();
|
||||||
private Optional<RadoneyeConfiguration> configuration = Optional.empty();
|
private RadoneyeConfiguration configuration = new RadoneyeConfiguration();
|
||||||
private @Nullable ScheduledFuture<?> scheduledTask;
|
private @Nullable ScheduledFuture<?> scheduledTask;
|
||||||
|
|
||||||
private volatile int refreshInterval;
|
|
||||||
private volatile int errorConnectCounter;
|
private volatile int errorConnectCounter;
|
||||||
private volatile int errorReadCounter;
|
private volatile int errorReadCounter;
|
||||||
private volatile int errorWriteCounter;
|
private volatile int errorWriteCounter;
|
||||||
|
@ -78,16 +76,14 @@ abstract public class AbstractRadoneyeHandler extends BeaconBluetoothHandler {
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
logger.debug("Initialize");
|
logger.debug("Initialize");
|
||||||
super.initialize();
|
super.initialize();
|
||||||
configuration = Optional.of(getConfigAs(RadoneyeConfiguration.class));
|
configuration = getConfigAs(RadoneyeConfiguration.class);
|
||||||
logger.debug("Using configuration: {}", configuration.get());
|
logger.debug("Using configuration: {}", configuration);
|
||||||
cancelScheduledTask();
|
cancelScheduledTask();
|
||||||
configuration.ifPresent(cfg -> {
|
logger.debug("Start scheduled task to read device in every {} seconds", configuration.refreshInterval);
|
||||||
refreshInterval = cfg.refreshInterval;
|
scheduledTask = scheduler.scheduleWithFixedDelay(this::executePeridioc, CHECK_PERIOD_SEC, CHECK_PERIOD_SEC,
|
||||||
logger.debug("Start scheduled task to read device in every {} seconds", refreshInterval);
|
TimeUnit.SECONDS);
|
||||||
scheduledTask = scheduler.scheduleWithFixedDelay(this::executePeridioc, CHECK_PERIOD_SEC, CHECK_PERIOD_SEC,
|
|
||||||
TimeUnit.SECONDS);
|
sinceLastReadSec.set(configuration.refreshInterval); // update immediately
|
||||||
});
|
|
||||||
sinceLastReadSec.set(refreshInterval); // update immediately
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -293,7 +289,16 @@ abstract public class AbstractRadoneyeHandler extends BeaconBluetoothHandler {
|
||||||
private boolean isTimeToRead() {
|
private boolean isTimeToRead() {
|
||||||
int sinceLastRead = sinceLastReadSec.get();
|
int sinceLastRead = sinceLastReadSec.get();
|
||||||
logger.debug("Time since last update: {} sec", sinceLastRead);
|
logger.debug("Time since last update: {} sec", sinceLastRead);
|
||||||
return sinceLastRead >= refreshInterval;
|
return sinceLastRead >= configuration.refreshInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the configured major firmware version
|
||||||
|
*
|
||||||
|
* @return the major firmware version configured
|
||||||
|
*/
|
||||||
|
protected int getFwVersion() {
|
||||||
|
return configuration.fwVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,10 +22,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class RadoneyeConfiguration {
|
public class RadoneyeConfiguration {
|
||||||
public String address = "";
|
public String address = "";
|
||||||
public int refreshInterval;
|
public int fwVersion = 1;
|
||||||
|
public int refreshInterval = 300;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[address=" + address + ", refreshInterval=" + refreshInterval + "]";
|
return "[address=" + address + ", fwVersion=" + fwVersion + ", refreshInterval=" + refreshInterval + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,8 @@ import org.slf4j.LoggerFactory;
|
||||||
public class RadoneyeDataParser {
|
public class RadoneyeDataParser {
|
||||||
public static final String RADON = "radon";
|
public static final String RADON = "radon";
|
||||||
|
|
||||||
private static final int EXPECTED_DATA_LEN = 20;
|
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 int EXPECTED_VER_PLUS = 1;
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RadoneyeDataParser.class);
|
private static final Logger logger = LoggerFactory.getLogger(RadoneyeDataParser.class);
|
||||||
|
@ -38,18 +39,32 @@ public class RadoneyeDataParser {
|
||||||
private RadoneyeDataParser() {
|
private RadoneyeDataParser() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, Number> parseRd200Data(int[] data) throws RadoneyeParserException {
|
public static Map<String, Number> parseRd200Data(int fwVersion, int[] data) throws RadoneyeParserException {
|
||||||
logger.debug("Parsed data length: {}", data.length);
|
logger.debug("Parsed data length: {}", data.length);
|
||||||
logger.debug("Parsed data: {}", data);
|
logger.debug("Parsed data: {}", data);
|
||||||
if (data.length == EXPECTED_DATA_LEN) {
|
|
||||||
final Map<String, Number> result = new HashMap<>();
|
|
||||||
|
|
||||||
int[] radonArray = subArray(data, 2, 6);
|
final Map<String, Number> result = new HashMap<>();
|
||||||
result.put(RADON, new BigDecimal(readFloat(radonArray) * 37));
|
|
||||||
return result;
|
switch (fwVersion) {
|
||||||
} else {
|
case 1:
|
||||||
throw new RadoneyeParserException(String.format("Illegal data structure length '%d'", data.length));
|
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;
|
||||||
|
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;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("fwVersion: " + fwVersion + " is not implemented");
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int intFromBytes(int lowByte, int highByte) {
|
private static int intFromBytes(int lowByte, int highByte) {
|
||||||
|
|
|
@ -33,9 +33,14 @@ import org.slf4j.LoggerFactory;
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class RadoneyeHandler extends AbstractRadoneyeHandler {
|
public class RadoneyeHandler extends AbstractRadoneyeHandler {
|
||||||
|
|
||||||
private static final String SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123";
|
private static final UUID SERVICE_UUID_V1 = UUID.fromString("00001523-1212-efde-1523-785feabcd123");
|
||||||
private static final String TRIGGER_UID = "00001524-1212-efde-1523-785feabcd123";
|
private static final UUID SERVICE_UUID_V2 = UUID.fromString("00001524-0000-1000-8000-00805f9b34fb");
|
||||||
private static final String DATA_UUID = "00001525-1212-efde-1523-785feabcd123";
|
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");
|
||||||
|
private static final UUID DATA_UUID_V2 = UUID.fromString("00001525-0000-1000-8000-00805f9b34fb");
|
||||||
|
private static final byte[] DATA_TRIGGER_V1 = new byte[] { 0x50 };
|
||||||
|
private static final byte[] DATA_TRIGGER_V2 = new byte[] { 0x50 };
|
||||||
|
|
||||||
public RadoneyeHandler(Thing thing) {
|
public RadoneyeHandler(Thing thing) {
|
||||||
super(thing);
|
super(thing);
|
||||||
|
@ -43,15 +48,11 @@ public class RadoneyeHandler extends AbstractRadoneyeHandler {
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(RadoneyeHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(RadoneyeHandler.class);
|
||||||
|
|
||||||
private final UUID dataUuid = UUID.fromString(DATA_UUID);
|
|
||||||
private final UUID triggerUuid = UUID.fromString(TRIGGER_UID);
|
|
||||||
private final byte[] triggerData = new byte[] { 0x50 };
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateChannels(int[] is) {
|
protected void updateChannels(int[] is) {
|
||||||
Map<String, Number> data;
|
Map<String, Number> data;
|
||||||
try {
|
try {
|
||||||
data = RadoneyeDataParser.parseRd200Data(is);
|
data = RadoneyeDataParser.parseRd200Data(getFwVersion(), is);
|
||||||
logger.debug("Parsed data: {}", data);
|
logger.debug("Parsed data: {}", data);
|
||||||
Number radon = data.get(RadoneyeDataParser.RADON);
|
Number radon = data.get(RadoneyeDataParser.RADON);
|
||||||
logger.debug("Parsed data radon number: {}", radon);
|
logger.debug("Parsed data radon number: {}", radon);
|
||||||
|
@ -65,16 +66,40 @@ public class RadoneyeHandler extends AbstractRadoneyeHandler {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected UUID getDataUUID() {
|
protected UUID getDataUUID() {
|
||||||
return dataUuid;
|
int fwVersion = getFwVersion();
|
||||||
|
switch (fwVersion) {
|
||||||
|
case 1:
|
||||||
|
return DATA_UUID_V1;
|
||||||
|
case 2:
|
||||||
|
return DATA_UUID_V2;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("fwVersion: " + fwVersion + " is not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected UUID getTriggerUUID() {
|
protected UUID getTriggerUUID() {
|
||||||
return triggerUuid;
|
int fwVersion = getFwVersion();
|
||||||
|
switch (fwVersion) {
|
||||||
|
case 1:
|
||||||
|
return TRIGGER_UID_V1;
|
||||||
|
case 2:
|
||||||
|
return TRIGGER_UID_V2;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("fwVersion: " + fwVersion + " is not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected byte[] getTriggerData() {
|
protected byte[] getTriggerData() {
|
||||||
return triggerData;
|
int fwVersion = getFwVersion();
|
||||||
|
switch (fwVersion) {
|
||||||
|
case 1:
|
||||||
|
return DATA_TRIGGER_V1;
|
||||||
|
case 2:
|
||||||
|
return DATA_TRIGGER_V2;
|
||||||
|
default:
|
||||||
|
throw new UnsupportedOperationException("fwVersion: " + fwVersion + " is not implemented");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
# thing types
|
||||||
|
|
||||||
|
thing-type.bluetooth.radoneye_rd200.label = RadonEye RD200
|
||||||
|
thing-type.bluetooth.radoneye_rd200.description = Indoor radon monitor
|
||||||
|
|
||||||
|
# thing types config
|
||||||
|
|
||||||
|
thing-type.config.bluetooth.radoneye_rd200.address.label = Address
|
||||||
|
thing-type.config.bluetooth.radoneye_rd200.address.description = Bluetooth address in XX:XX:XX:XX:XX:XX format
|
||||||
|
thing-type.config.bluetooth.radoneye_rd200.fwVersion.label = Firmware Version
|
||||||
|
thing-type.config.bluetooth.radoneye_rd200.fwVersion.description = The major version of the firmware on the device.
|
||||||
|
thing-type.config.bluetooth.radoneye_rd200.refreshInterval.label = Refresh Interval
|
||||||
|
thing-type.config.bluetooth.radoneye_rd200.refreshInterval.description = States how often a refresh shall occur in seconds.
|
||||||
|
|
||||||
|
# channel types
|
||||||
|
|
||||||
|
channel-type.bluetooth.radoneye_radon.label = Radon Current Level
|
||||||
|
channel-type.bluetooth.radoneye_radon.description = Radon gas level
|
|
@ -24,9 +24,14 @@
|
||||||
<label>Address</label>
|
<label>Address</label>
|
||||||
<description>Bluetooth address in XX:XX:XX:XX:XX:XX format</description>
|
<description>Bluetooth address in XX:XX:XX:XX:XX:XX format</description>
|
||||||
</parameter>
|
</parameter>
|
||||||
|
<parameter name="fwVersion" type="integer" min="1" max="2">
|
||||||
|
<label>Firmware Version</label>
|
||||||
|
<description>The major version of the firmware on the device.</description>
|
||||||
|
<default>1</default>
|
||||||
|
</parameter>
|
||||||
<parameter name="refreshInterval" type="integer" min="10">
|
<parameter name="refreshInterval" type="integer" min="10">
|
||||||
<label>Refresh Interval</label>
|
<label>Refresh Interval</label>
|
||||||
<description>States how often a refresh shall occur in seconds. This could have impact to battery lifetime</description>
|
<description>States how often a refresh shall occur in seconds.</description>
|
||||||
<default>300</default>
|
<default>300</default>
|
||||||
</parameter>
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
|
|
|
@ -32,20 +32,28 @@ public class RadoneyeParserTest {
|
||||||
@Test
|
@Test
|
||||||
public void testEmptyData() {
|
public void testEmptyData() {
|
||||||
int[] data = {};
|
int[] data = {};
|
||||||
assertThrows(RadoneyeParserException.class, () -> RadoneyeDataParser.parseRd200Data(data));
|
assertThrows(RadoneyeParserException.class, () -> RadoneyeDataParser.parseRd200Data(1, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWrongDataLen() throws RadoneyeParserException {
|
public void testWrongDataLen() throws RadoneyeParserException {
|
||||||
int[] data = { 1, 55, 51, 0, 122, 0, 61, 0, 119, 9, 11, 194, 169, 2, 46, 0, 0 };
|
int[] data = { 1, 55, 51, 0, 122, 0, 61, 0, 119, 9, 11, 194, 169, 2, 46, 0, 0 };
|
||||||
assertThrows(RadoneyeParserException.class, () -> RadoneyeDataParser.parseRd200Data(data));
|
assertThrows(RadoneyeParserException.class, () -> RadoneyeDataParser.parseRd200Data(1, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testParsingRd200() throws RadoneyeParserException {
|
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 };
|
int[] data = { 80, 16, 31, -123, 43, 64, 123, 20, 94, 64, 92, -113, -118, 64, 15, 0, 12, 0, 0, 0 };
|
||||||
Map<String, Number> result = RadoneyeDataParser.parseRd200Data(data);
|
Map<String, Number> result = RadoneyeDataParser.parseRd200Data(1, data);
|
||||||
|
|
||||||
assertEquals(99, result.get(RadoneyeDataParser.RADON).intValue());
|
assertEquals(99, result.get(RadoneyeDataParser.RADON).intValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParsingRd200v2() throws RadoneyeParserException {
|
||||||
|
int[] data = { 0xff, 0xff, 0x5b, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };
|
||||||
|
Map<String, Number> result = RadoneyeDataParser.parseRd200Data(2, data);
|
||||||
|
|
||||||
|
assertEquals(91, result.get(RadoneyeDataParser.RADON).intValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue