Skip to content
This repository was archived by the owner on Aug 8, 2018. It is now read-only.

Commit bd81cff

Browse files
committedJul 11, 2016
Support for custom refresh intervals per item.
1 parent e2b34cd commit bd81cff

8 files changed

+184
-60
lines changed
 

‎README.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ e.g.
1919
The following keys are available:
2020

2121
* `device` is the instance ID (integer) of the device as configured on your local network (it is *not* the IP address of the device)
22-
* `type` is the object type. Available types are `analogInput`, `analogOutput`, `analogValue`, `binaryInput`, `binaryOutput`, `binaryValue`
22+
* `type` is the object type. Available types are `analogInput`, `analogOutput`, `analogValue`, `binaryInput`, `binaryOutput`, `binaryValue`, `multiStateInput`, `multiStateOutput`, `multiStateValue`
2323
* `id`is the instance ID (integer) of the object you want to tie to this openHAB item
24+
* `refreshInterval` (optional) periods between property read requests
2425

2526
All these properties are coming from bacnet standard. If you don't know yet device id or available properties please contact your device manufacturer and ask for appropriate documentation. Source code contains class named `org.openhab.binding.bacnet.internal.util.DiscoveryMain` which drops all devices and available properties.
2627

@@ -31,8 +32,9 @@ This binding have few basic options. Bold items are mandatory
3132
* port (default 47808)
3233
* localNetworkNumber (default 0) - bacnet network number
3334
* localDeviceId (default 1339) - device id used to identify openhab in bacnet network
35+
* refreshInterval (default 30000) - milliseconds between polling values for configured items
36+
* discoveryTimeout (default 30000) - specifies how many milliseconds openhab will listen for whois responses from devices. *Note* binding start will be delayed for `discoveryTimeout`.
3437

35-
Currently binding will fetch all items every 30 seconds.
3638

3739
## How does it work?
3840

‎src/main/java/org/openhab/binding/bacnet/BacNetBindingProvider.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
public interface BacNetBindingProvider extends BindingProvider {
1818
public BacNetBindingConfig configForItemName(String itemName);
1919

20-
public BacNetBindingConfig configForEndpoint(int deviceId, Type type, int id);
20+
public BacNetBindingConfig configForProperty(int deviceId, Type type, int id);
2121

2222
public Collection<BacNetBindingConfig> allConfigs();
2323

‎src/main/java/org/openhab/binding/bacnet/internal/BacNetBinding.java

+88-54
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,21 @@
44
import java.util.Dictionary;
55
import java.util.HashMap;
66
import java.util.Map;
7+
import java.util.concurrent.Executors;
8+
import java.util.concurrent.ScheduledExecutorService;
9+
import java.util.concurrent.ThreadFactory;
710
import java.util.concurrent.TimeUnit;
11+
import java.util.concurrent.atomic.AtomicBoolean;
812

913
import org.code_house.bacnet4j.wrapper.api.BacNetClient;
10-
import org.code_house.bacnet4j.wrapper.api.BacNetClientException;
1114
import org.code_house.bacnet4j.wrapper.api.Device;
1215
import org.code_house.bacnet4j.wrapper.api.DeviceDiscoveryListener;
1316
import org.code_house.bacnet4j.wrapper.api.JavaToBacNetConverter;
1417
import org.code_house.bacnet4j.wrapper.api.Property;
1518
import org.code_house.bacnet4j.wrapper.ip.BacNetIpClient;
1619
import org.openhab.binding.bacnet.BacNetBindingProvider;
20+
import org.openhab.binding.bacnet.internal.queue.ReadPropertyTask;
21+
import org.openhab.binding.bacnet.internal.queue.WritePropertyTask;
1722
import org.openhab.core.binding.AbstractActiveBinding;
1823
import org.openhab.core.items.Item;
1924
import org.openhab.core.library.types.StringType;
@@ -30,16 +35,28 @@
3035
import com.serotonin.bacnet4j.type.Encodable;
3136

3237
public class BacNetBinding extends AbstractActiveBinding<BacNetBindingProvider>
33-
implements ManagedService, DeviceDiscoveryListener {
38+
implements ManagedService, DeviceDiscoveryListener, PropertyValueReceiver<Encodable> {
3439
static final Logger logger = LoggerFactory.getLogger(BacNetBinding.class);
3540

41+
private static final Long DEFAULT_REFRESH_INTERVAL = 30000L;
3642
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);
3753

3854
private Map<Integer, Device> deviceMap = Collections.synchronizedMap(new HashMap<Integer, Device>());
3955
private IpNetworkBuilder networkConfigurationBuilder;
4056
private BacNetClient client;
4157

4258
private Integer localDeviceId = DEFAULT_LOCAL_DEVICE_ID;
59+
private Long refreshInterval = DEFAULT_REFRESH_INTERVAL;
4360

4461
@Override
4562
protected String getName() {
@@ -54,12 +71,14 @@ public void activate() {
5471

5572
@Override
5673
public void deactivate() {
57-
super.deactivate();
5874
logger.debug("Bacnet binding is going down");
5975
if (client != null) {
76+
scheduler.shutdown();
6077
client.stop();
6178
client = null;
79+
initialized.set(false);
6280
}
81+
super.deactivate();
6382
}
6483

6584
@Override
@@ -75,19 +94,17 @@ public void internalReceiveCommand(String itemName, Command command) {
7594
private void performUpdate(final String itemName, final Type newValue) {
7695
final BacNetBindingConfig config = configForItemName(itemName);
7796
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);
91108
}
92109
}
93110
}
@@ -102,29 +119,27 @@ public void removeBindingProvider(BacNetBindingProvider bindingProvider) {
102119

103120
@Override
104121
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);
119133
}
120134
}
121135
}
136+
initialized.set(true);
122137
}
123138
}
124139

