C# 与 BACnet 服务器的通信

BACnet 简介

BACnet 是一种专为智能建筑设计的通信协议,已被国际标准化组织(ISO)、美国国家标准协会(ANSI)及美国采暖、制冷与空调工程师学会(ASHRAE)等机构标准化。它主要用于智能建筑的控制系统,如暖通空调(HVAC)系统、照明控制、门禁、火警检测等设备。BACnet 的优势在于降低了系统维护成本,简化了安装过程,并提供五种行业标准协议,避免了设备供应商的垄断,极大提升了系统的扩展性和兼容性。

实例概述

本文将介绍两个 C# 与 BACnet 服务器通信的实例,代码和 BACnet 模拟器软件下载地址将在文末提供。

第一个实例 :实现AO类型等点值的读取。
第二个实例:实现BO类型等点开关状态的读写功能。

注意:由于端口冲突等原因,BACnet 模拟器和客户端必须在不同的电脑上,并且位于同一网段才能进行测试。

第一个实例:值的读取

在 BACnet 模拟器中添加几个 AO 点,设定值为 26.5,并通过 C# 代码读取该值,关键代码如下:

cs 复制代码
// 初始化方法
public void init()
{
    try
    {
        // 获取本机所有可用的IP地址
        string[] availableIps = GetAvailableIps();
        
        // 遍历获取到的IP地址列表(目前注释掉了日志记录代码)
        for (int i = 0; i < availableIps.Length; i++)
            //PUBLIC.pubfun.Add2List("获取到有效IP:" + availableIps[i]);

        // 调用设备添加方法
        AddDevice();
        // 延时 5000 毫秒(目前注释掉了,可能是为了调试)
        //Thread.Sleep(5000);
        // 调用添加点的方法(目前注释掉了,可能是为了调试)
        //AddPoint();
    }
    catch (Exception err)
    {
        // 捕获异常并记录错误(目前注释掉了日志记录代码)
        //PUBLIC.pubfun.Add2List("init bac err:" + err.ToString());
    }
}

// 获取本地所有可用的IP地址
public string[] GetAvailableIps()
{
    List<string> list = new List<string>();

    // 获取所有网络接口
    foreach (NetworkInterface networkInterface in NetworkInterface.GetAllNetworkInterfaces())
    {
        // 筛选出有效的网络接口,排除只接收的接口和回环接口
        if (!networkInterface.IsReceiveOnly && networkInterface.OperationalStatus == OperationalStatus.Up && 
            (networkInterface.SupportsMulticast && networkInterface.NetworkInterfaceType != NetworkInterfaceType.Loopback))
        {
            // 获取IP属性
            IPInterfaceProperties ipProperties = networkInterface.GetIPProperties();

            // 检查是否有有效的网关地址
            if (ipProperties.GatewayAddresses != null && ipProperties.GatewayAddresses.Count != 0 &&
                (ipProperties.GatewayAddresses.Count != 1 || !(ipProperties.GatewayAddresses[0].Address.ToString() == "0.0.0.0")))
            {
                // 遍历获取所有的Unicast地址(IPv4地址)
                foreach (UnicastIPAddressInformation addressInformation in ipProperties.UnicastAddresses)
                {
                    if (addressInformation.Address.AddressFamily == AddressFamily.InterNetwork)
                        list.Add(addressInformation.Address.ToString()); // 添加IPv4地址到列表
                }
            }
        }
    }
    // 返回IP地址列表
    return list.ToArray();
}

// 发送设备查找指令(WHOIS)
public void SendWhoIs()
{
    // 清空设备字典
    //m_devices.Clear();
    dic_devices.Clear();
    m_devices[comm].Devices.Clear();

    // 发送WHOIS指令,查找设备
    comm.WhoIs(-1, -1);

    // 等待设备响应
    Thread.Sleep(whowait);
}

