mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[bluetooth] Add support for RadonEye (BLE) device (#11958)
Signed-off-by: Peter Obel <peter@ecomerc.com>
This commit is contained in:
parent
03e3b6aae2
commit
e9a1dd5b27
@ -206,6 +206,11 @@
|
|||||||
<artifactId>org.openhab.binding.bluetooth.govee</artifactId>
|
<artifactId>org.openhab.binding.bluetooth.govee</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.bluetooth.radoneye</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.bluetooth.roaming</artifactId>
|
<artifactId>org.openhab.binding.bluetooth.roaming</artifactId>
|
||||||
|
13
bundles/org.openhab.binding.bluetooth.radoneye/NOTICE
Normal file
13
bundles/org.openhab.binding.bluetooth.radoneye/NOTICE
Normal file
@ -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
|
47
bundles/org.openhab.binding.bluetooth.radoneye/README.md
Normal file
47
bundles/org.openhab.binding.bluetooth.radoneye/README.md
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
# radoneye
|
||||||
|
|
||||||
|
This extension adds support for [RadonEye](http://radonftlab.com/radon-sensor-product/radon-detector/rd200/) radon bluetooth detector.
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
Following thing types are supported by this extension:
|
||||||
|
|
||||||
|
| Thing Type ID | Description |
|
||||||
|
| ------------------- | -------------------------------------- |
|
||||||
|
| radoneye_rd200 | Original RadonEye (RD200) |
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
As any other Bluetooth device, RadonEye devices are discovered automatically by the corresponding bridge.
|
||||||
|
|
||||||
|
## Thing Configuration
|
||||||
|
|
||||||
|
Supported configuration parameters for the things:
|
||||||
|
|
||||||
|
| Property | Type | Default | Required | Description |
|
||||||
|
|---------------------------------|---------|---------|----------|-----------------------------------------------------------------|
|
||||||
|
| 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 |
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
Following channels are supported for `RadonEye` thing:
|
||||||
|
|
||||||
|
| Channel ID | Item Type | Description |
|
||||||
|
| ------------------ | ------------------------ | ------------------------------------------- |
|
||||||
|
| radon | Number:Density | The measured radon level |
|
||||||
|
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
radoneye.things (assuming you have a Bluetooth bridge with the ID `bluetooth:bluegiga:adapter1`:
|
||||||
|
|
||||||
|
```
|
||||||
|
bluetooth:radoneye_rd200:adapter1:sensor1 "radoneye Wave Plus Sensor 1" (bluetooth:bluegiga:adapter1) [ address="12:34:56:78:9A:BC", refreshInterval=300 ]
|
||||||
|
```
|
||||||
|
|
||||||
|
radoneye.items:
|
||||||
|
|
||||||
|
```
|
||||||
|
Number:Density radon "Radon level [%d %unit%]" { channel="bluetooth:radoneye_rd200:adapter1:sensor1:radon" }
|
||||||
|
```
|
25
bundles/org.openhab.binding.bluetooth.radoneye/pom.xml
Normal file
25
bundles/org.openhab.binding.bluetooth.radoneye/pom.xml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||||
|
<version>4.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.binding.bluetooth.radoneye</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Add-ons :: Bundles :: RadonEye Bluetooth Adapter</name>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.bluetooth</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<features name="org.openhab.binding.bluetooth.radoneye-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
|
||||||
|
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
|
||||||
|
|
||||||
|
<feature name="openhab-binding-bluetooth-radoneye" description="Bluetooth Binding RadonEye" version="${project.version}">
|
||||||
|
<feature>openhab-runtime-base</feature>
|
||||||
|
<feature>openhab-transport-serial</feature>
|
||||||
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth/${project.version}</bundle>
|
||||||
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.radoneye/${project.version}</bundle>
|
||||||
|
</feature>
|
||||||
|
|
||||||
|
</features>
|
@ -0,0 +1,326 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.bluetooth.radoneye.internal;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
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.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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link AbstractRadoneyeHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Peter Obel - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
abstract public class AbstractRadoneyeHandler extends BeaconBluetoothHandler {
|
||||||
|
|
||||||
|
private static final int CHECK_PERIOD_SEC = 10;
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(AbstractRadoneyeHandler.class);
|
||||||
|
|
||||||
|
private AtomicInteger sinceLastReadSec = new AtomicInteger();
|
||||||
|
private Optional<RadoneyeConfiguration> configuration = Optional.empty();
|
||||||
|
private @Nullable ScheduledFuture<?> scheduledTask;
|
||||||
|
|
||||||
|
private volatile int refreshInterval;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
logger.debug("Initialize");
|
||||||
|
super.initialize();
|
||||||
|
configuration = Optional.of(getConfigAs(RadoneyeConfiguration.class));
|
||||||
|
logger.debug("Using configuration: {}", configuration.get());
|
||||||
|
cancelScheduledTask();
|
||||||
|
configuration.ifPresent(cfg -> {
|
||||||
|
refreshInterval = cfg.refreshInterval;
|
||||||
|
logger.debug("Start scheduled task to read device in every {} seconds", refreshInterval);
|
||||||
|
scheduledTask = scheduler.scheduleWithFixedDelay(this::executePeridioc, CHECK_PERIOD_SEC, CHECK_PERIOD_SEC,
|
||||||
|
TimeUnit.SECONDS);
|
||||||
|
});
|
||||||
|
sinceLastReadSec.set(refreshInterval); // update immediately
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
scheduledTask = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executePeridioc() {
|
||||||
|
sinceLastReadSec.addAndGet(CHECK_PERIOD_SEC);
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
errorReadCounter++;
|
||||||
|
if (errorReadCounter < 6) {
|
||||||
|
logger.debug("Read data from device {} failed {} times", address, errorReadCounter);
|
||||||
|
} 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");
|
||||||
|
}
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void printServices() {
|
||||||
|
device.getServices().forEach(service -> logger.debug("Device {} Service '{}'", address, service));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
}
|
||||||
|
execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isTimeToRead() {
|
||||||
|
int sinceLastRead = sinceLastReadSec.get();
|
||||||
|
logger.debug("Time since last update: {} sec", sinceLastRead);
|
||||||
|
return sinceLastRead >= refreshInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the UUID of the characteristic, which holds the sensor data
|
||||||
|
*
|
||||||
|
* @return the UUID of the data characteristic
|
||||||
|
*/
|
||||||
|
protected abstract UUID getDataUUID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the UUID of the characteristic, that triggers and update of the sensor data
|
||||||
|
*
|
||||||
|
* @return the UUID of the data characteristic
|
||||||
|
*/
|
||||||
|
protected abstract UUID getTriggerUUID();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides the data that sent to the trigger characteristic will update the sensor data
|
||||||
|
*
|
||||||
|
* @return the trigger data as an byte array
|
||||||
|
*/
|
||||||
|
protected abstract byte[] getTriggerData();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method parses the content of the bluetooth characteristic and updates the Thing channels accordingly.
|
||||||
|
*
|
||||||
|
* @param is the content of the bluetooth characteristic
|
||||||
|
*/
|
||||||
|
abstract protected void updateChannels(int[] is);
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.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.
|
||||||
|
*
|
||||||
|
* @author Peter Obel - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RadoneyeBindingConstants {
|
||||||
|
|
||||||
|
// List of all Thing Type UIDs
|
||||||
|
public static final ThingTypeUID THING_TYPE_RADONEYE = new ThingTypeUID(BluetoothBindingConstants.BINDING_ID,
|
||||||
|
"radoneye_rd200");
|
||||||
|
|
||||||
|
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_RADONEYE);
|
||||||
|
|
||||||
|
// Channel IDs
|
||||||
|
public static final String CHANNEL_ID_RADON = "radon";
|
||||||
|
|
||||||
|
public static final Unit<Dimensionless> PARTS_PER_BILLION = new TransformedUnit<>(Units.ONE,
|
||||||
|
MultiplyConverter.ofRational(BigInteger.ONE, BigInteger.valueOf(1000000000)));
|
||||||
|
public static final Unit<Density> 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³");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.bluetooth.radoneye.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration class for {@link RadoneyeBinding} device.
|
||||||
|
*
|
||||||
|
* @author Peter Obel - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RadoneyeConfiguration {
|
||||||
|
public String address = "";
|
||||||
|
public int refreshInterval;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[address=" + address + ", refreshInterval=" + refreshInterval + "]";
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.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.
|
||||||
|
*
|
||||||
|
* @author Peter Obel - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RadoneyeDataParser {
|
||||||
|
public static final String RADON = "radon";
|
||||||
|
|
||||||
|
private static final int EXPECTED_DATA_LEN = 20;
|
||||||
|
private static final int EXPECTED_VER_PLUS = 1;
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(RadoneyeDataParser.class);
|
||||||
|
|
||||||
|
private RadoneyeDataParser() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Number> parseRd200Data(int[] data) throws RadoneyeParserException {
|
||||||
|
logger.debug("Parsed data length: {}", data.length);
|
||||||
|
logger.debug("Parsed data: {}", data);
|
||||||
|
if (data.length == EXPECTED_DATA_LEN) {
|
||||||
|
final Map<String, Number> result = new HashMap<>();
|
||||||
|
|
||||||
|
int[] radonArray = subArray(data, 2, 6);
|
||||||
|
result.put(RADON, new BigDecimal(readFloat(radonArray) * 37));
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new RadoneyeParserException(String.format("Illegal data structure length '%d'", data.length));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int intFromBytes(int lowByte, int highByte) {
|
||||||
|
return (highByte & 0xFF) << 8 | (lowByte & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Little endian
|
||||||
|
private static int fromByteArrayLE(int[] bytes) {
|
||||||
|
int result = 0;
|
||||||
|
for (int i = 0; i < bytes.length; i++) {
|
||||||
|
result |= (bytes[i] & 0xFF) << (8 * i);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float readFloat(int[] bytes) {
|
||||||
|
int i = fromByteArrayLE(bytes);
|
||||||
|
return Float.intBitsToFloat(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] subArray(int[] array, int beg, int end) {
|
||||||
|
return Arrays.copyOfRange(array, beg, end + 1);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,155 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.bluetooth.radoneye.internal;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.bluetooth.BluetoothBindingConstants;
|
||||||
|
import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryDevice;
|
||||||
|
import org.openhab.binding.bluetooth.discovery.BluetoothDiscoveryParticipant;
|
||||||
|
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||||
|
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.ThingUID;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This discovery participant is able to recognize RadonEye devices and create discovery results for them.
|
||||||
|
*
|
||||||
|
* @author Peter Obel - Initial contribution
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component
|
||||||
|
public class RadoneyeDiscoveryParticipant implements BluetoothDiscoveryParticipant {
|
||||||
|
|
||||||
|
private static final String RADONEYE_BLUETOOTH_COMPANY_ID = "f24be3";
|
||||||
|
|
||||||
|
private static final String RD200 = "R20"; // RadonEye First Generation BLE
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||||
|
return RadoneyeBindingConstants.SUPPORTED_THING_TYPES_UIDS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable ThingUID getThingUID(BluetoothDiscoveryDevice device) {
|
||||||
|
if (isRadoneyeDevice(device)) {
|
||||||
|
if (RD200.equals(getModel(device))) {
|
||||||
|
return new ThingUID(RadoneyeBindingConstants.THING_TYPE_RADONEYE, device.getAdapter().getUID(),
|
||||||
|
device.getAddress().toString().toLowerCase().replace(":", ""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable DiscoveryResult createResult(BluetoothDiscoveryDevice device) {
|
||||||
|
if (!isRadoneyeDevice(device)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ThingUID thingUID = getThingUID(device);
|
||||||
|
if (thingUID == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (RD200.equals(getModel(device))) {
|
||||||
|
return createResult(device, thingUID, "RadonEye (BLE)");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiresConnection(BluetoothDiscoveryDevice device) {
|
||||||
|
return isRadoneyeDevice(device);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isRadoneyeDevice(BluetoothDiscoveryDevice device) {
|
||||||
|
String manufacturerMacId = device.getAddress().toString().toLowerCase().replace(":", "").substring(0, 6);
|
||||||
|
if (manufacturerMacId.equals(RADONEYE_BLUETOOTH_COMPANY_ID.toLowerCase())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getSerial(BluetoothDiscoveryDevice device) {
|
||||||
|
String name = device.getName();
|
||||||
|
String[] parts = name.split(":");
|
||||||
|
if (parts.length == 3) {
|
||||||
|
return parts[2];
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getManufacturer(BluetoothDiscoveryDevice device) {
|
||||||
|
String name = device.getName();
|
||||||
|
String[] parts = name.split(":");
|
||||||
|
if (parts.length == 3) {
|
||||||
|
return parts[0];
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getModel(BluetoothDiscoveryDevice device) {
|
||||||
|
String name = device.getName();
|
||||||
|
String[] parts = name.split(":");
|
||||||
|
if (parts.length == 3) {
|
||||||
|
return parts[1];
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DiscoveryResult createResult(BluetoothDiscoveryDevice device, ThingUID thingUID, String label) {
|
||||||
|
Map<String, Object> properties = new HashMap<>();
|
||||||
|
properties.put(BluetoothBindingConstants.CONFIGURATION_ADDRESS, device.getAddress().toString());
|
||||||
|
properties.put(Thing.PROPERTY_VENDOR, "RadonEye");
|
||||||
|
String name = device.getName();
|
||||||
|
String serialNumber = device.getSerialNumber();
|
||||||
|
String firmwareRevision = device.getFirmwareRevision();
|
||||||
|
String model = device.getModel();
|
||||||
|
String hardwareRevision = device.getHardwareRevision();
|
||||||
|
Integer txPower = device.getTxPower();
|
||||||
|
if (serialNumber != null) {
|
||||||
|
properties.put(Thing.PROPERTY_SERIAL_NUMBER, serialNumber);
|
||||||
|
} else {
|
||||||
|
properties.put(Thing.PROPERTY_MODEL_ID, getSerial(device));
|
||||||
|
}
|
||||||
|
if (firmwareRevision != null) {
|
||||||
|
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, firmwareRevision);
|
||||||
|
}
|
||||||
|
if (model != null) {
|
||||||
|
properties.put(Thing.PROPERTY_MODEL_ID, model);
|
||||||
|
} else {
|
||||||
|
properties.put(Thing.PROPERTY_MODEL_ID, getModel(device));
|
||||||
|
}
|
||||||
|
if (hardwareRevision != null) {
|
||||||
|
properties.put(Thing.PROPERTY_HARDWARE_VERSION, hardwareRevision);
|
||||||
|
}
|
||||||
|
if (txPower != null) {
|
||||||
|
properties.put(BluetoothBindingConstants.PROPERTY_TXPOWER, Integer.toString(txPower));
|
||||||
|
}
|
||||||
|
properties.put(Thing.PROPERTY_MAC_ADDRESS, device.getAddress().toString());
|
||||||
|
|
||||||
|
// Create the discovery result and add to the inbox
|
||||||
|
return DiscoveryResultBuilder.create(thingUID).withProperties(properties)
|
||||||
|
.withRepresentationProperty(BluetoothBindingConstants.CONFIGURATION_ADDRESS)
|
||||||
|
.withBridge(device.getAdapter().getUID()).withLabel(label).build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.bluetooth.radoneye.internal;
|
||||||
|
|
||||||
|
import static org.openhab.binding.bluetooth.radoneye.internal.RadoneyeBindingConstants.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.library.dimension.Density;
|
||||||
|
import org.openhab.core.library.types.QuantityType;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RadoneyeHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Peter Obel - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RadoneyeHandler extends AbstractRadoneyeHandler {
|
||||||
|
|
||||||
|
private static final String SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123";
|
||||||
|
private static final String TRIGGER_UID = "00001524-1212-efde-1523-785feabcd123";
|
||||||
|
private static final String DATA_UUID = "00001525-1212-efde-1523-785feabcd123";
|
||||||
|
|
||||||
|
public RadoneyeHandler(Thing thing) {
|
||||||
|
super(thing);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
protected void updateChannels(int[] is) {
|
||||||
|
Map<String, Number> data;
|
||||||
|
try {
|
||||||
|
data = RadoneyeDataParser.parseRd200Data(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<Density>(radon, BECQUEREL_PER_CUBIC_METRE));
|
||||||
|
}
|
||||||
|
} catch (RadoneyeParserException e) {
|
||||||
|
logger.error("Failed to parse data received from Radoneye sensor: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UUID getDataUUID() {
|
||||||
|
return dataUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected UUID getTriggerUUID() {
|
||||||
|
return triggerUuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] getTriggerData() {
|
||||||
|
return triggerData;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.bluetooth.radoneye.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link RadoneyeHandlerFactory} is responsible for creating things and thing handlers.
|
||||||
|
*
|
||||||
|
* @author Peter Obel - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.radoneye")
|
||||||
|
public class RadoneyeHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||||
|
return RadoneyeBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
if (thingTypeUID.equals(RadoneyeBindingConstants.THING_TYPE_RADONEYE)) {
|
||||||
|
return new RadoneyeHandler(thing);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.bluetooth.radoneye.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception for data parsing errors.
|
||||||
|
*
|
||||||
|
* @author Peter Obel - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RadoneyeParserException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1;
|
||||||
|
|
||||||
|
public RadoneyeParserException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadoneyeParserException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadoneyeParserException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public RadoneyeParserException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="bluetooth"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
|
||||||
|
|
||||||
|
<thing-type id="radoneye_rd200">
|
||||||
|
<supported-bridge-type-refs>
|
||||||
|
<bridge-type-ref id="roaming"/>
|
||||||
|
<bridge-type-ref id="bluegiga"/>
|
||||||
|
<bridge-type-ref id="bluez"/>
|
||||||
|
</supported-bridge-type-refs>
|
||||||
|
|
||||||
|
<label>RadonEye RD200</label>
|
||||||
|
<description>Indoor radon monitor</description>
|
||||||
|
|
||||||
|
<channels>
|
||||||
|
<channel id="rssi" typeId="rssi"/>
|
||||||
|
<channel id="radon" typeId="radoneye_radon"/>
|
||||||
|
</channels>
|
||||||
|
|
||||||
|
<config-description>
|
||||||
|
<parameter name="address" type="text">
|
||||||
|
<label>Address</label>
|
||||||
|
<description>Bluetooth address in XX:XX:XX:XX:XX:XX format</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="refreshInterval" type="integer" min="10">
|
||||||
|
<label>Refresh Interval</label>
|
||||||
|
<description>States how often a refresh shall occur in seconds. This could have impact to battery lifetime</description>
|
||||||
|
<default>300</default>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<channel-type id="radoneye_radon">
|
||||||
|
<item-type>Number:Density</item-type>
|
||||||
|
<label>Radon Current Level</label>
|
||||||
|
<description>Radon gas level</description>
|
||||||
|
<state readOnly="true" pattern="%.0f %unit%"/>
|
||||||
|
</channel-type>
|
||||||
|
</thing:thing-descriptions>
|
@ -0,0 +1,51 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.bluetooth.radoneye;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.binding.bluetooth.radoneye.internal.RadoneyeDataParser;
|
||||||
|
import org.openhab.binding.bluetooth.radoneye.internal.RadoneyeParserException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests {@link RadoneyeParserTest}.
|
||||||
|
*
|
||||||
|
* @author Peter Obel - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class RadoneyeParserTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyData() {
|
||||||
|
int[] data = {};
|
||||||
|
assertThrows(RadoneyeParserException.class, () -> RadoneyeDataParser.parseRd200Data(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 };
|
||||||
|
assertThrows(RadoneyeParserException.class, () -> RadoneyeDataParser.parseRd200Data(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParsingRd200() throws RadoneyeParserException {
|
||||||
|
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);
|
||||||
|
|
||||||
|
assertEquals(99, result.get(RadoneyeDataParser.RADON).intValue());
|
||||||
|
}
|
||||||
|
}
|
@ -75,6 +75,7 @@
|
|||||||
<module>org.openhab.binding.bluetooth.enoceanble</module>
|
<module>org.openhab.binding.bluetooth.enoceanble</module>
|
||||||
<module>org.openhab.binding.bluetooth.generic</module>
|
<module>org.openhab.binding.bluetooth.generic</module>
|
||||||
<module>org.openhab.binding.bluetooth.govee</module>
|
<module>org.openhab.binding.bluetooth.govee</module>
|
||||||
|
<module>org.openhab.binding.bluetooth.radoneye</module>
|
||||||
<module>org.openhab.binding.bluetooth.roaming</module>
|
<module>org.openhab.binding.bluetooth.roaming</module>
|
||||||
<module>org.openhab.binding.bluetooth.ruuvitag</module>
|
<module>org.openhab.binding.bluetooth.ruuvitag</module>
|
||||||
<module>org.openhab.binding.bondhome</module>
|
<module>org.openhab.binding.bondhome</module>
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.enoceanble/${project.version}</bundle>
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.enoceanble/${project.version}</bundle>
|
||||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.generic/${project.version}</bundle>
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.generic/${project.version}</bundle>
|
||||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.govee/${project.version}</bundle>
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.govee/${project.version}</bundle>
|
||||||
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.radoneye/${project.version}</bundle>
|
||||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.roaming/${project.version}</bundle>
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.roaming/${project.version}</bundle>
|
||||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.ruuvitag/${project.version}</bundle>
|
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.bluetooth.ruuvitag/${project.version}</bundle>
|
||||||
</feature>
|
</feature>
|
||||||
|
Loading…
Reference in New Issue
Block a user