15
15
import java .util .UUID ;
16
16
import java .util .concurrent .ScheduledFuture ;
17
17
import java .util .concurrent .TimeUnit ;
18
- import java .util .concurrent .atomic .AtomicInteger ;
18
+ import java .util .concurrent .atomic .AtomicLong ;
19
19
20
20
import org .eclipse .jdt .annotation .NonNullByDefault ;
21
21
import org .eclipse .jdt .annotation .Nullable ;
22
- import org .openhab .binding .bluetooth .BeaconBluetoothHandler ;
23
22
import org .openhab .binding .bluetooth .BluetoothCharacteristic ;
24
23
import org .openhab .binding .bluetooth .BluetoothDevice .ConnectionState ;
25
- import org .openhab .binding .bluetooth .BluetoothUtils ;
24
+ import org .openhab .binding .bluetooth .ConnectedBluetoothHandler ;
26
25
import org .openhab .binding .bluetooth .notification .BluetoothConnectionStatusNotification ;
27
26
import org .openhab .core .thing .Thing ;
28
- import org .openhab .core .thing .ThingStatus ;
29
- import org .openhab .core .thing .ThingStatusDetail ;
30
27
import org .slf4j .Logger ;
31
28
import org .slf4j .LoggerFactory ;
32
29
35
32
* sent to one of the channels.
36
33
*
37
34
* @author Peter Obel - Initial contribution
35
+ * @author Jörg Sautter - Use the ConnectedBluetoothHandler the handle the connection state
38
36
*/
39
37
@ 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 {
43
39
44
40
private final Logger logger = LoggerFactory .getLogger (AbstractRadoneyeHandler .class );
41
+ private final AtomicLong isNotifying = new AtomicLong (-1 );
45
42
46
- private AtomicInteger sinceLastReadSec = new AtomicInteger ();
47
43
private RadoneyeConfiguration configuration = new RadoneyeConfiguration ();
48
44
private @ Nullable ScheduledFuture <?> scheduledTask ;
49
45
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
-
71
46
public AbstractRadoneyeHandler (Thing thing ) {
72
47
super (thing );
73
48
}
@@ -80,216 +55,81 @@ public void initialize() {
80
55
logger .debug ("Using configuration: {}" , configuration );
81
56
cancelScheduledTask ();
82
57
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 );
87
60
}
88
61
89
62
@ Override
90
63
public void dispose () {
91
64
logger .debug ("Dispose" );
92
65
cancelScheduledTask ();
93
- serviceState = ServiceState .NOT_RESOLVED ;
94
- readState = ReadState .IDLE ;
95
66
super .dispose ();
96
67
}
97
68
98
69
private void cancelScheduledTask () {
99
- if (scheduledTask != null ) {
100
- scheduledTask .cancel (true );
70
+ ScheduledFuture <?> task = scheduledTask ;
71
+ if (task != null ) {
72
+ task .cancel (false );
101
73
scheduledTask = null ;
102
74
}
103
75
}
104
76
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 ();
221
80
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 );
237
86
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
+ });
244
99
} 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 ());
249
102
}
250
- disconnect ();
103
+ } catch (Exception e ) {
104
+ String message = "Failed to execute for device " + address ;
105
+ logger .warn (message , e );
251
106
}
252
107
}
253
108
254
- private void discoverServices () {
255
- logger .debug ("Discover services for device {}" , address );
256
- serviceState = ServiceState .RESOLVING ;
257
- device .discoverServices ();
258
- }
259
-
260
109
@ 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 );
267
112
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 );
270
119
}
271
120
272
121
@ Override
273
122
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
+ }
285
132
}
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 ;
293
133
}
294
134
295
135
/**
@@ -301,6 +141,13 @@ protected int getFwVersion() {
301
141
return configuration .fwVersion ;
302
142
}
303
143
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
+
304
151
/**
305
152
* Provides the UUID of the characteristic, which holds the sensor data
306
153
*
@@ -327,5 +174,5 @@ protected int getFwVersion() {
327
174
*
328
175
* @param is the content of the bluetooth characteristic
329
176
*/
330
- protected abstract void updateChannels (int [] is );
177
+ protected abstract void updateChannels (byte [] is );
331
178
}
0 commit comments