// 查找并添加设备下的点(此方法较复杂,涉及到不同的处理模式)
public void AddPoint()
{
    try
    {
        lock (dic_devices) // 加锁,防止多线程操作冲突
        {
            dic_units.Clear(); // 清空点列表

            // 遍历所有设备
            foreach (var item in dic_devices)
            {
                foreach (var it in item.Value)
                {
                    // 尝试将设备信息转换为 KeyValuePair 类型
                    KeyValuePair<BacnetAddress, uint>? nullable = it as KeyValuePair<BacnetAddress, uint>?;
                    if (!nullable.HasValue)
                        return; // 如果转换失败,则退出

                    BacnetAddress key = nullable.Value.Key; // 获取设备的地址
                    uint num1 = nullable.Value.Value; // 获取设备的ID

                    // 如果使用的是 MSTP 协议,且源地址为 -1
                    if (comm.Transport is BacnetMstpProtocolTransport && (int)((BacnetMstpProtocolTransport)comm.Transport).SourceAddress == -1)
                    {
                        // 这里留有注释说明,需要补充处理的逻辑
                        //PUBLIC.pubfun.Add2List("走到了一个特别的地方 需要补充处理。。。");
                        //PUBLIC.pubfun.log("走到了一个特别的地方 需要补充处理。。。", "");
                    }

                    int timeout = comm.Timeout; // 获取通信超时设置
                    IList<BacnetValue> value_list1 = null; // 存储设备对象列表的变量

                    try
                    {
                        if (readmode == 1) // 如果是多点模式
                        {
                            #region 多点模式处理

                            // 如果设备对象列表为空,尝试读取对象列表
                            if (value_list1 == null)
                            {
                                try
                                {
                                    // 发送读取设备对象的请求
                                    bool ret = comm.ReadPropertyRequest(
                                        key,
                                        new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, num1),
                                        BacnetPropertyIds.PROP_OBJECT_LIST,
                                        out value_list1, (byte)0,
                                        uint.MaxValue);

                                    // 如果没有响应,重置 value_list1
                                    if (!ret)
                                    {
                                        value_list1 = null;
                                    }
                                }
                                catch (Exception ex)
                                {
                                    // 捕获异常并处理
                                    value_list1 = null;
                                }
                            }

                            // 如果仍然获取不到设备对象,记录错误
                            if (value_list1 == null)
                            {
                                //PUBLIC.pubfun.Add2List("获取不到设备对象通讯终断!");
                                //PUBLIC.pubfun.log("获取不到设备对象通讯终断!", "");
                            }
                            else
                            {
                                // 遍历返回的设备对象列表
                                foreach (BacnetValue bacnetValue in value_list1)
                                {
                                    BacnetObjectId object_id = (BacnetObjectId)bacnetValue.Value;
                                    // 将设备对象添加到列表中
                                    this.AddObjectEntry(comm, key, null, object_id, num1);
                                }
                            }

                            #endregion
                        }

                        if (readmode == 0) // 如果是单点模式
                        {
                            #region 单点模式处理

                            if (value_list1 == null)
                            {
                                try
                                {
                                    // 发送读取设备对象的请求
                                    if (!comm.ReadPropertyRequest(key, new BacnetObjectId(BacnetObjectTypes.OBJECT_DEVICE, num1), BacnetPropertyIds.PROP_OBJECT_LIST, out value_list1, (byte)0, 0))
                                    {
                                        // 获取不到设备对象,记录错误
                                        return;
                                    }
                                }
                                catch (Exception ex)
                                {
                                    // 捕获异常并处理
                                    return;
                                }
                                if (value_list1 != null && value_list1.Count == 1 && value_list1[0].Value is uint)
                                {
                                    uint count = (uint)value_list1[0].Value;
                                    // 如果获取到对象列表,逐个添加
                                    this.AddObjectListOneByOneAsync(comm, key, num1, count);
                                }
                                else
                                {
                                    // 获取不到对象数量,记录错误
                                }
                            }
                            else
                            {
                                // 如果有设备对象,直接添加
                                foreach (BacnetValue bacnetValue in value_list1)
                                {
                                    BacnetObjectId object_id = (BacnetObjectId)bacnetValue.Value;
                                    this.AddObjectEntry(comm, key, null, object_id, num1);
                                }
                            }

                            #endregion
                        }
                    }
                    finally
                    {
                        // 恢复默认光标(此处可以有其他清理操作)
                        //this.Cursor = Cursors.Default;
                    }
                }
            }
        }
    }
    catch (Exception err)
    {
        // 发生异常时记录错误
        //PUBLIC.pubfun.Add2List("AddPoint err:" + err.ToString());
        //PUBLIC.pubfun.log("AddPoint err:" , err.ToString());
    }
}

 // 读取值的主方法
