Skip to content

Commit c79fdc5

Browse files
authored
[radoneye] Fixed the binding for HW v1 and v2 (openhab#18125)
Signed-off-by: Jörg Sautter <[email protected]>
1 parent 660acbf commit c79fdc5

File tree

10 files changed

+153
-274
lines changed

10 files changed

+153
-274
lines changed

bundles/org.openhab.binding.bluetooth.radoneye/README.md

+6-4
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ Supported configuration parameters for the things:
2828

2929
Following channels are supported for `RadonEye` thing:
3030

31-
| Channel ID | Item Type | Description |
32-
| ------------------ | ------------------------ | ------------------------------------------- |
33-
| radon | Number:Density | The measured radon level |
31+
| Channel ID | Item Type | Description |
32+
|------------|----------------------------------|----------------------------------------|
33+
| radon | Number:RadiationSpecificActivity | The measured radon level |
34+
| decay | Number:Dimensionless | The decay count in the last time frame |
3435

3536
## Example
3637

@@ -43,5 +44,6 @@ bluetooth:radoneye_rd200:adapter1:sensor1 "radoneye Wave Plus Sensor 1" (blueto
4344
radoneye.items:
4445

4546
```java
46-
Number:Density radon "Radon level [%d %unit%]" { channel="bluetooth:radoneye_rd200:adapter1:sensor1:radon" }
47+
Number:RadiationSpecificActivity radon "Radon level [%d %unit%]" { channel="bluetooth:radoneye_rd200:adapter1:sensor1:radon" }
48+
Number:Dimensionless decay "Decay count [%d %unit%]" { channel="bluetooth:radoneye_rd200:adapter1:sensor1:decay" }
4749
```

bundles/org.openhab.binding.bluetooth.radoneye/src/main/java/org/openhab/binding/bluetooth/radoneye/internal/AbstractRadoneyeHandler.java

+60-213
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,15 @@
1515
import java.util.UUID;
1616
import java.util.concurrent.ScheduledFuture;
1717
import java.util.concurrent.TimeUnit;
18-
import java.util.concurrent.atomic.AtomicInteger;
18+
import java.util.concurrent.atomic.AtomicLong;
1919

2020
import org.eclipse.jdt.annotation.NonNullByDefault;
2121
import org.eclipse.jdt.annotation.Nullable;
22-
import org.openhab.binding.bluetooth.BeaconBluetoothHandler;
2322
import org.openhab.binding.bluetooth.BluetoothCharacteristic;
2423
import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
25-
import org.openhab.binding.bluetooth.BluetoothUtils;
24+
import org.openhab.binding.bluetooth.ConnectedBluetoothHandler;
2625
import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
2726
import org.openhab.core.thing.Thing;
28-
import org.openhab.core.thing.ThingStatus;
29-
import org.openhab.core.thing.ThingStatusDetail;
3027
import org.slf4j.Logger;
3128
import org.slf4j.LoggerFactory;
3229

@@ -35,39 +32,17 @@
3532
* sent to one of the channels.
3633
*
3734
* @author Peter Obel - Initial contribution
35+
* @author Jörg Sautter - Use the ConnectedBluetoothHandler the handle the connection state
3836
*/
3937
@NonNullByDefault
40-
public abstract class AbstractRadoneyeHandler extends BeaconBluetoothHandler {
41-
42-
private static final int CHECK_PERIOD_SEC = 10;
38+
public abstract class AbstractRadoneyeHandler extends ConnectedBluetoothHandler {
4339

4440
private final Logger logger = LoggerFactory.getLogger(AbstractRadoneyeHandler.class);
41+
private final AtomicLong isNotifying = new AtomicLong(-1);
4542

46-
private AtomicInteger sinceLastReadSec = new AtomicInteger();
4743
private RadoneyeConfiguration configuration = new RadoneyeConfiguration();
4844
private @Nullable ScheduledFuture<?> scheduledTask;
4945

50-
private volatile int errorConnectCounter;
51-
private volatile int errorReadCounter;
52-
private volatile int errorWriteCounter;
53-
private volatile int errorDisconnectCounter;
54-
private volatile int errorResolvingCounter;
55-
56-
private volatile ServiceState serviceState = ServiceState.NOT_RESOLVED;
57-
private volatile ReadState readState = ReadState.IDLE;
58-
59-
private enum ServiceState {
60-
NOT_RESOLVED,
61-
RESOLVING,
62-
RESOLVED
63-
}
64-
65-
private enum ReadState {
66-
IDLE,
67-
READING,
68-
WRITING
69-
}
70-
7146
public AbstractRadoneyeHandler(Thing thing) {
7247
super(thing);
7348
}
@@ -80,216 +55,81 @@ public void initialize() {
8055
logger.debug("Using configuration: {}", configuration);
8156
cancelScheduledTask();
8257
logger.debug("Start scheduled task to read device in every {} seconds", configuration.refreshInterval);
83-
scheduledTask = scheduler.scheduleWithFixedDelay(this::executePeridioc, CHECK_PERIOD_SEC, CHECK_PERIOD_SEC,
84-
TimeUnit.SECONDS);
85-
86-
sinceLastReadSec.set(configuration.refreshInterval); // update immediately
58+
scheduledTask = scheduler.scheduleWithFixedDelay(this::execute, configuration.refreshInterval,
59+
configuration.refreshInterval, TimeUnit.SECONDS);
8760
}
8861

8962
@Override
9063
public void dispose() {
9164
logger.debug("Dispose");
9265
cancelScheduledTask();
93-
serviceState = ServiceState.NOT_RESOLVED;
94-
readState = ReadState.IDLE;
9566
super.dispose();
9667
}
9768

9869
private void cancelScheduledTask() {
99-
if (scheduledTask != null) {
100-
scheduledTask.cancel(true);
70+
ScheduledFuture<?> task = scheduledTask;
71+
if (task != null) {
72+
task.cancel(false);
10173
scheduledTask = null;
10274
}
10375
}
10476

105-
private void executePeridioc() {
106-
sinceLastReadSec.addAndGet(CHECK_PERIOD_SEC);
107-
execute();
108-
}
109-
110-
private synchronized void execute() {
111-
ConnectionState connectionState = device.getConnectionState();
112-
logger.debug("Device {} state is {}, serviceState {}, readState {}", address, connectionState, serviceState,
113-
readState);
114-
115-
switch (connectionState) {
116-
case DISCOVERING:
117-
case DISCOVERED:
118-
case DISCONNECTED:
119-
if (isTimeToRead()) {
120-
connect();
121-
}
122-
break;
123-
case CONNECTED:
124-
read();
125-
break;
126-
default:
127-
break;
128-
}
129-
}
130-
131-
private void connect() {
132-
logger.debug("Connect to device {}...", address);
133-
if (!device.connect()) {
134-
errorConnectCounter++;
135-
if (errorConnectCounter < 6) {
136-
logger.debug("Connecting to device {} failed {} times", address, errorConnectCounter);
137-
} else {
138-
logger.debug("ERROR: Controller reset needed. Connecting to device {} failed {} times", address,
139-
errorConnectCounter);
140-
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connecting to device failed");
141-
}
142-
} else {
143-
logger.debug("Connected to device {}", address);
144-
errorConnectCounter = 0;
145-
}
146-
}
147-
148-
private void disconnect() {
149-
logger.debug("Disconnect from device {}...", address);
150-
if (!device.disconnect()) {
151-
errorDisconnectCounter++;
152-
if (errorDisconnectCounter < 6) {
153-
logger.debug("Disconnect from device {} failed {} times", address, errorDisconnectCounter);
154-
} else {
155-
logger.debug("ERROR: Controller reset needed. Disconnect from device {} failed {} times", address,
156-
errorDisconnectCounter);
157-
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
158-
"Disconnect from device failed");
159-
}
160-
} else {
161-
logger.debug("Disconnected from device {}", address);
162-
errorDisconnectCounter = 0;
163-
}
164-
}
165-
166-
private void read() {
167-
switch (serviceState) {
168-
case NOT_RESOLVED:
169-
logger.debug("Discover services on device {}", address);
170-
discoverServices();
171-
break;
172-
case RESOLVED:
173-
switch (readState) {
174-
case IDLE:
175-
if (getTriggerUUID() != null) {
176-
logger.debug("Send trigger data to device {}...", address);
177-
BluetoothCharacteristic characteristic = device.getCharacteristic(getTriggerUUID());
178-
if (characteristic != null) {
179-
readState = ReadState.WRITING;
180-
errorWriteCounter = 0;
181-
device.writeCharacteristic(characteristic, getTriggerData()).whenComplete((v, ex) -> {
182-
readSensorData();
183-
});
184-
} else {
185-
errorWriteCounter++;
186-
if (errorWriteCounter < 6) {
187-
logger.debug("Read/write data from device {} failed {} times", address,
188-
errorWriteCounter);
189-
} else {
190-
logger.debug(
191-
"ERROR: Controller reset needed. Read/write data from device {} failed {} times",
192-
address, errorWriteCounter);
193-
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
194-
"Read/write data from device failed");
195-
}
196-
disconnect();
197-
}
198-
} else {
199-
readSensorData();
200-
}
201-
202-
break;
203-
default:
204-
logger.debug("Unhandled Resolved readState {} on device {}", readState, address);
205-
break;
206-
}
207-
break;
208-
default: // serviceState RESOLVING
209-
errorResolvingCounter++;
210-
if (errorResolvingCounter < 6) {
211-
logger.debug("Unhandled serviceState {} on device {}", serviceState, address);
212-
} else {
213-
logger.debug("ERROR: Controller reset needed. Unhandled serviceState {} on device {}",
214-
serviceState, address);
215-
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
216-
"Service discovery for device failed");
217-
}
218-
break;
219-
}
220-
}
77+
private void execute() {
78+
try {
79+
long since = isNotifying.get();
22180

222-
private void readSensorData() {
223-
logger.debug("Read data from device {}...", address);
224-
BluetoothCharacteristic characteristic = device.getCharacteristic(getDataUUID());
225-
if (characteristic != null) {
226-
readState = ReadState.READING;
227-
errorReadCounter = 0;
228-
errorResolvingCounter = 0;
229-
device.readCharacteristic(characteristic).whenComplete((data, ex) -> {
230-
try {
231-
logger.debug("Characteristic {} from device {}: {}", characteristic.getUuid(), address, data);
232-
updateStatus(ThingStatus.ONLINE);
233-
sinceLastReadSec.set(0);
234-
updateChannels(BluetoothUtils.toIntArray(data));
235-
} finally {
236-
readState = ReadState.IDLE;
81+
if (since != -1) {
82+
logger.debug("Send trigger data to device {}", address);
83+
writeCharacteristic(getServiceUUID(), getTriggerUUID(), getTriggerData(), false).exceptionally((t) -> {
84+
String message = "Failed to send trigger data to device " + address + ", disconnect";
85+
logger.warn(message, t);
23786
disconnect();
238-
}
239-
});
240-
} else {
241-
errorReadCounter++;
242-
if (errorReadCounter < 6) {
243-
logger.debug("Read data from device {} failed {} times", address, errorReadCounter);
87+
return null;
88+
});
89+
} else if (device.getConnectionState() == ConnectionState.CONNECTED && device.isServicesDiscovered()) {
90+
// we can enable the notifications multiple times, this is handled internally
91+
enableNotifications(getServiceUUID(), getDataUUID()).thenAccept((v) -> {
92+
isNotifying.set(System.currentTimeMillis());
93+
}).exceptionally((t) -> {
94+
String message = "Failed to enable notifications on device " + address + ", disconnect";
95+
logger.warn(message, t);
96+
disconnect();
97+
return null;
98+
});
24499
} else {
245-
logger.debug("ERROR: Controller reset needed. Read data from device {} failed {} times", address,
246-
errorReadCounter);
247-
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
248-
"Read data from device failed");
100+
logger.debug("Device {} state is {}, discovered {}", address, device.getConnectionState(),
101+
device.isServicesDiscovered());
249102
}
250-
disconnect();
103+
} catch (Exception e) {
104+
String message = "Failed to execute for device " + address;
105+
logger.warn(message, e);
251106
}
252107
}
253108

254-
private void discoverServices() {
255-
logger.debug("Discover services for device {}", address);
256-
serviceState = ServiceState.RESOLVING;
257-
device.discoverServices();
258-
}
259-
260109
@Override
261-
public void onServicesDiscovered() {
262-
serviceState = ServiceState.RESOLVED;
263-
logger.debug("Service discovery completed for device {}", address);
264-
printServices();
265-
execute();
266-
}
110+
public void onCharacteristicUpdate(BluetoothCharacteristic characteristic, byte[] value) {
111+
super.onCharacteristicUpdate(characteristic, value);
267112

268-
private void printServices() {
269-
device.getServices().forEach(service -> logger.debug("Device {} Service '{}'", address, service));
113+
if (!getDataUUID().equals(characteristic.getUuid())) {
114+
return;
115+
}
116+
117+
logger.debug("Characteristic {} from device {}: {}", characteristic.getUuid(), address, value);
118+
updateChannels(value);
270119
}
271120

272121
@Override
273122
public void onConnectionStateChange(BluetoothConnectionStatusNotification connectionNotification) {
274-
logger.debug("Connection State Change Event is {}", connectionNotification.getConnectionState());
275-
switch (connectionNotification.getConnectionState()) {
276-
case DISCONNECTED:
277-
if (serviceState == ServiceState.RESOLVING) {
278-
serviceState = ServiceState.NOT_RESOLVED;
279-
}
280-
readState = ReadState.IDLE;
281-
break;
282-
default:
283-
break;
284-
123+
super.onConnectionStateChange(connectionNotification);
124+
// stop sending triggers to a probably broken connection
125+
isNotifying.set(-1);
126+
127+
if (connectionNotification.getConnectionState() == ConnectionState.CONNECTED) {
128+
// start discovering when super.onConnectionStateChange does not
129+
if (device.isServicesDiscovered() && !device.discoverServices()) {
130+
logger.debug("Error while discovering services");
131+
}
285132
}
286-
execute();
287-
}
288-
289-
private boolean isTimeToRead() {
290-
int sinceLastRead = sinceLastReadSec.get();
291-
logger.debug("Time since last update: {} sec", sinceLastRead);
292-
return sinceLastRead >= configuration.refreshInterval;
293133
}
294134

295135
/**
@@ -301,6 +141,13 @@ protected int getFwVersion() {
301141
return configuration.fwVersion;
302142
}
303143

144+
/**
145+
* Provides the UUID of the service, which holds the characteristics
146+
*
147+
* @return the UUID of the data characteristic
148+
*/
149+
protected abstract UUID getServiceUUID();
150+
304151
/**
305152
* Provides the UUID of the characteristic, which holds the sensor data
306153
*
@@ -327,5 +174,5 @@ protected int getFwVersion() {
327174
*
328175
* @param is the content of the bluetooth characteristic
329176
*/
330-
protected abstract void updateChannels(int[] is);
177+
protected abstract void updateChannels(byte[] is);
331178
}

0 commit comments

Comments
 (0)