4
4
import java .util .Dictionary ;
5
5
import java .util .HashMap ;
6
6
import java .util .Map ;
7
+ import java .util .concurrent .Executors ;
8
+ import java .util .concurrent .ScheduledExecutorService ;
9
+ import java .util .concurrent .ThreadFactory ;
7
10
import java .util .concurrent .TimeUnit ;
11
+ import java .util .concurrent .atomic .AtomicBoolean ;
8
12
9
13
import org .code_house .bacnet4j .wrapper .api .BacNetClient ;
10
- import org .code_house .bacnet4j .wrapper .api .BacNetClientException ;
11
14
import org .code_house .bacnet4j .wrapper .api .Device ;
12
15
import org .code_house .bacnet4j .wrapper .api .DeviceDiscoveryListener ;
13
16
import org .code_house .bacnet4j .wrapper .api .JavaToBacNetConverter ;
14
17
import org .code_house .bacnet4j .wrapper .api .Property ;
15
18
import org .code_house .bacnet4j .wrapper .ip .BacNetIpClient ;
16
19
import org .openhab .binding .bacnet .BacNetBindingProvider ;
20
+ import org .openhab .binding .bacnet .internal .queue .ReadPropertyTask ;
21
+ import org .openhab .binding .bacnet .internal .queue .WritePropertyTask ;
17
22
import org .openhab .core .binding .AbstractActiveBinding ;
18
23
import org .openhab .core .items .Item ;
19
24
import org .openhab .core .library .types .StringType ;
30
35
import com .serotonin .bacnet4j .type .Encodable ;
31
36
32
37
public class BacNetBinding extends AbstractActiveBinding <BacNetBindingProvider >
33
- implements ManagedService , DeviceDiscoveryListener {
38
+ implements ManagedService , DeviceDiscoveryListener , PropertyValueReceiver < Encodable > {
34
39
static final Logger logger = LoggerFactory .getLogger (BacNetBinding .class );
35
40
41
+ private static final Long DEFAULT_REFRESH_INTERVAL = 30000L ;
36
42
private static final Integer DEFAULT_LOCAL_DEVICE_ID = 1339 ;
43
+ private static final Long DEFAULT_DISCOVERY_TIMEOUT = 30000L ;
44
+
45
+ private final ScheduledExecutorService scheduler = Executors .newSingleThreadScheduledExecutor (new ThreadFactory () {
46
+ @ Override
47
+ public Thread newThread (Runnable r ) {
48
+ return new Thread (r , "bacnet-binding-executor" );
49
+ }
50
+ });
51
+
52
+ private final AtomicBoolean initialized = new AtomicBoolean (false );
37
53
38
54
private Map <Integer , Device > deviceMap = Collections .synchronizedMap (new HashMap <Integer , Device >());
39
55
private IpNetworkBuilder networkConfigurationBuilder ;
40
56
private BacNetClient client ;
41
57
42
58
private Integer localDeviceId = DEFAULT_LOCAL_DEVICE_ID ;
59
+ private Long refreshInterval = DEFAULT_REFRESH_INTERVAL ;
43
60
44
61
@ Override
45
62
protected String getName () {
@@ -54,12 +71,14 @@ public void activate() {
54
71
55
72
@ Override
56
73
public void deactivate () {
57
- super .deactivate ();
58
74
logger .debug ("Bacnet binding is going down" );
59
75
if (client != null ) {
76
+ scheduler .shutdown ();
60
77
client .stop ();
61
78
client = null ;
79
+ initialized .set (false );
62
80
}
81
+ super .deactivate ();
63
82
}
64
83
65
84
@ Override
@@ -75,19 +94,17 @@ public void internalReceiveCommand(String itemName, Command command) {
75
94
private void performUpdate (final String itemName , final Type newValue ) {
76
95
final BacNetBindingConfig config = configForItemName (itemName );
77
96
if (config != null ) {
78
- Property endpoint = deviceEndpointForConfig (config );
79
- if (endpoint != null ) {
80
- try {
81
- client .setPropertyValue (endpoint , newValue , new JavaToBacNetConverter <Type >() {
82
- @ Override
83
- public Encodable toBacNet (Type java ) {
84
- return BacNetValueConverter .openHabTypeToBacNetValue (config .type .getBacNetType (), newValue );
85
- }
86
- });
87
- } catch (BacNetClientException e ) {
88
- logger .error ("Could not set value {} for property {} for item {} (bacnet {}:{})" , newValue ,
89
- endpoint , config .itemName , e );
90
- }
97
+ Property property = devicePropertyForConfig (config );
98
+ if (property != null ) {
99
+ scheduler .execute (
100
+ new WritePropertyTask <Type >(client , property , newValue , new JavaToBacNetConverter <Type >() {
101
+ @ Override
102
+ public Encodable toBacNet (Type java ) {
103
+ return BacNetValueConverter .openHabTypeToBacNetValue (config .type .getBacNetType (),
104
+ newValue );
105
+ }
106
+ }));
107
+ logger .info ("Submited task to write {} value {} for item {}" , property , newValue , itemName );
91
108
}
92
109
}
93
110
}
@@ -102,29 +119,27 @@ public void removeBindingProvider(BacNetBindingProvider bindingProvider) {
102
119
103
120
@ Override
104
121
protected void execute () {
105
- for (BacNetBindingProvider provider : providers ) {
106
- for (BacNetBindingConfig config : provider .allConfigs ()) {
107
- Property property = deviceEndpointForConfig (config );
108
- if (property != null ) {
109
- try {
110
- update (property );
111
- } catch (BacNetClientException e ) {
112
- logger .error ("Could not fetch property {} for item {} from bacnet" , property , config .itemName ,
113
- e );
114
- }
115
- try {
116
- Thread .sleep (100 );
117
- } catch (InterruptedException e ) {
118
- logger .warn ("Read task interrupted" , e );
122
+ if (!initialized .get ()) {
123
+ logger .trace ("Creating scheduled tasks to read bacnet properties" );
124
+ for (BacNetBindingProvider provider : providers ) {
125
+ for (BacNetBindingConfig config : provider .allConfigs ()) {
126
+ long refreshInterval = config .refreshInterval != 0 ? config .refreshInterval : getRefreshInterval ();
127
+ Property property = devicePropertyForConfig (config );
128
+ if (property != null ) {
129
+ scheduler .scheduleAtFixedRate (new ReadPropertyTask (client , property , this ), refreshInterval ,
130
+ refreshInterval , TimeUnit .MILLISECONDS );
131
+ logger .debug ("Scheduled read property task to fetch item {} value from {} every {}ms" ,
132
+ config .itemName , property , refreshInterval );
119
133
}
120
134
}
121
135
}
136
+ initialized .set (true );
122
137
}
123
138
}
124
139
125
140
@ Override
126
141
protected long getRefreshInterval () {
127
- return TimeUnit . SECONDS . toMillis ( 150 ) ;
142
+ return refreshInterval ;
128
143
}
129
144
130
145
@ Override
@@ -154,39 +169,41 @@ public void updated(Dictionary<String, ?> properties) throws ConfigurationExcept
154
169
this .localDeviceId = Integer .parseInt ((String ) properties .get ("localDeviceId" ));
155
170
} else {
156
171
if (this .localDeviceId != DEFAULT_LOCAL_DEVICE_ID ) {
157
- localDeviceId = DEFAULT_LOCAL_DEVICE_ID ; // reset to default from previous value
172
+ this . localDeviceId = DEFAULT_LOCAL_DEVICE_ID ; // reset to default from previous value
158
173
}
159
174
}
160
175
176
+ if (properties .get ("refreshInterval" ) != null ) {
177
+ this .refreshInterval = Long .parseLong ((String ) properties .get ("refreshInterval" ));
178
+ } else {
179
+ if (this .refreshInterval != DEFAULT_REFRESH_INTERVAL ) {
180
+ this .refreshInterval = DEFAULT_REFRESH_INTERVAL ; // reset to default from previous value
181
+ }
182
+ }
183
+
184
+ final long discoveryTimeout ;
185
+ if (properties .get ("discoveryTimeout" ) != null ) {
186
+ discoveryTimeout = Long .parseLong ((String ) properties .get ("discoveryTimeout" ));
187
+ } else {
188
+ discoveryTimeout = DEFAULT_DISCOVERY_TIMEOUT ;
189
+ }
190
+
161
191
client = new BacNetIpClient (networkConfigurationBuilder .build (), localDeviceId );
162
192
client .start ();
163
- setProperlyConfigured (true );
164
193
194
+ // start discovery in new thread so it will not delay config admin thread
165
195
new Thread (new Runnable () {
166
196
@ Override
167
197
public void run () {
168
- client .discoverDevices (BacNetBinding .this , 5000 );
198
+ client .discoverDevices (BacNetBinding .this , discoveryTimeout );
199
+
200
+ // upper call blocks thread for discoveryTimeout ms, thus we are safe to set
201
+ // properly configured here - all known devices should be discovered already
202
+ setProperlyConfigured (true );
169
203
}
170
204
}).start ();
171
205
}
172
206
173
- protected void update (Property property ) {
174
- if (client == null ) {
175
- logger .error ("Ignoring update request for property {}, client is not ready yet" , property );
176
- return ;
177
- }
178
- Encodable value = client .getPropertyValue (property , new BypassConverter ());
179
- State state = UnDefType .UNDEF ;
180
- BacNetBindingConfig config = configForEndpoint (property );
181
- if (config == null || value == null ) {
182
- return ;
183
- }
184
-
185
- state = this .createState (config .itemType , value );
186
- eventPublisher .postUpdate (config .itemName , state );
187
- logger .debug ("Updating item {} to value {} throught property {}" , config .itemName , value , property );
188
- }
189
-
190
207
private State createState (Class <? extends Item > type , Encodable value ) {
191
208
try {
192
209
return BacNetValueConverter .bacNetValueToOpenHabState (type , value );
@@ -196,10 +213,13 @@ private State createState(Class<? extends Item> type, Encodable value) {
196
213
}
197
214
}
198
215
199
- private Property deviceEndpointForConfig (BacNetBindingConfig config ) {
216
+ private Property devicePropertyForConfig (BacNetBindingConfig config ) {
200
217
Device device = deviceMap .get (config .deviceId );
201
218
if (device != null ) {
202
219
return new Property (device , config .id , config .type );
220
+ } else {
221
+ logger .warn ("Could not find property {}.{}.{} for item {} cause device was not discovered" , config .deviceId ,
222
+ config .type .name (), config .id , config .itemName );
203
223
}
204
224
return null ;
205
225
}
@@ -214,9 +234,9 @@ private BacNetBindingConfig configForItemName(String itemName) {
214
234
return null ;
215
235
}
216
236
217
- private BacNetBindingConfig configForEndpoint (Property property ) {
237
+ private BacNetBindingConfig configForProperty (Property property ) {
218
238
for (BacNetBindingProvider provider : providers ) {
219
- BacNetBindingConfig config = provider .configForEndpoint (property .getDevice ().getInstanceNumber (),
239
+ BacNetBindingConfig config = provider .configForProperty (property .getDevice ().getInstanceNumber (),
220
240
property .getType (), property .getId ());
221
241
if (config != null ) {
222
242
return config ;
@@ -230,4 +250,18 @@ public void deviceDiscovered(Device device) {
230
250
logger .info ("Discovered device " + device );
231
251
deviceMap .put (device .getInstanceNumber (), device );
232
252
}
253
+
254
+ @ Override
255
+ public void receiveProperty (Property property , Encodable value ) {
256
+ State state = UnDefType .UNDEF ;
257
+ BacNetBindingConfig config = configForProperty (property );
258
+ if (config == null || value == null ) {
259
+ return ;
260
+ }
261
+
262
+ state = this .createState (config .itemType , value );
263
+ eventPublisher .postUpdate (config .itemName , state );
264
+ logger .debug ("Updating item {} to value {} throught property {}" , config .itemName , value , property );
265
+ }
266
+
233
267
}
0 commit comments