public void ReadTags()
{
    // 清空存储单位值和设备值的字典
    dic_unit_value.Clear();
    dic_values.Clear();
    
    try
    {
        // 锁定设备字典,避免多线程冲突
        lock (dic_devices)
        {
            // 锁定单位字典
            lock (dic_units)
            {
                // 遍历每个单位
                foreach (var units in dic_units)
                {
                    BacnetAddress key = null; // 存储设备地址
                    BacnetObjectId object_id = new BacnetObjectId(); // 存储对象ID
                    string m_tagid = "", m_unitid = ""; // 标签ID和单位ID
                    KeyValuePair<BacnetAddress, uint>? nullable; // 可空的键值对

                    #region 取出参数基本信息
                    // 获取单位对应的设备信息
                    foreach (var it in dic_devices[string.Format("{0}:{1}", units.Key.Split(':')[0], units.Key.Split(':')[1])])
                    {
                        nullable = it as KeyValuePair<BacnetAddress, uint>?;
                        if (!nullable.HasValue)
                            return; // 如果转换失败,直接返回
                        key = nullable.Value.Key; // 获取设备地址
                        uint num1 = nullable.Value.Value; // 获取设备端口号
                        m_unitid = units.Key; // 获取单位ID
                        m_tagid = string.Format("{0}:{1}", units.Key.Split(':')[0], units.Key.Split(':')[1]); // 生成标签ID
                        object_id = (BacnetObjectId)units.Value; // 获取对象ID(如模拟输入等)
                    }
                    #endregion

                    #region 读值操作
                    try
                    {
                        // 创建属性引用数组,准备读取属性
                        BacnetPropertyReference[] propertyReferenceArray = new BacnetPropertyReference[1]
                        {
                            new BacnetPropertyReference(8U, uint.MaxValue) // 属性ID为8(例如,某个特定数据属性)
                        };
                        IList<BacnetReadAccessResult> list = null; // 用于存储读取结果的列表

                        // 判断读取模式
                        if (readmode == 0)
                        {
                            #region 单个属性读取方式
                            try
                            {
                                // 调用单个属性读取方法
                                if (!this.ReadAllPropertiesBySingle(comm, key, object_id, out list))
                                {
                                    // 设备通讯发生错误
                                    // 记录错误信息(注释掉的部分表示日志处理)
                                    continue; // 继续下一个单位
                                }
                            }
                            catch (Exception ex2)
                            {
                                // 处理读取时发生的异常
                                continue; // 继续下一个单位
                            }
                            #endregion
                        }

                        if (readmode == 1)
                        {
                            #region 多个属性一起读取的方式
                            try
                            {
                                // 关键的读取请求
                                if (!comm.ReadPropertyMultipleRequest(key, object_id, (IList<BacnetPropertyReference>)propertyReferenceArray, out list, (byte)0))
                                {
                                    // 记录设备通讯错误(注释掉的部分表示日志处理)
                                    continue; // 继续下一个单位
                                }
                            }
                            catch (Exception ex1)
                            {
                                // 如果多个属性读取失败,尝试单个属性读取
                                try
                                {
                                    if (!this.ReadAllPropertiesBySingle(comm, key, object_id, out list))
                                    {
                                        // 记录设备通讯错误(注释掉的部分表示日志处理)
                                        continue; // 继续下一个单位
                                    }
                                }
                                catch (Exception ex2)
                                {
                                    // 处理读取时发生的异常
                                    continue; // 继续下一个单位
                                }
                            }
                            #endregion
                        }

                        // 如果读取成功,处理返回的值
                        if (list[0].values != null)
                        {
                            foreach (BacnetPropertyValue bacnetPropertyValue in (IEnumerable<BacnetPropertyValue>)list[0].values)
                            {
                                string m_tag_name = bacnetPropertyValue.property.ToString(); // 获取标签名
                                string m_tag_value = "";
                                if (bacnetPropertyValue.value[0].Value != null)
                                    m_tag_value = bacnetPropertyValue.value[0].Value.ToString(); // 获取标签值
                                
                                // 将值存入单位值字典
                                dic_2_setvalue(dic_unit_value, m_unitid, m_tag_name, m_tag_value);
                            }
                        }
                    }
                    finally
                    {
                        // 释放光标或其他资源(注释掉的部分可以用于恢复状态)
                    }
                    #endregion
                }
            }
        }
        
        // 结构变换,对单位值进行重组
        #region 结构变换
        foreach (var item_value in dic_unit_value)
        {
            if (!item_value.Value.ContainsKey("PROP_OBJECT_NAME"))
            {
                continue; // 如果没有对象名称,跳过
            }
            string m_temp_key = string.Format("{0}:{1}", item_value.Key.Split(':')[0], item_value.Key.Split(':')[1]);
            // 将单位值添加到结果字典
            dic_2_setvalue(dic_values, m_temp_key + ":" + item_value.Value["PROP_OBJECT_NAME"], "SOURCENAME", item_value.Key);
            foreach (var unit_value in item_value.Value)
            {
                dic_2_setvalue(dic_values, m_temp_key + ":" + item_value.Value["PROP_OBJECT_NAME"], unit_value.Key, unit_value.Value);
            }
        }
        #endregion
    }
    catch (Exception err)
    {
        // 处理全局异常,记录错误(注释掉的部分表示日志处理)
        // Refresh(); // 或者其他恢复操作
    }
}
第二个实例:开关的读写

