模拟BACnet设备(八)

8 篇文章 1 订阅

前言

前面一到七篇,从理论,工具到实践介绍了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
            {
                //init
                if (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);
                }
                else
                    deviceId = (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 point
                BacnetIpUdpProtocolTransport udp = new
                    BacnetIpUdpProtocolTransport(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 index
                    value = 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 whole
                    BacnetValue[] 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 false
                b.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;
               //可以是任何model
                v.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 vers
                    RemoveOldSubscriptions();

                    //find subscription
                    if (!m_subscriptions.ContainsKey(object_id)) return;
                    List<Subscription> subs = m_subscriptions[object_id];
                     
                    //convert
                    List<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 all
                    foreach (Subscription sub in subs)
                    {
                        if (sub.monitoredProperty.propertyIdentifier == (uint)BacnetPropertyIds.PROP_ALL || sub.monitoredProperty.propertyIdentifier == (uint)property_id)
                        {
                            //send notify
                            if (!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);
                        }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);
                }
            }
        }

        /// <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
            {
                //init
                if (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);
                }
                else
                    deviceId = (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 point
                BacnetIpUdpProtocolTransport udp = new
                    BacnetIpUdpProtocolTransport(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);
            }
        }
        #endregion

        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 index
                    value = 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 whole
                    BacnetValue[] 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 false
                b.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;
               //可以是任何model
                v.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 vers
                    RemoveOldSubscriptions();

                    //find subscription
                    if (!m_subscriptions.ContainsKey(object_id)) return;
                    List<Subscription> subs = m_subscriptions[object_id];
                     
                    //convert
                    List<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 all
                    foreach (Subscription sub in subs)
                    {
                        if (sub.monitoredProperty.propertyIdentifier == (uint)BacnetPropertyIds.PROP_ALL || sub.monitoredProperty.propertyIdentifier == (uint)property_id)
                        {
                            //send notify
                            if (!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");

                    //crceate
                    Subscription sub = HandleSubscriptionRequest(sender, adr,
                        invokeId, subscriberProcessIdentifier,
                        monitoredObjectIdentifier, (uint)BacnetPropertyIds.PROP_ALL,
                        cancellationRequest, issueConfirmedNotifications, lifetime,
                        covIncrement);

                    //send confirm
                    sender.SimpleAckResponse(adr,
                        BacnetConfirmedServices.SERVICE_CONFIRMED_SUBSCRIBE_COV_PROPERTY,
                        invokeId);

                    //also send first values
                    if (!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);
                        }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);
                }
            }
        }

        /// <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 sequence
            System.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;

                    //等待 segmentACK
                    if((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 segmentACK
                            break;
                        }
                        if (segmentation.sequence_number != current_number)
                        {
                            // a retransmit
                            more_follows = true;
                        }
                    }
                    else
                    {
                        // a negative segmentACK perhaps
                        byte current_number = segmentation.sequence_number;
                        //didn't wait
                        sender.WaitForSegmentAck(adr, invoke_id, segmentation, 0);
                        if(segmentation.sequence_number != current_number)
                        {
                            // a retransmit
                            more_follows = true;
                        }
                    }

                    if (more_follows)
                        lock (m_lockObject) transmit(segmentation);
                    else
                        break;
                }
                sender.Transport.MaxInfoFrames = old_max_info_frames;
            });
        }

        //当其它设备发出WhoIs时,client回答Iam
        private 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);

        #endregion

        public 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();

        }

        #endregion

        private 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值