125140
@Override
126141
protected long getRefreshInterval() {
127-
return TimeUnit.SECONDS.toMillis(150);
142+
return refreshInterval;
128143
}
129144

130145
@Override
@@ -154,39 +169,41 @@ public void updated(Dictionary<String, ?> properties) throws ConfigurationExcept
154169
this.localDeviceId = Integer.parseInt((String) properties.get("localDeviceId"));
155170
} else {
156171
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
158173
}
159174
}
160175

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+
161191
client = new BacNetIpClient(networkConfigurationBuilder.build(), localDeviceId);
162192
client.start();
163-
setProperlyConfigured(true);
164193

194+
// start discovery in new thread so it will not delay config admin thread
165195
new Thread(new Runnable() {
166196
@Override
167197
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);
169203
}
170204
}).start();
171205
}
172206

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-
190207
private State createState(Class<? extends Item> type, Encodable value) {
191208
try {
192209
return BacNetValueConverter.bacNetValueToOpenHabState(type, value);
@@ -196,10 +213,13 @@ private State createState(Class<? extends Item> type, Encodable value) {
196213
}
197214
}
198215

199-
private Property deviceEndpointForConfig(BacNetBindingConfig config) {
216+
private Property devicePropertyForConfig(BacNetBindingConfig config) {
200217
Device device = deviceMap.get(config.deviceId);
201218
if (device != null) {
202219
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);
203223
}
204224
return null;
205225
}
@@ -214,9 +234,9 @@ private BacNetBindingConfig configForItemName(String itemName) {
214234
return null;
215235
}
216236

217-
private BacNetBindingConfig configForEndpoint(Property property) {
237+
private BacNetBindingConfig configForProperty(Property property) {
218238
for (BacNetBindingProvider provider : providers) {
219-
BacNetBindingConfig config = provider.configForEndpoint(property.getDevice().getInstanceNumber(),
239+
BacNetBindingConfig config = provider.configForProperty(property.getDevice().getInstanceNumber(),
220240
property.getType(), property.getId());
221241
if (config != null) {
222242
return config;
@@ -230,4 +250,18 @@ public void deviceDiscovered(Device device) {
230250
logger.info("Discovered device " + device);
231251
deviceMap.put(device.getInstanceNumber(), device);
232252
}
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+
233267
}

‎src/main/java/org/openhab/binding/bacnet/internal/BacNetBindingConfig.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class BacNetBindingConfig implements BindingConfig {
1616
public final Integer deviceId;
1717
public final Type type;
1818
public final Integer id;
19+
public final long refreshInterval;
1920

2021
private static final Pattern CONFIG_PATTERN = Pattern.compile("^([A-z]+=[^,]+(,|$))+");
2122

@@ -26,13 +27,14 @@ static BacNetBindingConfig parseBindingConfig(String itemName, Class<? extends I
2627
throw new BindingConfigParseException(
2728
"Invalid BacNet config: '" + configString + "'. Expected key1=value1,key2=value2");
2829
}
29-
HashMap<String, String> values = new HashMap<String, String>();
30+
31+
Map<String, String> values = new HashMap<String, String>();
3032
for (String item : configString.split(",")) {
3133
String[] parts = item.split("=");
3234
if (parts.length != 2) {
3335
throw new BindingConfigParseException("Expected key=value in BacNet config");
3436
}
35-
values.put(parts[0], parts[1]);
37+
values.put(parts[0].trim(), parts[1].trim());
3638
}
3739
return new BacNetBindingConfig(itemName, itemType, values);
3840
}
@@ -54,6 +56,12 @@ public BacNetBindingConfig(String itemName, Class<? extends Item> itemType, Map<
5456
this.type = parseObjectTypeName(type);
5557
this.id = Integer.parseInt(id);
5658

59+
if (values.containsKey("refreshInterval")) {
60+
this.refreshInterval = Long.parseLong(values.get("refreshInterval"));
61+
} else {
62+
this.refreshInterval = 0;
63+
}
64+
5765
}
5866

5967
private Type parseObjectTypeName(String name) {

‎src/main/java/org/openhab/binding/bacnet/internal/BacNetGenericBindingProvider.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public Collection<BacNetBindingConfig> allConfigs() {
5858
}
5959

6060
@Override
61-
public BacNetBindingConfig configForEndpoint(int deviceId, Type type, int id) {
61+
public BacNetBindingConfig configForProperty(int deviceId, Type type, int id) {
6262
for (BindingConfig bindingConfig : bindingConfigs.values()) {
6363
BacNetBindingConfig config = (BacNetBindingConfig) bindingConfig;
6464
if (config.deviceId == deviceId && config.type == type && config.id == id) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.openhab.binding.bacnet.internal;
2+
3+
import org.code_house.bacnet4j.wrapper.api.Property;
4+
5+
/**
6+
*
7+
*
8+
* @param <T> Base type of received values.
9+
*/
10+
public interface PropertyValueReceiver<T> {
11+
12+
void receiveProperty(Property property, T value);
13+
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package org.openhab.binding.bacnet.internal.queue;
2+
3+
import org.code_house.bacnet4j.wrapper.api.BacNetClient;
4+
import org.code_house.bacnet4j.wrapper.api.Property;
5+
import org.openhab.binding.bacnet.internal.BypassConverter;
6+
import org.openhab.binding.bacnet.internal.PropertyValueReceiver;
7+
8+
import com.serotonin.bacnet4j.type.Encodable;
9+
10+
public class ReadPropertyTask implements Runnable {
11+
12+
private final BacNetClient client;
13+
private final Property property;
14+
private final PropertyValueReceiver<Encodable> receiver;
15+
16+
public ReadPropertyTask(BacNetClient client, Property property, PropertyValueReceiver<Encodable> receiver) {
17+
this.client = client;
18+
this.property = property;
19+
this.receiver = receiver;
20+
21+
}
22+
23+
@Override
24+
public void run() {
25+
Encodable value = client.getPropertyValue(property, new BypassConverter());
26+
receiver.receiveProperty(property, value);
27+
28+
}
29+
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.openhab.binding.bacnet.internal.queue;
2+
3+
import org.code_house.bacnet4j.wrapper.api.BacNetClient;
4+
import org.code_house.bacnet4j.wrapper.api.BacNetClientException;
5+
import org.code_house.bacnet4j.wrapper.api.JavaToBacNetConverter;
6+
import org.code_house.bacnet4j.wrapper.api.Property;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
public class WritePropertyTask<T> implements Runnable {
11+
12+
private final Logger logger = LoggerFactory.getLogger(WritePropertyTask.class);
13+
14+
private final BacNetClient client;
15+
private final Property property;
16+
private final T value;
17+
private final JavaToBacNetConverter<T> converter;
18+
19+
public WritePropertyTask(BacNetClient client, Property property, T value, JavaToBacNetConverter<T> converter) {
20+
this.client = client;
21+
this.property = property;
22+
this.value = value;
23+
this.converter = converter;
24+
25+
}
26+
27+
@Override
28+
public void run() {
29+
try {
30+
client.setPropertyValue(property, value, converter);
31+
} catch (BacNetClientException e) {
32+
logger.error("Could not set value {} for property {}", value, property, e);
33+
}
34+
}
35+
36+
}

0 commit comments

Comments
 (0)
This repository has been archived.