模拟器设置一个 BO 点,并实现开关状态的读写功能,主要代码如下:

cs 复制代码
// 读取属性 -------------------------------------------------------------------------
public bool SendReadProperty(
    int deviceidx,                 // 设备索引
    uint instance,                 // 实例
    int arrayidx,                  // 数组索引
    BACnetEnums.BACNET_OBJECT_TYPE objtype, // 对象类型
    BACnetEnums.BACNET_PROPERTY_ID objprop, // 属性ID
    Property property              // 属性对象
)
{
    // 参数说明:
    //   Device index (设备索引,用于网络和MAC地址)
    //   Object Type (对象类型)
    //   Property ID (属性ID)
    //   Value returned (返回值)

    // 如果设备索引无效或超出范围,返回false
    if ((deviceidx < 0) || (deviceidx >= BACnetData.Devices.Count)) return false;

    // 获取设备的远程端点信息
    IPEndPoint remoteEP = BACnetData.Devices[deviceidx].ServerEP;
    if (remoteEP == null) return false;

    // 如果属性对象为空,返回false
    if (property == null) return false;

    // 定义发送和接收的字节数组
    Byte[] sendBytes = new Byte[50];
    Byte[] recvBytes = new Byte[512];
    uint len;

    // 设置 BVLL 部分(BACnet Virtual Link Layer)
    sendBytes[0] = BACnetEnums.BACNET_BVLC_TYPE_BIP;
    sendBytes[1] = BACnetEnums.BACNET_UNICAST_NPDU;
    sendBytes[2] = 0x00;
    sendBytes[3] = 0x00;  // BVLL长度,稍后修正(可能为24)

    // 设置 NPDU 部分(Network Protocol Data Unit)
    sendBytes[4] = BACnetEnums.BACNET_PROTOCOL_VERSION;
    if (BACnetData.Devices[deviceidx].SourceLength == 0)
        sendBytes[5] = 0x04;  // 控制标志,无目标地址
    else
        sendBytes[5] = 0x24;  // 控制标志,有广播或目标地址

    len = 6;
    if (BACnetData.Devices[deviceidx].SourceLength > 0)
    {
        // 获取设备的网络号(例如:2001)
        byte[] temp2 = new byte[2];
        temp2 = BitConverter.GetBytes(BACnetData.Devices[deviceidx].Network);
        sendBytes[len++] = temp2[1];
        sendBytes[len++] = temp2[0];

        // 获取设备的MAC地址
        byte[] temp4 = new byte[4];
        temp4 = BitConverter.GetBytes(BACnetData.Devices[deviceidx].MACAddress);

        sendBytes[len++] = 0x01;  // MAC地址长度
        sendBytes[len++] = temp4[0];
        sendBytes[len++] = 0xFF;  // 跳数计数 = 255
    }

    // 设置 APDU 部分(Application Protocol Data Unit)
    sendBytes[len++] = 0x00;  // 控制标志
    sendBytes[len++] = 0x05;  // 最大APDU长度(1476)

    // 创建调用计数器
    sendBytes[len++] = (byte)(InvokeCounter);
    InvokeCounter = ((InvokeCounter + 1) & 0xFF);

    sendBytes[len++] = 0x0C;  // 服务选择:读取属性请求

    // 设置服务请求部分(APDU的可变部分):
    // 设置对象ID(上下文标记)
    len = APDU.SetObjectID(ref sendBytes, len, objtype, instance);

    // 设置属性ID(上下文标记)
    len = APDU.SetPropertyID(ref sendBytes, len, objprop);

    // 可选的数组索引
    if (arrayidx >= 0)
        len = APDU.SetArrayIdx(ref sendBytes, len, arrayidx);

    // 修正BVLL长度
    sendBytes[3] = (byte)len;

    // 创建计时器(我们也可以使用阻塞的recvFrom方法)
    Timer ReadPropTimer = new Timer();
    try
    {
        int Count = 0;
        using (ReadPropTimer)
        {
            // 绑定计时器事件
            ReadPropTimer.Tick += new EventHandler(Timer_Tick);

            while (Count < 3)
            {
                // 禁用广播
                SendUDP.EnableBroadcast = false;
                // 发送数据包
                SendUDP.Send(sendBytes, (int)len, remoteEP);

                // 启动计时器
                TimerDone = false;
                ReadPropTimer.Interval = 400;  // 100毫秒
                ReadPropTimer.Start();
                while (!TimerDone)
                {
                    // 等待确认响应
                    Application.DoEvents();

                    if (SendUDP.Client.Available > 0)
                    {
                        // 接收响应数据
                        recvBytes = SendUDP.Receive(ref remoteEP);

                        int APDUOffset = NPDU.Parse(recvBytes, 4); // BVLL始终为4字节

                        // 检查APDU响应
                        if (recvBytes[APDUOffset] == 0x30)  // 确认请求
                        {
                            // 验证Invoke ID是否一致
                            byte ic = (byte)(InvokeCounter == 0 ? 255 : InvokeCounter - 1);
                            if (ic == recvBytes[APDUOffset + 1])
                            {
                                // 解析返回的属性数据
                                APDU.ParseProperty(ref recvBytes, APDUOffset, property);
                                return true;  // 成功,跳出循环
                            }
                        }
                    }
                }
                Count++;  // 增加重试次数
                BACnetData.PacketRetryCount++;  // 计数器增加
                ReadPropTimer.Stop(); // 停止计时器,准备下次重试
            }
            return false;  // 重试次数已达到上限,返回失败
        }
    }
    finally
    {
        // 确保计时器停止
        ReadPropTimer.Stop();
    }
}



   public bool /*BACnetStack*/ SendWriteProperty(
    int deviceidx,                         // 设备索引(用于获取网络和MAC地址)
    uint instance,                         // 实例号
    int arrayidx,                          // 数组索引
    BACnetEnums.BACNET_OBJECT_TYPE objtype, // 对象类型
    BACnetEnums.BACNET_PROPERTY_ID objprop, // 属性ID
    Property property,                     // 属性值
    int priority                           // 优先级
)
{
    // 创建并发送一个确认请求
    if ((deviceidx < 0) || (deviceidx >= BACnetData.Devices.Count)) return false; // 如果设备索引无效,返回false

    IPEndPoint remoteEP = BACnetData.Devices[deviceidx].ServerEP;  // 获取远程设备的服务器端点
    if (remoteEP == null) return false;  // 如果远程端点为空,返回false

    if (property == null) return false;  // 如果属性为空,返回false

    Byte[] sendBytes = new Byte[50];  // 定义发送字节数组
    Byte[] recvBytes = new Byte[512]; // 定义接收字节数组
    uint len;  // 定义数据长度

    // BVLL部分(BACnet Virtual Link Layer)
    sendBytes[0] = BACnetEnums.BACNET_BVLC_TYPE_BIP;  // BVLC类型,BIP表示IP协议
    sendBytes[1] = BACnetEnums.BACNET_UNICAST_NPDU;   // NPDU类型,表示单播
    sendBytes[2] = 0x00;  // 保留字段
    sendBytes[3] = 0x00;  // BVLL长度(可能是24?)

    // NPDU部分(Network Protocol Data Unit,网络协议数据单元)
    sendBytes[4] = BACnetEnums.BACNET_PROTOCOL_VERSION;  // BACnet协议版本
    if (BACnetData.Devices[deviceidx].SourceLength == 0)
        sendBytes[5] = 0x04;  // 控制标志,表示没有目标地址
    else
        sendBytes[5] = 0x24;  // 控制标志,表示有广播或目标地址

    len = 6;  // 初始长度为6
    if (BACnetData.Devices[deviceidx].SourceLength > 0)
    {
        // 获取网络号(例如:2001)
        byte[] temp2 = new byte[2];
        temp2 = BitConverter.GetBytes(BACnetData.Devices[deviceidx].Network);  // 获取设备网络号
        sendBytes[len++] = temp2[1];
        sendBytes[len++] = temp2[0];

        // 获取MAC地址(例如:0x0D)
        byte[] temp4 = new byte[4];
        temp4 = BitConverter.GetBytes(BACnetData.Devices[deviceidx].MACAddress);  // 获取设备的MAC地址
        sendBytes[len++] = 0x01;  // MAC地址长度
        sendBytes[len++] = temp4[0];

        sendBytes[len++] = 0xFF;  // 跳数(Hop Count)= 255
    }

    // APDU部分(Application Protocol Data Unit,应用协议数据单元)
    sendBytes[len++] = 0x00;  // 控制标志
    sendBytes[len++] = 0x05;  // 最大APDU长度(1476字节)

    // 创建调用计数器(Invoke Counter)
    sendBytes[len++] = (byte)(InvokeCounter);  // 使用当前InvokeCounter
    InvokeCounter = ((InvokeCounter + 1) & 0xFF);  // 更新InvokeCounter,防止溢出

    sendBytes[len++] = 0x0F;  // 服务选择码:表示Write Property请求

    // 设置服务请求部分(APDU的可变部分):
    len = APDU.SetObjectID(ref sendBytes, len, objtype, instance);  // 设置对象ID(上下文标签)
    len = APDU.SetPropertyID(ref sendBytes, len, objprop);  // 设置属性ID(上下文标签)

    // 如果有数组索引,设置数组索引
    if (arrayidx >= 0)
        len = APDU.SetArrayIdx(ref sendBytes, len, arrayidx);

    // 设置要发送的属性值
    len = APDU.SetProperty(ref sendBytes, len, property);

    // 如果有优先级,设置优先级
    if (priority > 0)
        len = APDU.SetPriority(ref sendBytes, len, priority);

    // 修正BVLL长度
    sendBytes[3] = (byte)len;

    // 创建定时器(我们也可以使用阻塞式recvFrom)
    Timer ReadPropTimer = new Timer();

    try
    {
        using (ReadPropTimer)
        {
            int Count = 0;
            ReadPropTimer.Tick += new EventHandler(Timer_Tick);  // 定时器Tick事件

            // 循环最多3次发送请求
            while (Count < 3)
            {
                SendUDP.EnableBroadcast = false;  // 禁用广播
                SendUDP.Send(sendBytes, (int)len, remoteEP);  // 发送数据包

                // 启动定时器
                TimerDone = false;
                ReadPropTimer.Interval = 400; // 设置定时器间隔
                ReadPropTimer.Start();

                // 等待直到定时器完成
                while (!TimerDone)
                {
                    // 等待确认响应
                    Application.DoEvents();

                    // 如果收到数据包
                    if (SendUDP.Client.Available > 0)
                    {
                        // 接收数据
                        recvBytes = SendUDP.Receive(ref remoteEP);

                        // 解析NPDU部分,返回APDU的偏移量
                        int APDUOffset = NPDU.Parse(recvBytes, 4); // BVLL始终为4字节

                        // 检查APDU响应类型,确定如何处理
                        if (recvBytes[APDUOffset] == 0x20)  // 确认请求类型
                        {
                            // 验证Invoke ID是否一致
                            byte ic = (byte)(InvokeCounter == 0 ? 255 : InvokeCounter - 1);
                            if (ic == recvBytes[APDUOffset + 1])
                            {
                                return true;  // 如果Invoke ID匹配,返回成功
                            }
                        }
                    }
                }
                Count++;  // 增加重试次数
                BACnetData.PacketRetryCount++;  // 增加重试计数
                ReadPropTimer.Stop();  // 停止定时器,准备下一轮循环
            }
            return false;  // 如果3次重试都没有成功,返回失败
        }
    }
    finally
    {
        ReadPropTimer.Stop();  // 确保定时器停止
    }
}
总结

