文章目录
- 前言
- 模拟呼梯设备的功能
- 前期准备——xml文件的编写
- 创建工程,建立BACnet模拟设备
- 如何将设备的对象列表打包发送呢?
- 被订阅的属性值变化时,如何主动通知对方?
- 读写属性值
- 完整代码
- 小结
前言
前面一到七篇,从理论,工具到实践介绍了BACnet,今天这一篇我们来模拟一下BACnet设备。
我们模拟Bacnet.Room.Simulator
来写一个有关于电梯呼梯的BACnet设备.
完整工程代码下载地址:https://download.csdn.net/download/weixin_40314351/89161436
效果图如下:
模拟呼梯设备的功能
这个呼梯设备呢,比较简单只有三个参数,最低楼层,最高楼层和当前楼层,会变化的是当前楼层,随着上行或下行的时候当前楼层就会变化,下面是这个Demo的UI。
实现的功能有:
1 能在Yabe中查找到这个设备,以及显示这个设备的所有对象;
2 实现基本的读写功能;
3 能够实现订阅属性值变化的功能;
前期准备——xml文件的编写
作为一个BACnet模拟设备,需要有一个xml文件,按面向对象的结构记录设备的对象以及属性和值。参考Bacnet.Room.Simulator
的DeviceStorage.Xml,自己也写了一个模拟电梯的DeviceStorage.Xml,代码如下:
注意不要缺少或过多一些 < > 等字符,否则在加载的时候会报错,报错了也不是什么大问题,它会提醒哪一行第几列有错误,改正就可以了。
<?xml version="1.0"?>
<DeviceStorage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Objects><!--设备对象--><Object Type="OBJECT_DEVICE" Instance="64237"><Properties><Property Id="PROP_OBJECT_IDENTIFIER" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"><Value>OBJECT_DEVICE:64237</Value></Property><Property Id="PROP_OBJECT_NAME" TAG="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>ElevatorBacnet</Value></Property><Property Id="PROP_OBJECT_TYPE" TAG="BACNET_APPLICATION_TAG_ENUMERATED"><Value>8</Value></Property><Property Id="PROP_SYSTEM_STATUS" TAG="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_VENDOR_NAME" TAG="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>F. Chaxel,Thanks to Morten Kvistgaard,MIT license,2015</Value></Property><Property Id="PROP_VENDOR_IDENTIFIER" TAG="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>61440</Value></Property><Property Id="PROP_MODEL_NAME" TAG="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Hpmont_FC_2024</Value></Property><Property Id="PROP_FIRMWARE_REVISION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>0.1.0</Value></Property><Property Id="PROP_APPLICATION_SOFTWARE_VERSION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>0.1.0</Value></Property><Property Id="PROP_PROTOCOL_VERSION" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>1</Value></Property><Property Id="PROP_PROTOCOL_REVISION" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>14</Value></Property><Property Id="PROP_PROTOCOL_SERVICES_SUPPORTED" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>01111111101111000011101110000000011010101</Value></Property><Property Id="PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>0000000010101010000000000000000100000000111110111111111</Value></Property><Property Id="PROP_OBJECT_LIST" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"></Property><Property Id="PROP_MAX_APDU_LENGTH_ACCEPTED" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>1476</Value></Property><Property Id="PROP_SEGMENTATION_SUPPORTED" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>3</Value></Property><Property Id="PROP_APDU_TIMEOUT" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>3000</Value></Property><Property Id="PROP_NUMBER_OF_APDU_RETRIES" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>3</Value></Property><Property Id="PROP_DEVICE_ADDRESS_BINDING" Tag="BACNET_APPLICATION_TAG_NULL"/><Property Id="PROP_DATABASE_REVISION" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>0</Value></Property><Property Id="PROP_DESCRIPTION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Free ElevatorController Simulator, X,xm, 2024</Value></Property><Property Id="PROP_LOCATION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>China.Shenzhen</Value></Property></Properties></Object><!--模拟输入 0--><Object Type="OBJECT_ANALOG_INPUT" Instance="0"><Properties><Property Id="PROP_DESCRIPTION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Elevator Min Layer</Value></Property><Property Id="PROP_EVENT_STATE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OBJECT_IDENTIFIER" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"><Value>OBJECT_ANALOG_INPUT:0</Value></Property><Property Id="PROP_OBJECT_NAME" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Min.Layer</Value></Property><Property Id="PROP_OBJECT_TYPE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OUT_OF_SERVICE" Tag="BACNET_APPLICATION_TAG_BOOLEAN"><Value>False</Value></Property><Property Id="PROP_PRESENT_VALUE" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>1</Value></Property><Property Id="PROP_RELIABILITY" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_STATUS_FLAGS" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>0000</Value></Property><Property Id="PROP_UNITS" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>95</Value><!--UNITS_NO_UNITS--></Property></Properties></Object><!--模拟输入 1--><Object Type="OBJECT_ANALOG_INPUT" Instance="1"><Properties><Property Id="PROP_DESCRIPTION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Elevator Max Layer</Value></Property><Property Id="PROP_EVENT_STATE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OBJECT_IDENTIFIER" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"><Value>OBJECT_ANALOG_INPUT:0</Value></Property><Property Id="PROP_OBJECT_NAME" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Max.Layer</Value></Property><Property Id="PROP_OBJECT_TYPE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OUT_OF_SERVICE" Tag="BACNET_APPLICATION_TAG_BOOLEAN"><Value>False</Value></Property><Property Id="PROP_PRESENT_VALUE" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>9</Value></Property><Property Id="PROP_RELIABILITY" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_STATUS_FLAGS" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>0000</Value></Property><Property Id="PROP_UNITS" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>95</Value><!--UNITS_NO_UNITS--></Property></Properties></Object><!--模拟值--><Object Type="OBJECT_ANALOG_VALUE" Instance="0"><Properties><Property Id="PROP_DESCRIPTION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Current Layer</Value></Property><Property Id="PROP_EVENT_STATE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OBJECT_IDENTIFIER" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"><Value>OBJECT_ANALOG_VALUE:0</Value></Property><Property Id="PROP_OBJECT_NAME" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Current.Layer</Value></Property><Property Id="PROP_OBJECT_TYPE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>2</Value></Property><Property Id="PROP_OUT_OF_SERVICE" Tag="BACNET_APPLICATION_TAG_BOOLEAN"><Value>False</Value></Property><Property Id="PROP_PRESENT_VALUE" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>3</Value></Property><Property Id="PROP_RELIABILITY" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_STATUS_FLAGS" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>0000</Value></Property><Property Id="PROP_UNITS" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>95</Value></Property></Properties></Object></Objects>
</DeviceStorage>
创建工程,建立BACnet模拟设备
使用VS2019创建一个.NET Framework 4.6.1的桌面应用应用程序,NuGet管理包下载BACnet.dll到项目中。
创建一个BacnetActivity.cs文件类,用于对BACnet设备的管理。
因为是作为一个BACnet模拟设备,需要实现的功能肯定有:
被发现: OnWhoIs
被访问读写: OnReadPropertyRequest
OnWritePropertyRequest
被订阅属性值变化: OnSubscribeCOV
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.NetworkInformation;using System.IO.BACnet;
using System.IO.BACnet.Storage;
using System.Windows.Forms;
using System.Diagnostics;namespace ElevatorBacnet
{static class BacnetActivity{public static DeviceStorage m_storage;public static string m_local_ip_endpoint = "192.168.2.164";public static BacnetClient m_ip_server;private static Dictionary<BacnetObjectId, List<Subscription>> m_subscriptions= new Dictionary<BacnetObjectId, List<Subscription>>();public static object m_lockObject = new object();private static BacnetSegmentations m_supported_segentation =BacnetSegmentations.SEGMENTATION_BOTH;internal static uint deviceId = 1234;//重新初始化public static void ReInitialize(){try{//initif (m_ip_server != null)m_ip_server.Dispose();PhysicalAddress macAddr = null;var netiface = NetworkInterface.GetAllNetworkInterfaces();foreach(NetworkInterface net in netiface){if(net.OperationalStatus == OperationalStatus.Up&& net.NetworkInterfaceType == NetworkInterfaceType.Ethernet){macAddr = net.GetPhysicalAddress(); break;}}/* PhysicalAddress physical = (from netface in NetworkInterface.GetAllNetworkInterfaces()where ((netface.OperationalStatus == OperationalStatus.Up) &&(netface.NetworkInterfaceType == NetworkInterfaceType.Ethernet))select netface.GetPhysicalAddress()).FirstOrDefault();*/if (Program.DeviceId == -1){if (macAddr != null){byte[] mac = macAddr.GetAddressBytes();deviceId = (uint)mac[5] + (uint)((mac[4] << 8) << 6);}deviceId = deviceId + ((uint)Program.Count & 0x3F);}elsedeviceId = (uint)Program.DeviceId;Program.DeviceId = (int)deviceId;m_storage = DeviceStorage.Load("DeviceStorage.xml", deviceId);m_storage.ReadOverride += ReadOverride;m_storage.ChangeOfValue += ChangeOfValue;//create udp service pointBacnetIpUdpProtocolTransport udp = newBacnetIpUdpProtocolTransport(port: 0xBAC0, useExclusivePort: false,localEndpointIp: m_local_ip_endpoint);m_ip_server = new BacnetClient(udp);m_ip_server.OnWhoIs += OnWhoIs;m_ip_server.OnReadPropertyRequest += OnReadPropertyRequest;m_ip_server.OnWritePropertyRequest += OnWritePropertyRequest;m_ip_server.OnReadPropertyMultipleRequest += OnReadPropertyMultipleRequest;m_ip_server.OnSubscribeCOV += OnSubscribeCOV;m_ip_server.OnSubscribeCOVProperty += OnSubscribeCOVProperty;m_ip_server.Start();//发送问候m_ip_server.Iam(m_storage.DeviceId, m_supported_segentation);}catch(Exception ex){MessageBox.Show(ex.Message);}}}
如何将设备的对象列表打包发送呢?
Yabe能够获取BACnet设备的所有对象列表,那么BACnet设备是怎么将它自己的对象列表整合发送的?答案是在DeviceStorage
类的回调方法ReadOverride
中实现的。在这个重写方法中,如果对方请求读取自己的对象列表就会在这个方法中将对象列表打包。如下:
当然这个方法里面还包括一些其它的,比如说明支持的对象类型, 支持的标准协议服务,以及是否支持分段等。
为什么要用回调方法ReadOverride
,而不是直接从配置文件xml中读取,因为xml是明文可能会被其他人恶意更改,导致程序不能正常运行。
private static void ReadOverride(BacnetObjectId objectId, BacnetPropertyIds propertyId, uint arrayIndex, out IList<BacnetValue> value, out DeviceStorage.ErrorCodes status, out bool handled){handled = true;value = new BacnetValue[0];status = DeviceStorage.ErrorCodes.Good;if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&propertyId == BacnetPropertyIds.PROP_OBJECT_LIST){//对象列表 列出设备中的可被BACnet 服务访问的所有对象的标识符if (arrayIndex == 0){//对象列表value = new BacnetValue[]{new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT,(uint)m_storage.Objects.Length)};}else if(arrayIndex != System.IO.BACnet.Serialize.ASN1.BACNET_ARRAY_ALL){//object list indexvalue = new BacnetValue[]{new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID,new BacnetObjectId(m_storage.Objects[arrayIndex-1].Type,m_storage.Objects[arrayIndex-1].Instance))};}else{//整个对象列表 object list wholeBacnetValue[] list = new BacnetValue[m_storage.Objects.Length];for(int i=0; i<list.Length;i++){list[i].Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID;list[i].Value = new BacnetObjectId(m_storage.Objects[i].Type,m_storage.Objects[i].Instance);}value = list;}}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED){//协议对象类型支持 表示设备的协议实现所支持的标准对象类型BacnetValue v = new BacnetValue();v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING;BacnetBitString b = new BacnetBitString();//全部设置为false set all falseb.SetBit((byte)BacnetObjectTypes.MAX_ASHRAE_OBJECT_TYPE, false);b.SetBit((byte)BacnetObjectTypes.OBJECT_ANALOG_INPUT, true);b.SetBit((byte)BacnetObjectTypes.OBJECT_DEVICE, true);b.SetBit((byte)BacnetObjectTypes.OBJECT_ANALOG_VALUE, true);v.Value = b;value = new BacnetValue[] { v };}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_PROTOCOL_SERVICES_SUPPORTED ){//协议服务支持 表示设备的协议实现所支持的标准协议服务BacnetValue v = new BacnetValue();v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING;BacnetBitString b = new BacnetBitString();b.SetBit((byte)BacnetServicesSupported.MAX_BACNET_SERVICES_SUPPORTED, false);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_I_AM, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_WHO_IS, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_READ_PROP_MULTIPLE, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_READ_PROPERTY, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_WRITE_PROPERTY, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_SUBSCRIBE_COV, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_SUBSCRIBE_COV_PROPERTY, true);v.Value = b;value = new BacnetValue[] { v };}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_SEGMENTATION_SUPPORTED){//分段支持 表示设备是否支持报文分段,分段传输和分段接收BacnetValue v = new BacnetValue();v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED;v.Value = (uint)BacnetSegmentations.SEGMENTATION_BOTH;value = new BacnetValue[] { v };}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_SYSTEM_STATUS){//系统状态 表示设备的物理和逻辑状态BacnetValue v = new BacnetValue();v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED;//可以是任何modelv.Value = (uint)BacnetDeviceStatus.OPERATIONAL;value = new BacnetValue[] { v };}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_ACTIVE_COV_SUBSCRIPTIONS){//暂未实现handled = false;}else if(objectId.type == BacnetObjectTypes.OBJECT_OCTETSTRING_VALUE &&objectId.instance == 0 &&propertyId == BacnetPropertyIds.PROP_PRESENT_VALUE){//暂未实现handled = false;}else if(objectId.type == BacnetObjectTypes.OBJECT_GROUP &&propertyId == BacnetPropertyIds.PROP_PRESENT_VALUE){//对象组//暂时未实现handled = false;}else{handled = false;}}
从源码DeviceStorage
类中可以看到在执行读取属性ReadProperty
的时候会判断ReadOverride
回调方法是否为空,如果不为空则会先执行ReadOverride
且当这个方法的返回值为true时,就直接返回回调结果了,而不会继续往下走。如果返回值是false,则会继续往下走下面的逻辑。
被订阅的属性值变化时,如何主动通知对方?
前面的提及到的OnSubscribeCOV
只是将被订阅的属性记录起来,并发送一个ACK给对方而已,而当订阅值发生变化时是在DeviceStorage
类的ChangeOfValue
回调方法中主动通知对方的。
private static void ChangeOfValue(DeviceStorage sender, BacnetObjectId object_id, BacnetPropertyIds property_id, uint array_index, IList<BacnetValue> value){System.Threading.ThreadPool.QueueUserWorkItem((o) =>{lock (m_lockObject){Console.WriteLine("Enter ChangeOfValue");//remove old lefto versRemoveOldSubscriptions();//find subscriptionif (!m_subscriptions.ContainsKey(object_id)) return;List<Subscription> subs = m_subscriptions[object_id];//convertList<BacnetPropertyValue> values = new List<BacnetPropertyValue>();BacnetPropertyValue tmp = new BacnetPropertyValue();tmp.property = new BacnetPropertyReference((uint)property_id, array_index);tmp.value = value;values.Add(tmp);//send to allforeach (Subscription sub in subs){if (sub.monitoredProperty.propertyIdentifier == (uint)BacnetPropertyIds.PROP_ALL || sub.monitoredProperty.propertyIdentifier == (uint)property_id){//send notifyif (!sub.reciever.Notify(sub.reciever_address, sub.subscriberProcessIdentifier,m_storage.DeviceId, sub.monitoredObjectIdentifer, (uint)sub.GetTimeRemaining(), sub.issueConfimedNotifications, values))Trace.TraceError("Couldn't send notify");}}}}, null);}
读写属性值
OnWritePropertyRequest
收到被写的请求后要先判断这个属性值是否允许写入,以及这个属性值是否在xml文件中,找得到属性且被允许写入才能写入,否则就回一个Err.
OnReadPropertyRequest
读请求也是类似的,先判断这个属性值是否在xml文件中,如果存在则返回当前值给请求方,若不存在则返回Err.
/// <summary>///响应写的请求/// </summary>private static void OnWritePropertyRequest(BacnetClient sender, BacnetAddress adr,byte invokeId, BacnetObjectId objectId, BacnetPropertyValue value,BacnetMaxSegments maxSegments){//先判断这个对象的属性是否支持写入BacnetPropertyIds PropId = (BacnetPropertyIds)value.property.propertyIdentifier;bool AllowWrite =(objectId.Equals("OBJECT_ANALOG_VALUE:0") && (PropId == BacnetPropertyIds.PROP_OUT_OF_SERVICE)) ||(objectId.Equals("OBJECT_ANALOG_VALUE:0") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||(objectId.Equals("OBJECT_ANALOG_INPUT:0") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||(objectId.Equals("OBJECT_ANALOG_INPUT:1") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE));if (AllowWrite == false){sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_WRITE_ACCESS_DENIED);return;}lock (m_lockObject){try{//先写配置xml文件中这个对象DeviceStorage.ErrorCodes code = m_storage.WriteCommandableProperty(objectId,(BacnetPropertyIds)value.property.propertyIdentifier,value.value[0], value.priority);if (code == DeviceStorage.ErrorCodes.NotForMe)code = m_storage.WriteProperty(objectId,(BacnetPropertyIds)value.property.propertyIdentifier,value.property.propertyArrayIndex,value.value);//回应写的请求if (code == DeviceStorage.ErrorCodes.Good){sender.SimpleAckResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY,invokeId);}else{if(code == DeviceStorage.ErrorCodes.WriteAccessDenied){sender.ErrorResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_WRITE_ACCESS_DENIED);}elsesender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}catch{sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}}/// <summary>///响应读的请求/// </summary>private static void OnReadPropertyRequest(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetObjectId objectId, BacnetPropertyReference property, BacnetMaxSegments maxSegments){lock (m_lockObject){try{//先看看配置xml文件中是否有这个对象IList<BacnetValue> value;DeviceStorage.ErrorCodes code = m_storage.ReadProperty(objectId, property.GetPropertyId(),property.propertyArrayIndex,out value);if (code == DeviceStorage.ErrorCodes.Good){//回应读的请求sender.ReadPropertyResponse(adr, invokeId,sender.GetSegmentBuffer(maxSegments),objectId, property, value);}else{sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}catch{sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}}
完整代码
1 BacnetActivity.cs文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.NetworkInformation;using System.IO.BACnet;
using System.IO.BACnet.Storage;
using System.Windows.Forms;
using System.Diagnostics;namespace ElevatorBacnet
{static class BacnetActivity{public static DeviceStorage m_storage;public static string m_local_ip_endpoint = "192.168.2.164";public static BacnetClient m_ip_server;private static Dictionary<BacnetObjectId, List<Subscription>> m_subscriptions= new Dictionary<BacnetObjectId, List<Subscription>>();public static object m_lockObject = new object();private static BacnetSegmentations m_supported_segentation =BacnetSegmentations.SEGMENTATION_BOTH;internal static uint deviceId = 1234;//重新初始化public static void ReInitialize(){try{//initif (m_ip_server != null)m_ip_server.Dispose();PhysicalAddress macAddr = null;var netiface = NetworkInterface.GetAllNetworkInterfaces();foreach(NetworkInterface net in netiface){if(net.OperationalStatus == OperationalStatus.Up&& net.NetworkInterfaceType == NetworkInterfaceType.Ethernet){macAddr = net.GetPhysicalAddress(); break;}}/* PhysicalAddress physical = (from netface in NetworkInterface.GetAllNetworkInterfaces()where ((netface.OperationalStatus == OperationalStatus.Up) &&(netface.NetworkInterfaceType == NetworkInterfaceType.Ethernet))select netface.GetPhysicalAddress()).FirstOrDefault();*/if (Program.DeviceId == -1){if (macAddr != null){byte[] mac = macAddr.GetAddressBytes();deviceId = (uint)mac[5] + (uint)((mac[4] << 8) << 6);}deviceId = deviceId + ((uint)Program.Count & 0x3F);}elsedeviceId = (uint)Program.DeviceId;Program.DeviceId = (int)deviceId;m_storage = DeviceStorage.Load("DeviceStorage.xml", deviceId);m_storage.ReadOverride += ReadOverride;m_storage.ChangeOfValue += ChangeOfValue;//create udp service pointBacnetIpUdpProtocolTransport udp = newBacnetIpUdpProtocolTransport(port: 0xBAC0, useExclusivePort: false,localEndpointIp: m_local_ip_endpoint);m_ip_server = new BacnetClient(udp);m_ip_server.OnWhoIs += OnWhoIs;m_ip_server.OnReadPropertyRequest += OnReadPropertyRequest;m_ip_server.OnWritePropertyRequest += OnWritePropertyRequest;m_ip_server.OnReadPropertyMultipleRequest += OnReadPropertyMultipleRequest;m_ip_server.OnSubscribeCOV += OnSubscribeCOV;m_ip_server.OnSubscribeCOVProperty += OnSubscribeCOVProperty;m_ip_server.Start();//发送问候m_ip_server.Iam(m_storage.DeviceId, m_supported_segentation);}catch(Exception ex){MessageBox.Show(ex.Message);}}#region xml的读写public static BacnetValue GetBacnetPresentValue(BacnetObjectId objectId){lock (m_lockObject){IList<BacnetValue> val = null;m_storage.ReadProperty(objectId,BacnetPropertyIds.PROP_PRESENT_VALUE, 1,out val);return val[0];}}public static void SetBacnetPresentValue(BacnetObjectId id, BacnetValue bv){if (GetBacnetPresentValue(id).Value.ToString() == bv.Value.ToString())return;lock(m_lockObject){IList<BacnetValue> val = new BacnetValue[1] { bv };m_storage.WriteProperty(id, BacnetPropertyIds.PROP_PRESENT_VALUE,1, val, true);}}#endregionprivate static void ReadOverride(BacnetObjectId objectId, BacnetPropertyIds propertyId, uint arrayIndex, out IList<BacnetValue> value, out DeviceStorage.ErrorCodes status, out bool handled){handled = true;value = new BacnetValue[0];status = DeviceStorage.ErrorCodes.Good;if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&propertyId == BacnetPropertyIds.PROP_OBJECT_LIST){//对象列表 列出设备中的可被BACnet 服务访问的所有对象的标识符if (arrayIndex == 0){//对象列表value = new BacnetValue[]{new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT,(uint)m_storage.Objects.Length)};}else if(arrayIndex != System.IO.BACnet.Serialize.ASN1.BACNET_ARRAY_ALL){//object list indexvalue = new BacnetValue[]{new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID,new BacnetObjectId(m_storage.Objects[arrayIndex-1].Type,m_storage.Objects[arrayIndex-1].Instance))};}else{//整个对象列表 object list wholeBacnetValue[] list = new BacnetValue[m_storage.Objects.Length];for(int i=0; i<list.Length;i++){list[i].Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_OBJECT_ID;list[i].Value = new BacnetObjectId(m_storage.Objects[i].Type,m_storage.Objects[i].Instance);}value = list;}}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED){//协议对象类型支持 表示设备的协议实现所支持的标准对象类型BacnetValue v = new BacnetValue();v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING;BacnetBitString b = new BacnetBitString();//全部设置为false set all falseb.SetBit((byte)BacnetObjectTypes.MAX_ASHRAE_OBJECT_TYPE, false);b.SetBit((byte)BacnetObjectTypes.OBJECT_ANALOG_INPUT, true);b.SetBit((byte)BacnetObjectTypes.OBJECT_DEVICE, true);b.SetBit((byte)BacnetObjectTypes.OBJECT_ANALOG_VALUE, true);v.Value = b;value = new BacnetValue[] { v };}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_PROTOCOL_SERVICES_SUPPORTED ){//协议服务支持 表示设备的协议实现所支持的标准协议服务BacnetValue v = new BacnetValue();v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_BIT_STRING;BacnetBitString b = new BacnetBitString();b.SetBit((byte)BacnetServicesSupported.MAX_BACNET_SERVICES_SUPPORTED, false);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_I_AM, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_WHO_IS, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_READ_PROP_MULTIPLE, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_READ_PROPERTY, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_WRITE_PROPERTY, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_SUBSCRIBE_COV, true);b.SetBit((byte)BacnetServicesSupported.SERVICE_SUPPORTED_SUBSCRIBE_COV_PROPERTY, true);v.Value = b;value = new BacnetValue[] { v };}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_SEGMENTATION_SUPPORTED){//分段支持 表示设备是否支持报文分段,分段传输和分段接收BacnetValue v = new BacnetValue();v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED;v.Value = (uint)BacnetSegmentations.SEGMENTATION_BOTH;value = new BacnetValue[] { v };}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_SYSTEM_STATUS){//系统状态 表示设备的物理和逻辑状态BacnetValue v = new BacnetValue();v.Tag = BacnetApplicationTags.BACNET_APPLICATION_TAG_ENUMERATED;//可以是任何modelv.Value = (uint)BacnetDeviceStatus.OPERATIONAL;value = new BacnetValue[] { v };}else if(objectId.type == BacnetObjectTypes.OBJECT_DEVICE &&objectId.instance == m_storage.DeviceId &&propertyId == BacnetPropertyIds.PROP_ACTIVE_COV_SUBSCRIPTIONS){//暂未实现handled = false;}else if(objectId.type == BacnetObjectTypes.OBJECT_OCTETSTRING_VALUE &&objectId.instance == 0 &&propertyId == BacnetPropertyIds.PROP_PRESENT_VALUE){//暂未实现handled = false;}else if(objectId.type == BacnetObjectTypes.OBJECT_GROUP &&propertyId == BacnetPropertyIds.PROP_PRESENT_VALUE){//对象组//暂时未实现handled = false;}else{handled = false;}}private static void ChangeOfValue(DeviceStorage sender, BacnetObjectId object_id, BacnetPropertyIds property_id, uint array_index, IList<BacnetValue> value){System.Threading.ThreadPool.QueueUserWorkItem((o) =>{lock (m_lockObject){Console.WriteLine("Enter ChangeOfValue");//remove old lefto versRemoveOldSubscriptions();//find subscriptionif (!m_subscriptions.ContainsKey(object_id)) return;List<Subscription> subs = m_subscriptions[object_id];//convertList<BacnetPropertyValue> values = new List<BacnetPropertyValue>();BacnetPropertyValue tmp = new BacnetPropertyValue();tmp.property = new BacnetPropertyReference((uint)property_id, array_index);tmp.value = value;values.Add(tmp);//send to allforeach (Subscription sub in subs){if (sub.monitoredProperty.propertyIdentifier == (uint)BacnetPropertyIds.PROP_ALL || sub.monitoredProperty.propertyIdentifier == (uint)property_id){//send notifyif (!sub.reciever.Notify(sub.reciever_address, sub.subscriberProcessIdentifier,m_storage.DeviceId, sub.monitoredObjectIdentifer, (uint)sub.GetTimeRemaining(), sub.issueConfimedNotifications, values))Trace.TraceError("Couldn't send notify");}}}}, null);}/// <summary>/// 订阅描述/// </summary>private class Subscription{public BacnetClient reciever;public BacnetAddress reciever_address;public uint subscriberProcessIdentifier;public BacnetObjectId monitoredObjectIdentifer;public BacnetPropertyReference monitoredProperty;public bool issueConfimedNotifications;public uint lefttime;public DateTime start;public float covIncrement;public Subscription(BacnetClient reciever, BacnetAddress reciever_address,uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifer,BacnetPropertyReference monitoredProperty,bool issueConfimedNotifications, uint lefttime,float covIncrement){this.reciever = reciever;this.reciever_address = reciever_address;this.subscriberProcessIdentifier = subscriberProcessIdentifier;this.monitoredObjectIdentifer = monitoredObjectIdentifer;this.monitoredProperty = monitoredProperty;this.issueConfimedNotifications = issueConfimedNotifications;this.lefttime = lefttime;this.start = DateTime.Now;this.covIncrement = covIncrement;}public int GetTimeRemaining(){if (lefttime == 0) return 0;uint elapse = (uint)(DateTime.Now - start).TotalSeconds;if(lefttime > elapse){return (int)(lefttime - elapse);}return -1;}}//移除旧的订阅private static void RemoveOldSubscriptions(){LinkedList<BacnetObjectId> to_be_deleted = new LinkedList<BacnetObjectId>();foreach(KeyValuePair<BacnetObjectId, List<Subscription>> entry in m_subscriptions){for(int i=0; i<entry.Value.Count; i++){if(entry.Value[i].GetTimeRemaining() < 0){//移除订阅时间为0的属性entry.Value.RemoveAt(i);i--; }}if (entry.Value.Count == 0)to_be_deleted.AddLast(entry.Key);}foreach (BacnetObjectId obj_id in to_be_deleted)m_subscriptions.Remove(obj_id);}private static Subscription HandleSubscriptionRequest(BacnetClient sender, BacnetAddress adr,byte invokde_id,uint subscriberProcessIdentifier,BacnetObjectId monitoredObjectIdentifer,uint property_id, bool cancellationRequest,bool issueConfimedNotifications, uint lefttime,float covIncrement){//移除旧的剩余的订阅RemoveOldSubscriptions();//找存在的List<Subscription> subs = null;Subscription sub = null;if(m_subscriptions.ContainsKey(monitoredObjectIdentifer)){subs = m_subscriptions[monitoredObjectIdentifer];foreach(Subscription s in subs){if(s.reciever.Equals(sender) &&s.reciever_address.Equals(adr) &&s.monitoredObjectIdentifer.Equals(monitoredObjectIdentifer)&&s.monitoredProperty.propertyIdentifier == property_id){sub = s;break;}}}//取消订阅if(cancellationRequest && sub != null){subs.Remove(sub);if (subs.Count == 0)m_subscriptions.Remove(sub.monitoredObjectIdentifer);//发送确认sender.SimpleAckResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV,invokde_id);return null;}//如果需要则创建if(sub == null){sub = new Subscription(sender, adr, subscriberProcessIdentifier,monitoredObjectIdentifer, new BacnetPropertyReference((uint)BacnetPropertyIds.PROP_ALL,System.IO.BACnet.Serialize.ASN1.BACNET_ARRAY_ALL),issueConfimedNotifications, lefttime, covIncrement);if(subs == null){subs = new List<Subscription>();m_subscriptions.Add(sub.monitoredObjectIdentifer, subs);}subs.Add(sub);}//可能需要更新sub.issueConfimedNotifications = issueConfimedNotifications;sub.lefttime = lefttime;sub.start = DateTime.Now;return sub;}/// <summary>///响应订阅的请求/// </summary>private static void OnSubscribeCOV(BacnetClient sender, BacnetAddress adr, byte invokeId, uint subscriberProcessIdentifier, BacnetObjectId monitoredObjectIdentifier,bool cancellationRequest, bool issueConfirmedNotifications, uint lifetime, BacnetMaxSegments maxSegments){lock(m_lockObject){try{Console.WriteLine("Enter OnSubscribeCOV");//创建一个订阅Subscription sub = HandleSubscriptionRequest(sender, adr,invokeId, subscriberProcessIdentifier, monitoredObjectIdentifier,(uint)BacnetPropertyIds.PROP_ALL,cancellationRequest,issueConfirmedNotifications, lifetime, 0);//发送确认sender.SimpleAckResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV,invokeId);//且发送当前值if (!cancellationRequest){System.Threading.ThreadPool.QueueUserWorkItem((o) =>{ IList<BacnetPropertyValue> values;if (m_storage.ReadPropertyAll(sub.monitoredObjectIdentifer, out values)){if (!sender.Notify(adr, sub.subscriberProcessIdentifier,m_storage.DeviceId, sub.monitoredObjectIdentifer,(uint)sub.GetTimeRemaining(),sub.issueConfimedNotifications, values)){Trace.TraceError("Couldn't send notify");}}}, null);}}catch{sender.ErrorResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}}private static void OnSubscribeCOVProperty(BacnetClient sender, BacnetAddress adr, byte invokeId, uint subscriberProcessIdentifier,BacnetObjectId monitoredObjectIdentifier,BacnetPropertyReference monitoredProperty, bool cancellationRequest, bool issueConfirmedNotifications,uint lifetime, float covIncrement, BacnetMaxSegments maxSegments){lock (m_lockObject){try {Console.WriteLine("Enter OnSubscribeCOVProperty");//crceateSubscription sub = HandleSubscriptionRequest(sender, adr,invokeId, subscriberProcessIdentifier,monitoredObjectIdentifier, (uint)BacnetPropertyIds.PROP_ALL,cancellationRequest, issueConfirmedNotifications, lifetime,covIncrement);//send confirmsender.SimpleAckResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY,invokeId);//also send first valuesif (!cancellationRequest){System.Threading.ThreadPool.QueueUserWorkItem((o) =>{IList<BacnetValue> _values;m_storage.ReadProperty(sub.monitoredObjectIdentifer,(BacnetPropertyIds)sub.monitoredProperty.propertyIdentifier,sub.monitoredProperty.propertyArrayIndex, out _values);List<BacnetPropertyValue> values = new List<BacnetPropertyValue>();BacnetPropertyValue tmp = new BacnetPropertyValue();tmp.property = sub.monitoredProperty;tmp.value = _values;values.Add(tmp);if (!sender.Notify(adr, sub.subscriberProcessIdentifier,m_storage.DeviceId, sub.monitoredObjectIdentifer,(uint)sub.GetTimeRemaining(),sub.issueConfimedNotifications, values)){Trace.TraceError("Couldn't send notify");}}, null);}}catch{sender.ErrorResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}}/// <summary>///响应写的请求/// </summary>private static void OnWritePropertyRequest(BacnetClient sender, BacnetAddress adr,byte invokeId, BacnetObjectId objectId, BacnetPropertyValue value,BacnetMaxSegments maxSegments){//先判断这个对象的属性是否支持写入BacnetPropertyIds PropId = (BacnetPropertyIds)value.property.propertyIdentifier;bool AllowWrite =(objectId.Equals("OBJECT_ANALOG_VALUE:0") && (PropId == BacnetPropertyIds.PROP_OUT_OF_SERVICE)) ||(objectId.Equals("OBJECT_ANALOG_VALUE:0") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||(objectId.Equals("OBJECT_ANALOG_INPUT:0") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE)) ||(objectId.Equals("OBJECT_ANALOG_INPUT:1") && (PropId == BacnetPropertyIds.PROP_PRESENT_VALUE));if (AllowWrite == false){sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_WRITE_ACCESS_DENIED);return;}lock (m_lockObject){try{//先写配置xml文件中这个对象DeviceStorage.ErrorCodes code = m_storage.WriteCommandableProperty(objectId,(BacnetPropertyIds)value.property.propertyIdentifier,value.value[0], value.priority);if (code == DeviceStorage.ErrorCodes.NotForMe)code = m_storage.WriteProperty(objectId,(BacnetPropertyIds)value.property.propertyIdentifier,value.property.propertyArrayIndex,value.value);//回应写的请求if (code == DeviceStorage.ErrorCodes.Good){sender.SimpleAckResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY,invokeId);}else{if(code == DeviceStorage.ErrorCodes.WriteAccessDenied){sender.ErrorResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_WRITE_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_WRITE_ACCESS_DENIED);}elsesender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}catch{sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}}/// <summary>///响应读的请求/// </summary>private static void OnReadPropertyRequest(BacnetClient sender, BacnetAddress adr, byte invokeId, BacnetObjectId objectId, BacnetPropertyReference property, BacnetMaxSegments maxSegments){lock (m_lockObject){try{//先看看配置xml文件中是否有这个对象IList<BacnetValue> value;DeviceStorage.ErrorCodes code = m_storage.ReadProperty(objectId, property.GetPropertyId(),property.propertyArrayIndex,out value);if (code == DeviceStorage.ErrorCodes.Good){//回应读的请求sender.ReadPropertyResponse(adr, invokeId,sender.GetSegmentBuffer(maxSegments),objectId, property, value);}else{sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}catch{sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROPERTY,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}}private static void OnReadPropertyMultipleRequest(BacnetClient sender, BacnetAddress adr, byte invokeId, IList<BacnetReadAccessSpecification> properties, BacnetMaxSegments maxSegments){lock(m_lockObject){try{IList<BacnetPropertyValue> value;List<BacnetReadAccessResult> values = new List<BacnetReadAccessResult>();foreach(BacnetReadAccessSpecification p in properties){if(p.propertyReferences.Count == 1 && p.propertyReferences[0].propertyIdentifier == (uint)BacnetPropertyIds.PROP_ALL){if(!m_storage.ReadPropertyAll(p.objectIdentifier, out value)){sender.ErrorResponse(adr,BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE,invokeId, BacnetErrorClasses.ERROR_CLASS_OBJECT,BacnetErrorCodes.ERROR_CODE_UNKNOWN_OBJECT);return;}}else{m_storage.ReadPropertyMultiple(p.objectIdentifier, p.propertyReferences, out value);}values.Add(new BacnetReadAccessResult(p.objectIdentifier, value));}HandleSegmentationResponse(sender, adr, invokeId, maxSegments, (seg) =>{sender.ReadPropertyMultipleResponse(adr, invokeId, seg, values);});}catch{sender.ErrorResponse(adr, BacnetConfirmedServices.SERVICE_CONFIRMED_READ_PROP_MULTIPLE,invokeId, BacnetErrorClasses.ERROR_CLASS_DEVICE,BacnetErrorCodes.ERROR_CODE_OTHER);}}}private static void HandleSegmentationResponse(BacnetClient sender, BacnetAddress adr,byte invoke_id, BacnetMaxSegments max_segments,Action<BacnetClient.Segmentation>transmit){BacnetClient.Segmentation segmentation = sender.GetSegmentBuffer(max_segments);//先发送transmit(segmentation);if (segmentation == null ||segmentation.buffer.result == System.IO.BACnet.Serialize.EncodeResult.Good)return;//启动一个线程去处理段序号 //start new thread to handle the segment sequenceSystem.Threading.ThreadPool.QueueUserWorkItem((o) =>{byte old_max_info_frames = sender.Transport.MaxInfoFrames;//increase max_info_frames, to increase throughput. This might be against 'standard'//增加max_info_frames,以增加吞吐量。这可能违反“标准”。sender.Transport.MaxInfoFrames = segmentation.window_size;while (true){bool more_follows = (segmentation.buffer.result &System.IO.BACnet.Serialize.EncodeResult.NotEnoughBuffer) > 0;//等待 segmentACKif((segmentation.sequence_number -1) % segmentation.window_size==0 ||!more_follows){if (!sender.WaitForAllTransmits(sender.TransmitTimeout)){//Transmit timeour;break;}byte current_number = segmentation.sequence_number;if(!sender.WaitForSegmentAck(adr, invoke_id, segmentation, sender.Timeout)){//Didn't get segmentACKbreak;}if (segmentation.sequence_number != current_number){// a retransmitmore_follows = true;}}else{// a negative segmentACK perhapsbyte current_number = segmentation.sequence_number;//didn't waitsender.WaitForSegmentAck(adr, invoke_id, segmentation, 0);if(segmentation.sequence_number != current_number){// a retransmitmore_follows = true;}}if (more_follows)lock (m_lockObject) transmit(segmentation);elsebreak;}sender.Transport.MaxInfoFrames = old_max_info_frames;});}//当其它设备发出WhoIs时,client回答Iamprivate static void OnWhoIs(BacnetClient sender, BacnetAddress adr, int lowLimit, int highLimit){lock (m_lockObject){if (lowLimit != -1 && m_storage.DeviceId < lowLimit) return;else if (highLimit != -1 && m_storage.DeviceId > highLimit) return;else sender.Iam(m_storage.DeviceId, m_supported_segentation);}}}
}
2 MainForm.cs文件
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;using System.IO.BACnet;namespace ElevatorBacnet
{public partial class MainForm : Form{int currentFloor = 1;uint maxFloor = 9;uint minFloor = 1;uint targetFloor = 1;int barkFloor = -1;Button[] btnLayers;bool IsUp = false;bool IsDown = false;bool Remoteconsigne;#region BACnet的属性//模拟输入对象类型BacnetObjectId Bac_TempMinF = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 0);BacnetObjectId Bac_TempMaxF = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_INPUT, 1);//模拟值对象类型BacnetObjectId Bac_TempCurF = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE, 0);#endregionpublic MainForm(){ InitializeComponent();Init();timerEle.Enabled = true;timerEle.Start();}/// <summary>/// 初始化UI/// </summary>private void Init(){btnLayers = new Button[]{this.btn1,this.btn2,this.btn3, this.btn4,this.btn5, this.btn6, this.btn7, this.btn8, this.btn9};this.FormClosing += MainForm_FormClosing;}private void MainForm_FormClosing(object sender, FormClosingEventArgs e){this.timerEle.Stop();}private void timerEle_Tick(object sender, EventArgs e){IList<BacnetValue> val = null;BacnetActivity.m_storage.ReadProperty(Bac_TempCurF,BacnetPropertyIds.PROP_OUT_OF_SERVICE, 1,out val);Remoteconsigne = (bool)val[0].Value;if(Remoteconsigne == false){BacnetObjectId d;BacnetValue bv;d = new BacnetObjectId(BacnetObjectTypes.OBJECT_ANALOG_VALUE,(uint)0);bv = BacnetActivity.GetBacnetPresentValue(d);BacnetActivity.SetBacnetPresentValue(Bac_TempCurF, bv);}AnimateData();}private void AnimateData(){BacnetValue bv1, bv2;bv1 = BacnetActivity.GetBacnetPresentValue(Bac_TempMaxF);maxFloor = (uint)bv1.Value;tbMaxFloor.Text = maxFloor.ToString();bv2 = BacnetActivity.GetBacnetPresentValue(Bac_TempMinF);minFloor = (uint)bv2.Value;tbMinFloor.Text = minFloor.ToString();if (currentFloor != targetFloor){barkFloor = currentFloor;if (IsUp){currentFloor++;}else if (IsDown){currentFloor--;}this.btnLayers[barkFloor - 1].BackColor = SystemColors.Highlight;this.btnLayers[currentFloor - 1].BackColor = Color.Red;tbcurrentFloor.Text = currentFloor.ToString();if (Remoteconsigne == false){BacnetActivity.SetBacnetPresentValue(Bac_TempCurF, new BacnetValue(BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT,(uint)currentFloor));}}else{Reset();}}void Reset (){IsDown = false;IsUp = false;}/// <summary>///上行/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnUp_Click(object sender, EventArgs e){IsUp = true;}/// <summary>/// 下行/// </summary>/// <param name="sender"></param>/// <param name="e"></param>private void btnDown_Click(object sender, EventArgs e){IsDown = true;}private void numTargetFloor_ValueChanged(object sender, EventArgs e){targetFloor = (uint)numTargetFloor.Value;}}
}
3 MainForm 设计器代码:
namespace ElevatorBacnet
{partial class MainForm{/// <summary>/// 必需的设计器变量。/// </summary>private System.ComponentModel.IContainer components = null;/// <summary>/// 清理所有正在使用的资源。/// </summary>/// <param name="disposing">如果应释放托管资源,为 true;否则为 false。</param>protected override void Dispose(bool disposing){if (disposing && (components != null)){components.Dispose();}base.Dispose(disposing);}#region Windows 窗体设计器生成的代码/// <summary>/// 设计器支持所需的方法 - 不要修改/// 使用代码编辑器修改此方法的内容。/// </summary>private void InitializeComponent(){this.components = new System.ComponentModel.Container();this.gbEleFloor = new System.Windows.Forms.GroupBox();this.btn9 = new System.Windows.Forms.Button();this.btn8 = new System.Windows.Forms.Button();this.btn7 = new System.Windows.Forms.Button();this.btn6 = new System.Windows.Forms.Button();this.btn5 = new System.Windows.Forms.Button();this.btn4 = new System.Windows.Forms.Button();this.btn3 = new System.Windows.Forms.Button();this.btn2 = new System.Windows.Forms.Button();this.btn1 = new System.Windows.Forms.Button();this.btnUp = new System.Windows.Forms.Button();this.numTargetFloor = new System.Windows.Forms.NumericUpDown();this.btnDown = new System.Windows.Forms.Button();this.timerEle = new System.Windows.Forms.Timer(this.components);this.lbMinFloor = new System.Windows.Forms.Label();this.tbMinFloor = new System.Windows.Forms.TextBox();this.tbMaxFloor = new System.Windows.Forms.TextBox();this.lbMaxFloor = new System.Windows.Forms.Label();this.tbcurrentFloor = new System.Windows.Forms.TextBox();this.lbcurrentFloor = new System.Windows.Forms.Label();this.lbtargetFloor = new System.Windows.Forms.Label();this.gbEleFloor.SuspendLayout();((System.ComponentModel.ISupportInitialize)(this.numTargetFloor)).BeginInit();this.SuspendLayout();// // gbEleFloor// this.gbEleFloor.Controls.Add(this.btn9);this.gbEleFloor.Controls.Add(this.btn8);this.gbEleFloor.Controls.Add(this.btn7);this.gbEleFloor.Controls.Add(this.btn6);this.gbEleFloor.Controls.Add(this.btn5);this.gbEleFloor.Controls.Add(this.btn4);this.gbEleFloor.Controls.Add(this.btn3);this.gbEleFloor.Controls.Add(this.btn2);this.gbEleFloor.Controls.Add(this.btn1);this.gbEleFloor.Location = new System.Drawing.Point(31, 7);this.gbEleFloor.Name = "gbEleFloor";this.gbEleFloor.Size = new System.Drawing.Size(200, 435);this.gbEleFloor.TabIndex = 0;this.gbEleFloor.TabStop = false;this.gbEleFloor.Text = "电梯楼层";// // btn9// this.btn9.BackColor = System.Drawing.SystemColors.Highlight;this.btn9.Font = new System.Drawing.Font("微软雅黑", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.btn9.Location = new System.Drawing.Point(6, 15);this.btn9.Name = "btn9";this.btn9.Size = new System.Drawing.Size(73, 41);this.btn9.TabIndex = 8;this.btn9.Text = "9楼";this.btn9.UseVisualStyleBackColor = false;// // btn8// this.btn8.BackColor = System.Drawing.SystemColors.Highlight;this.btn8.Font = new System.Drawing.Font("微软雅黑", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.btn8.Location = new System.Drawing.Point(6, 61);this.btn8.Name = "btn8";this.btn8.Size = new System.Drawing.Size(73, 41);this.btn8.TabIndex = 7;this.btn8.Text = "8楼";this.btn8.UseVisualStyleBackColor = false;// // btn7// this.btn7.BackColor = System.Drawing.SystemColors.Highlight;this.btn7.Font = new System.Drawing.Font("微软雅黑", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.btn7.Location = new System.Drawing.Point(6, 107);this.btn7.Name = "btn7";this.btn7.Size = new System.Drawing.Size(73, 41);this.btn7.TabIndex = 6;this.btn7.Text = "7楼";this.btn7.UseVisualStyleBackColor = false;// // btn6// this.btn6.BackColor = System.Drawing.SystemColors.Highlight;this.btn6.Font = new System.Drawing.Font("微软雅黑", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.btn6.Location = new System.Drawing.Point(6, 153);this.btn6.Name = "btn6";this.btn6.Size = new System.Drawing.Size(73, 41);this.btn6.TabIndex = 5;this.btn6.Text = "6楼";this.btn6.UseVisualStyleBackColor = false;// // btn5// this.btn5.BackColor = System.Drawing.SystemColors.Highlight;this.btn5.Font = new System.Drawing.Font("微软雅黑", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.btn5.Location = new System.Drawing.Point(6, 199);this.btn5.Name = "btn5";this.btn5.Size = new System.Drawing.Size(73, 41);this.btn5.TabIndex = 4;this.btn5.Text = "5楼";this.btn5.UseVisualStyleBackColor = false;// // btn4// this.btn4.BackColor = System.Drawing.SystemColors.Highlight;this.btn4.Font = new System.Drawing.Font("微软雅黑", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.btn4.Location = new System.Drawing.Point(6, 245);this.btn4.Name = "btn4";this.btn4.Size = new System.Drawing.Size(73, 41);this.btn4.TabIndex = 3;this.btn4.Text = "4楼";this.btn4.UseVisualStyleBackColor = false;// // btn3// this.btn3.BackColor = System.Drawing.SystemColors.Highlight;this.btn3.Font = new System.Drawing.Font("微软雅黑", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.btn3.Location = new System.Drawing.Point(6, 291);this.btn3.Name = "btn3";this.btn3.Size = new System.Drawing.Size(73, 41);this.btn3.TabIndex = 2;this.btn3.Text = "3楼";this.btn3.UseVisualStyleBackColor = false;// // btn2// this.btn2.BackColor = System.Drawing.SystemColors.Highlight;this.btn2.Font = new System.Drawing.Font("微软雅黑", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.btn2.Location = new System.Drawing.Point(6, 337);this.btn2.Name = "btn2";this.btn2.Size = new System.Drawing.Size(73, 41);this.btn2.TabIndex = 1;this.btn2.Text = "2楼";this.btn2.UseVisualStyleBackColor = false;// // btn1// this.btn1.BackColor = System.Drawing.SystemColors.Highlight;this.btn1.Font = new System.Drawing.Font("微软雅黑", 10.8F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.btn1.Location = new System.Drawing.Point(6, 383);this.btn1.Name = "btn1";this.btn1.Size = new System.Drawing.Size(73, 41);this.btn1.TabIndex = 0;this.btn1.Text = "1楼";this.btn1.UseVisualStyleBackColor = false;// // btnUp// this.btnUp.Location = new System.Drawing.Point(271, 183);this.btnUp.Name = "btnUp";this.btnUp.Size = new System.Drawing.Size(78, 35);this.btnUp.TabIndex = 1;this.btnUp.Text = "Up";this.btnUp.UseVisualStyleBackColor = true;this.btnUp.Click += new System.EventHandler(this.btnUp_Click);// // numTargetFloor// this.numTargetFloor.Location = new System.Drawing.Point(377, 133);this.numTargetFloor.Maximum = new decimal(new int[] {9,0,0,0});this.numTargetFloor.Minimum = new decimal(new int[] {1,0,0,0});this.numTargetFloor.Name = "numTargetFloor";this.numTargetFloor.Size = new System.Drawing.Size(73, 25);this.numTargetFloor.TabIndex = 2;this.numTargetFloor.Value = new decimal(new int[] {1,0,0,0});this.numTargetFloor.ValueChanged += new System.EventHandler(this.numTargetFloor_ValueChanged);// // btnDown// this.btnDown.Location = new System.Drawing.Point(387, 183);this.btnDown.Name = "btnDown";this.btnDown.Size = new System.Drawing.Size(78, 35);this.btnDown.TabIndex = 3;this.btnDown.Text = "Down";this.btnDown.UseVisualStyleBackColor = true;this.btnDown.Click += new System.EventHandler(this.btnDown_Click);// // timerEle// this.timerEle.Interval = 1000;this.timerEle.Tick += new System.EventHandler(this.timerEle_Tick);// // lbMinFloor// this.lbMinFloor.AutoSize = true;this.lbMinFloor.Font = new System.Drawing.Font("宋体", 10.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.lbMinFloor.Location = new System.Drawing.Point(267, 29);this.lbMinFloor.Name = "lbMinFloor";this.lbMinFloor.Size = new System.Drawing.Size(104, 19);this.lbMinFloor.TabIndex = 4;this.lbMinFloor.Text = "最低楼层:";// // tbMinFloor// this.tbMinFloor.Enabled = false;this.tbMinFloor.Font = new System.Drawing.Font("宋体", 10.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.tbMinFloor.Location = new System.Drawing.Point(377, 26);this.tbMinFloor.Name = "tbMinFloor";this.tbMinFloor.Size = new System.Drawing.Size(73, 28);this.tbMinFloor.TabIndex = 5;this.tbMinFloor.Text = "1";// // tbMaxFloor// this.tbMaxFloor.Enabled = false;this.tbMaxFloor.Font = new System.Drawing.Font("宋体", 10.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.tbMaxFloor.Location = new System.Drawing.Point(377, 60);this.tbMaxFloor.Name = "tbMaxFloor";this.tbMaxFloor.Size = new System.Drawing.Size(73, 28);this.tbMaxFloor.TabIndex = 7;this.tbMaxFloor.Text = "9";// // lbMaxFloor// this.lbMaxFloor.AutoSize = true;this.lbMaxFloor.Font = new System.Drawing.Font("宋体", 10.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.lbMaxFloor.Location = new System.Drawing.Point(267, 63);this.lbMaxFloor.Name = "lbMaxFloor";this.lbMaxFloor.Size = new System.Drawing.Size(104, 19);this.lbMaxFloor.TabIndex = 6;this.lbMaxFloor.Text = "最高楼层:";// // tbcurrentFloor// this.tbcurrentFloor.Enabled = false;this.tbcurrentFloor.Font = new System.Drawing.Font("宋体", 10.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.tbcurrentFloor.Location = new System.Drawing.Point(377, 94);this.tbcurrentFloor.Name = "tbcurrentFloor";this.tbcurrentFloor.Size = new System.Drawing.Size(73, 28);this.tbcurrentFloor.TabIndex = 9;this.tbcurrentFloor.Text = "1";// // lbcurrentFloor// this.lbcurrentFloor.AutoSize = true;this.lbcurrentFloor.Font = new System.Drawing.Font("宋体", 10.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.lbcurrentFloor.Location = new System.Drawing.Point(267, 97);this.lbcurrentFloor.Name = "lbcurrentFloor";this.lbcurrentFloor.Size = new System.Drawing.Size(104, 19);this.lbcurrentFloor.TabIndex = 8;this.lbcurrentFloor.Text = "当前楼层:";// // lbtargetFloor// this.lbtargetFloor.AutoSize = true;this.lbtargetFloor.Font = new System.Drawing.Font("宋体", 10.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));this.lbtargetFloor.Location = new System.Drawing.Point(267, 133);this.lbtargetFloor.Name = "lbtargetFloor";this.lbtargetFloor.Size = new System.Drawing.Size(104, 19);this.lbtargetFloor.TabIndex = 10;this.lbtargetFloor.Text = "目标楼层:";// // MainForm// this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F);this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;this.ClientSize = new System.Drawing.Size(550, 454);this.Controls.Add(this.lbtargetFloor);this.Controls.Add(this.tbcurrentFloor);this.Controls.Add(this.lbcurrentFloor);this.Controls.Add(this.tbMaxFloor);this.Controls.Add(this.lbMaxFloor);this.Controls.Add(this.tbMinFloor);this.Controls.Add(this.lbMinFloor);this.Controls.Add(this.btnDown);this.Controls.Add(this.numTargetFloor);this.Controls.Add(this.btnUp);this.Controls.Add(this.gbEleFloor);this.Name = "MainForm";this.Text = "电梯BACnet---By唠嗑一下";this.gbEleFloor.ResumeLayout(false);((System.ComponentModel.ISupportInitialize)(this.numTargetFloor)).EndInit();this.ResumeLayout(false);this.PerformLayout();}#endregionprivate System.Windows.Forms.GroupBox gbEleFloor;private System.Windows.Forms.Button btn1;private System.Windows.Forms.Button btn9;private System.Windows.Forms.Button btn8;private System.Windows.Forms.Button btn7;private System.Windows.Forms.Button btn6;private System.Windows.Forms.Button btn5;private System.Windows.Forms.Button btn4;private System.Windows.Forms.Button btn3;private System.Windows.Forms.Button btn2;private System.Windows.Forms.Button btnUp;private System.Windows.Forms.NumericUpDown numTargetFloor;private System.Windows.Forms.Button btnDown;private System.Windows.Forms.Timer timerEle;private System.Windows.Forms.Label lbMinFloor;private System.Windows.Forms.TextBox tbMinFloor;private System.Windows.Forms.TextBox tbMaxFloor;private System.Windows.Forms.Label lbMaxFloor;private System.Windows.Forms.TextBox tbcurrentFloor;private System.Windows.Forms.Label lbcurrentFloor;private System.Windows.Forms.Label lbtargetFloor;}
}
4 Program.cs文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Threading;using System.Windows.Forms;namespace ElevatorBacnet
{static class Program{public static int Count;public static int DeviceId = -1;public static string IPAddress = "Default";/// <summary>/// 应用程序的主入口点。/// </summary>[STAThread]static void Main(string[] args){if(args != null && (args.Length >= 1)){if (Int32.TryParse(args[0], out DeviceId) == false)DeviceId = -1;}if(args != null && args.Length == 2){IPAddress = args[1];}/* Semaphore s = new Semaphore(63, 63, "ElevatorBanet{FAED-FAED}");if(s.WaitOne() == true){Count = 64 - s.Release();s.WaitOne();}*/try{BacnetActivity.ReInitialize();Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);Application.Run(new MainForm());}catch(Exception ex){MessageBox.Show("Fatal Error", "ElevatorBanet",MessageBoxButtons.OK, MessageBoxIcon.Error);}// s.Release();}}
}
5 DeviceStorage.xml
<?xml version="1.0"?>
<DeviceStorage xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><Objects><!--设备对象--><Object Type="OBJECT_DEVICE" Instance="64237"><Properties><Property Id="PROP_OBJECT_IDENTIFIER" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"><Value>OBJECT_DEVICE:64237</Value></Property><Property Id="PROP_OBJECT_NAME" TAG="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>ElevatorBacnet</Value></Property><Property Id="PROP_OBJECT_TYPE" TAG="BACNET_APPLICATION_TAG_ENUMERATED"><Value>8</Value></Property><Property Id="PROP_SYSTEM_STATUS" TAG="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_VENDOR_NAME" TAG="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>F. Chaxel,Thanks to Morten Kvistgaard,MIT license,2015</Value></Property><Property Id="PROP_VENDOR_IDENTIFIER" TAG="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>61440</Value></Property><Property Id="PROP_MODEL_NAME" TAG="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Hpmont_FC_2024</Value></Property><Property Id="PROP_FIRMWARE_REVISION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>0.1.0</Value></Property><Property Id="PROP_APPLICATION_SOFTWARE_VERSION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>0.1.0</Value></Property><Property Id="PROP_PROTOCOL_VERSION" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>1</Value></Property><Property Id="PROP_PROTOCOL_REVISION" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>14</Value></Property><Property Id="PROP_PROTOCOL_SERVICES_SUPPORTED" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>01111111101111000011101110000000011010101</Value></Property><Property Id="PROP_PROTOCOL_OBJECT_TYPES_SUPPORTED" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>0000000010101010000000000000000100000000111110111111111</Value></Property><Property Id="PROP_OBJECT_LIST" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"></Property><Property Id="PROP_MAX_APDU_LENGTH_ACCEPTED" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>1476</Value></Property><Property Id="PROP_SEGMENTATION_SUPPORTED" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>3</Value></Property><Property Id="PROP_APDU_TIMEOUT" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>3000</Value></Property><Property Id="PROP_NUMBER_OF_APDU_RETRIES" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>3</Value></Property><Property Id="PROP_DEVICE_ADDRESS_BINDING" Tag="BACNET_APPLICATION_TAG_NULL"/><Property Id="PROP_DATABASE_REVISION" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>0</Value></Property><Property Id="PROP_DESCRIPTION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Free ElevatorController Simulator, X,xm, 2024</Value></Property><Property Id="PROP_LOCATION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>China.Shenzhen</Value></Property></Properties></Object><!--模拟输入 0--><Object Type="OBJECT_ANALOG_INPUT" Instance="0"><Properties><Property Id="PROP_DESCRIPTION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Elevator Min Layer</Value></Property><Property Id="PROP_EVENT_STATE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OBJECT_IDENTIFIER" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"><Value>OBJECT_ANALOG_INPUT:0</Value></Property><Property Id="PROP_OBJECT_NAME" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Min.Layer</Value></Property><Property Id="PROP_OBJECT_TYPE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OUT_OF_SERVICE" Tag="BACNET_APPLICATION_TAG_BOOLEAN"><Value>False</Value></Property><Property Id="PROP_PRESENT_VALUE" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>1</Value></Property><Property Id="PROP_RELIABILITY" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_STATUS_FLAGS" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>0000</Value></Property><Property Id="PROP_UNITS" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>95</Value><!--UNITS_NO_UNITS--></Property></Properties></Object><!--模拟输入 1--><Object Type="OBJECT_ANALOG_INPUT" Instance="1"><Properties><Property Id="PROP_DESCRIPTION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Elevator Max Layer</Value></Property><Property Id="PROP_EVENT_STATE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OBJECT_IDENTIFIER" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"><Value>OBJECT_ANALOG_INPUT:0</Value></Property><Property Id="PROP_OBJECT_NAME" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Max.Layer</Value></Property><Property Id="PROP_OBJECT_TYPE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OUT_OF_SERVICE" Tag="BACNET_APPLICATION_TAG_BOOLEAN"><Value>False</Value></Property><Property Id="PROP_PRESENT_VALUE" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>9</Value></Property><Property Id="PROP_RELIABILITY" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_STATUS_FLAGS" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>0000</Value></Property><Property Id="PROP_UNITS" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>95</Value><!--UNITS_NO_UNITS--></Property></Properties></Object><!--模拟值--><Object Type="OBJECT_ANALOG_VALUE" Instance="0"><Properties><Property Id="PROP_DESCRIPTION" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Current Layer</Value></Property><Property Id="PROP_EVENT_STATE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_OBJECT_IDENTIFIER" Tag="BACNET_APPLICATION_TAG_OBJECT_ID"><Value>OBJECT_ANALOG_VALUE:0</Value></Property><Property Id="PROP_OBJECT_NAME" Tag="BACNET_APPLICATION_TAG_CHARACTER_STRING"><Value>Current.Layer</Value></Property><Property Id="PROP_OBJECT_TYPE" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>2</Value></Property><Property Id="PROP_OUT_OF_SERVICE" Tag="BACNET_APPLICATION_TAG_BOOLEAN"><Value>False</Value></Property><Property Id="PROP_PRESENT_VALUE" Tag="BACNET_APPLICATION_TAG_UNSIGNED_INT"><Value>3</Value></Property><Property Id="PROP_RELIABILITY" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>0</Value></Property><Property Id="PROP_STATUS_FLAGS" Tag="BACNET_APPLICATION_TAG_BIT_STRING"><Value>0000</Value></Property><Property Id="PROP_UNITS" Tag="BACNET_APPLICATION_TAG_ENUMERATED"><Value>95</Value></Property></Properties></Object></Objects>
</DeviceStorage>
小结
简要介绍了BACnet模拟设备,包含基本的读取功能, 订阅功能,返回对象列表属性功能。 完整工程代码下载地址:https://download.csdn.net/download/weixin_40314351/89161436