一、模拟BACnet/IP协议设备
-
下载Yabe模拟器
链接:https://pan.baidu.com/s/1lvHhQYLPEYvPn8cjZIfLUg 提取码:xccl -
默认下一步安装
-
安装后打开设备模拟器 Bacnet.Room.Simulator
二、客户端查看模拟设备信息
- 打开Yabe客户端工具,按下图方式操作即可查看设备相关的属性和值等信息
- 或者使用BacnetScan客户端工具,查看设备的点位属性和值等信息
三、Bacnet4j包下载
-
方法一:打包好的6.0版本的jar
链接:https://pan.baidu.com/s/1bt4d_upl9GdKadmV1u1SDA 提取码:bec1 -
方法二:下载源码,进行打包
链接:https://github.com/infiniteautomation/BACnet4J
mvn install -Dmaven.test.skip=true
四、代码读取点位
-
创建Maven项目,pom文件中引入Bacnet4j的jar
<dependencies> <dependency> <groupId>com.infiniteautomation</groupId> <artifactId>bacnet4j</artifactId> <version>6.0.0</version> </dependency> </dependencies>
-
Demo代码
import com.serotonin.bacnet4j.LocalDevice; import com.serotonin.bacnet4j.RemoteDevice; import com.serotonin.bacnet4j.exception.BACnetException; import com.serotonin.bacnet4j.npdu.ip.IpNetwork; import com.serotonin.bacnet4j.npdu.ip.IpNetworkBuilder; import com.serotonin.bacnet4j.transport.DefaultTransport; import com.serotonin.bacnet4j.type.enumerated.ObjectType; import com.serotonin.bacnet4j.type.enumerated.PropertyIdentifier; import com.serotonin.bacnet4j.type.primitive.ObjectIdentifier; import com.serotonin.bacnet4j.util.PropertyReferences; import com.serotonin.bacnet4j.util.PropertyValues; import com.serotonin.bacnet4j.util.ReadListener; import com.serotonin.bacnet4j.util.RequestUtils; import java.util.ArrayList; import java.util.List; /** * @author HFL * @description 读取设备点位属性和值 */ public class ReadBacnetTest { static LocalDevice localDevice; private static final Integer REMOTE_DEVICE_ID = 295046; public static void main(String[] args) throws Exception { try { //创建网络对象 IpNetwork ipNetwork = new IpNetworkBuilder() //本机的ip .withLocalBindAddress("172.18.42.29") //掩码和长度,如果不知道本机的掩码和长度的话,可以使用代码的工具类IpNetworkUtils获取 .withSubnet("255.255.255.0", 24) //默认的UDP端口 .withPort(47808) .withReuseAddress(true) .build(); //创建虚拟的本地设备,deviceNumber随意 localDevice = new LocalDevice(123, new DefaultTransport(ipNetwork)); //初始化本地设备 localDevice.initialize(); //搜寻网段内远程设备 localDevice.startRemoteDeviceDiscovery(); //获取远程设备,instanceNumber是远程设备ID RemoteDevice remoteDevice = localDevice.getRemoteDeviceBlocking(REMOTE_DEVICE_ID); //获取远程设备的标识符对象 List<ObjectIdentifier> objectList = RequestUtils.getObjectList(localDevice, remoteDevice).getValues(); List<ObjectIdentifier> aiList = new ArrayList<>(); List<ObjectIdentifier> avList = new ArrayList<>(); System.out.println("<===================对象标识符的对象类型,实例数(下标)===================>"); //Object所有标识符 for(ObjectIdentifier oi : objectList){ System.out.println(oi.getObjectType().toString() + "," + oi.getInstanceNumber()); /** * ObjectIdentifier对象的不同类型的数据,进行过滤 * 此例子只列举了测试软件中存在的部分类型 * 具体业务时,根据需求,取对应类型的对象即可 * 对象类型(ObjectType): * analog-input、analog-value、binary-value、character-string-value、multi-state-value、... * 对象属性标识符类型(PropertyIdentifier): * objectName、presentValue、description、eventState、units、... */ //analog-input if(oi.getObjectType().equals(ObjectType.analogInput)){ aiList.add(new ObjectIdentifier(ObjectType.analogInput, oi.getInstanceNumber())); } //analog-value if(oi.getObjectType().equals(ObjectType.analogValue)){ avList.add(new ObjectIdentifier(ObjectType.analogValue, oi.getInstanceNumber())); } //.... } System.out.println("<==================================================================>"); System.out.println("取值开始!!!================>"); //根据对象属性标识符的类型进行取值操作 [测试工具模拟的设备点位的属性有objectName、description、present-value等等] //analog-input PropertyValues pvAiObjectName = readValueByPropertyIdentifier(localDevice, remoteDevice, aiList, null, PropertyIdentifier.objectName); PropertyValues pvAiPresentValue = readValueByPropertyIdentifier(localDevice, remoteDevice, aiList, null, PropertyIdentifier.presentValue); PropertyValues pvAiDescription = readValueByPropertyIdentifier(localDevice, remoteDevice, aiList, null, PropertyIdentifier.description); for (ObjectIdentifier oi : aiList){ //取出点位对象不同类型分别对应的值 System.out.println(oi.getObjectType().toString() + " " + oi.getInstanceNumber() + " Name: " + pvAiObjectName.get(oi, PropertyIdentifier.objectName).toString()); System.out.println(oi.getObjectType().toString() + " " + oi.getInstanceNumber() + " PresentValue: " + pvAiPresentValue.get(oi, PropertyIdentifier.presentValue).toString()); System.out.println(oi.getObjectType().toString() + " " + oi.getInstanceNumber() + " Description: " + pvAiDescription.get(oi, PropertyIdentifier.description).toString()); } //analog-value 同上,只是对象属性标识符列表不同List PropertyValues pvAvPresentValue = readValueByPropertyIdentifier(localDevice, remoteDevice, avList, null, PropertyIdentifier.presentValue); PropertyValues pvAvDescription = readValueByPropertyIdentifier(localDevice, remoteDevice, avList, null, PropertyIdentifier.description); for (ObjectIdentifier oi : avList){ System.out.println(oi.getObjectType().toString() + " " + oi.getInstanceNumber() + " PresentValue: " + pvAvPresentValue.get(oi, PropertyIdentifier.presentValue).toString()); System.out.println(oi.getObjectType().toString() + " " + oi.getInstanceNumber() + " Description: " + pvAvDescription.get(oi, PropertyIdentifier.description).toString()); } System.out.println("================>取值结束!!!"); } catch (Exception e) { e.printStackTrace(); }finally { if(null != localDevice){ //shutdown localDevice.terminate(); } } } /** * 读取远程设备的属性值 根据属性标识符 * @param localDevice 本地设备 * @param d 远程设备 * @param ois 对象标识符列表 * @param callback 回调 * @param propertyIdentifier 属性标识符 * @return 某一类属性的值 * @throws BACnetException Exception */ public static PropertyValues readValueByPropertyIdentifier(final LocalDevice localDevice, final RemoteDevice d, final List<ObjectIdentifier> ois, final ReadListener callback, PropertyIdentifier propertyIdentifier) throws BACnetException { if (ois.size() == 0) { return new PropertyValues(); } final PropertyReferences refs = new PropertyReferences(); for (final ObjectIdentifier oid : ois) { refs.add(oid, propertyIdentifier); } return RequestUtils.readProperties(localDevice, d, refs, false, callback); } }
-
运行结果
<===================对象标识符的对象类型,实例数(下标)===================> device,295046 analog-input,0 analog-input,1 analog-input,2 analog-value,0 analog-value,1 analog-value,2 analog-value,3 characterstring-value,1 characterstring-value,2 characterstring-value,3 binary-value,0 binary-value,1 multi-state-value,0 multi-state-value,1 <==================================================================> 取值开始!!!================> analog-input 0 Name: Temperature.Indoor analog-input 0 PresentValue: 20.6 analog-input 0 Description: Indoor Temperature analog-input 1 Name: Temperature.Water analog-input 1 PresentValue: 34.0 analog-input 1 Description: Glycol Water Temperature analog-input 2 Name: Temperature.Outdoor analog-input 2 PresentValue: 12.0 analog-input 2 Description: Outdoor Temperature analog-value 0 PresentValue: 21.0 analog-value 0 Description: Effective SetPoint analog-value 1 PresentValue: 21.0 analog-value 1 Description: Setoint 1 analog-value 2 PresentValue: 19.0 analog-value 2 Description: Setpoint 2 analog-value 3 PresentValue: 17.0 analog-value 3 Description: Setpoint 3 ================>取值结束!!!
五、常见问题
-
连接超时
com.serotonin.bacnet4j.exception.BACnetTimeoutException at com.serotonin.bacnet4j.transport.ServiceFutureImpl.result(ServiceFutureImpl.java:75) at com.serotonin.bacnet4j.transport.ServiceFutureImpl.get(ServiceFutureImpl.java:64) at com.serotonin.bacnet4j.util.RequestUtils.sendReadPropertyAllowNull(RequestUtils.java:175) at com.serotonin.bacnet4j.util.RequestUtils.getObjectList(RequestUtils.java:162) at com.serotonin.bacnet4j.util.RequestUtils.getObjectList(RequestUtils.java:156) at ReadBacnetTest.main(ReadBacnetTest.java:52) Caused by: com.serotonin.bacnet4j.exception.BACnetTimeoutException at com.serotonin.bacnet4j.transport.DefaultTransport.lambda$expire$23(DefaultTransport.java:957) at com.serotonin.bacnet4j.transport.DefaultTransport$$Lambda$3/495523663.use(Unknown Source) at com.serotonin.bacnet4j.transport.UnackedMessageContext.useConsumer(UnackedMessageContext.java:162) at com.serotonin.bacnet4j.transport.DefaultTransport.expire(DefaultTransport.java:957) at com.serotonin.bacnet4j.transport.DefaultTransport.run(DefaultTransport.java:525) at java.lang.Thread.run(Thread.java:745)
问题分析: UDP端口47808被占用,都是客户端连接工具(Yabe或BacnetScan)导致的
解决: 关闭客户端连接工具,如果关闭后重启服务依旧连接超时, 可以查看47808端口是否被占用,如果被占用,将占用的进程kill掉即可
六、注意
1. LocalBindAddress必须是本机的IP,切记不能设置成Bacnet数据所在机器地址2.只能进行跨主机通信,无法实现跨网段通信