上述两个实例展示了如何在 C# 中实现与 BACnet 服务器的通信。若需要查看完整的代码和调试程序,请下载源码。

源码下载地址:https://download.csdn.net/download/weixin_44643352/90053081?spm=1001.2014.3001.5501

相关推荐
茂茂在长安18 分钟前
Linux 命令大全完整版(11)
java·linux·运维·服务器·前端·centos
pchmi20 分钟前
C#贪心算法
贪心算法·c#
小白&12331 分钟前
Linux-CentOS 7安装
linux·运维·服务器
木谷羊宫切割2 小时前
玩机日记 12 群晖部署AList并配置SSL,安装opkg,使用rclone挂载到本地
服务器·网络协议·ssl
TT-Kun3 小时前
Linux | 进程控制(进程终止与进程等待)
linux·运维·服务器
...:...:...3 小时前
Linux 第三次脚本作业
linux·运维·服务器
菜菜小蒙4 小时前
【Linux】基于UDP/TCP服务器与客户端的实现
linux·服务器·udp
且听风吟ayan4 小时前
leetcode day20 滑动窗口209+904
算法·leetcode·c#
web2u4 小时前
Docker入门及基本概念
java·运维·服务器·spring·docker·容器
Anna_Tong5 小时前
阿里云如何协助解决操作系统兼容性问题
linux·服务器·ubuntu·阿里云·centos·云计算·系统迁移