BACnet协议读取与发送

因为项目的需求,需要对接某个厂商的BACnet协议。

可以说是这协议的坑真的不少,自学的时候遇到了一大堆问题…

注意

本文档仅对python完成读取与发送BACnet协议数据的流程做出教程,不对其中的BUG做出解释。一切以本人踩坑为准.jpg

我的提问:

我这边的需求:因为BACnet没有主动推送数据的方式,我现在是写了个循环,对device设备号进行轮巡读取。但是每轮光是读取就需要花费3秒时间(600+个device实例)。各位如果有什么方法优化这个轮巡的话可以直接写在评论区进行交流,先感谢各位了!

更新

因为BACnet的扫描与通信注定不能跨网段,如果要实现跨网段搜索,需要BBMD设备。

开发环境

依旧是python≥3.6,使用了BAC0库。直接PIP安装,这里放上相关的GitHub源码,调试的时候可能会用的上。
BAC0-GitHub
BACpypes(BAC0的依赖库)

BACnet相关基础知识

楼宇自动控制网络数据通讯协议(即: A Data Communication Protocol for Building Automation and Control Networks,简称《BACnet协议》)。其相关的基础知识,这里放上百度百科链接,感兴趣的可以去查询,我这边也只是做了初步的了解。
BACnet百度百科

BACnet格式

我这里直接使用了BACnet模拟器创建了一组虚拟的设备,设置了虚拟的参数以供自己调试。感谢网上大神们无私奉献的链接,直接拿来下载用了,这里也提供给大家。
BACnet模拟器:BACnet Simulator,验证码=gcfb
BACnet调试工具:BACnet调试工具,验证码=mjp7
BACnet调试工具
其中,“Device X”称作device,子项目被称作type,子项目中的各项被称作属性和值,也就是property和value。在代码中,因为“-”具有很多特殊含义,于是代码中规定这些属性的名称按照小驼峰式命名规则。(例:present-value属性,在代码中的名称为presentValue)

BACnet代码

BACnet设备查找

BACnet协议允许通过whois方法扫描某一局域网下的BACnet设备。lite参数中的IP表示运行代码的本机IP,并非是BACnet设备或BACnet服务器的IP。

第一大坑:如果使用BACnet模拟器测试,那么BACnet模拟器和代码不能运行在同一电脑上,他们会互相占用对应端口!
顺便测试代码之前,把你的BACnet调试设备关掉,不然它也会占用端口。

import BAC0
myIPAddr = '192.168.1.1/24'
bacnet = BAC0.lite(ip=myIPAddr, )
bacnet.whois()
print(bacnet.whois())

打印输出:

2023-03-27 16:02:35,756 - INFO    | Starting BAC0 version 22.9.21 (Lite)
2023-03-27 16:02:35,756 - INFO    | Use BAC0.log_level to adjust verbosity of the app.
2023-03-27 16:02:35,756 - INFO    | Ex. BAC0.log_level('silence') or BAC0.log_level('error')
2023-03-27 16:02:35,756 - INFO    | Starting TaskManager
2023-03-27 16:02:35,757 - INFO    | Using ip : 192.169.1.1
2023-03-27 16:02:35,934 - INFO    | Starting app...
2023-03-27 16:02:35,935 - INFO    | BAC0 started
2023-03-27 16:02:35,935 - INFO    | Registered as Simple BACnet/IP App
2023-03-27 16:02:35,966 - INFO    | Update Local COV Task started
[('20:0x000000000000', 0), ('20:0x010000000000', 1), ('192.168.1.2', 4194302)]

进程已结束,退出代码0

我这里用的是模拟器,我在模拟器中创建了两个device,显示出了两个模拟设备的网络地址和一个模拟器的地址。

BACnet设备读取

使用bacnet.read()方法。让我们先来读一读源码中的说明:

# 节选自C:\PycharmProjects\bacnet_test\venv\Lib\site-packages\BAC0\core\io\Read.py
    def read(
        self,
        args,
        arr_index=None,
        vendor_id=0,
        bacoid=None,
        timeout=10,
        show_property_name=False,
    ):
        """
        Build a ReadProperty request, wait for the answer and return the value

        :param args: String with <addr> <type> <inst> <prop> [ <indx> ]
        :returns: data read from device (str representing data like 10 or True)

        *Example*::

            import BAC0
            myIPAddr = '192.168.1.10/24'
            bacnet = BAC0.connect(ip = myIPAddr)
            bacnet.read('2:5 analogInput 1 presentValue')

        Requests the controller at (Network 2, address 5) for the presentValue of
        its analog input 1 (AI:1).
        """

bacnet.read()函数接收一个类型为str的参数,字符串中的参数以空格为分隔,包含 addr type inst prop 四个参数.
之前我的模拟器输出了以下的device:
[('20:0x000000000000', 0), ('20:0x010000000000', 1), ('192.168.1.2', 4194302)]
在模拟器这里实际有用的只有前两个。我们以(‘20:0x010000000000’, 1) → AnalogInput 2 → presentValue作为测试项,那么

addr:20:0x010000000000
type:analogValue
inst:2
prop:presentValue

这就是最后的答案了。将其变成read函数需要的字符串参数,就是:'20:0x010000000000 analogValue 2 presentValue'
最终读取代码:

import BAC0
myIPAddr = '192.168.1.1/24'
bacnet = BAC0.lite(ip=myIPAddr, )
# bacnet.whois()  # 可以注释掉,这个的扫描时间比较长
# print(bacnet.whois())
data = bacnet.read('20:0x010000000000 analogValue 2 presentValue')
print(data)

完事了,你学会基本读取操作了

BACnet写入操作

同上,看看bacnet.write()源码说明:

    def write(self, args, vendor_id=0, timeout=10):
        """Build a WriteProperty request, wait for an answer, and return status [True if ok, False if not].

        :param args: String with <addr> <type> <inst> <prop> <value> [ <indx> ] - [ <priority> ]
        :returns: return status [True if ok, False if not]

        *Example*::

            import BAC0
            bacnet = BAC0.lite()
            bacnet.write('2:5 analogValue 1 presentValue 100 - 8')

        Direct the controller at (Network 2, address 5) to write 100 to the presentValues of
        its analogValue 1 (AV:1) at priority 8
        """

和上面读取没啥大区别,多了一个‘-’和一个优先级。

bacnet.write('20:0x010000000000 analogValue 2 presentValue 12345.0 - 1')

就直接这么写入就可以了。

AND其他…

没错,测试环境很快就通过了,我就信心满满的实际测试了。然后就报错了

bacpypes.errors.InvalidTag: integer application tag required

…TAG标签错误???通过对代码的debug,找到了报错的触发代码:

def decode(self, tag):
    if (tag.tagClass != Tag.applicationTagClass) or (tag.tagNumber != Tag.realAppTag):
        raise InvalidTag("real application tag required")
    if len(tag.tagData) != 4:
        raise InvalidTag("invalid tag length")

就在(tag.tagNumber != Tag.realAppTag)这个判断上。debug告诉我,tag.tagNumber的值是3,Tag.realAppTag是源码中规定的值4。我到这里才去审查厂家给我提供的BACnet服务器的对接数据,结果发现他们的presentValue类型是Long,而不是Real。

这个时候,我坚定的认为自己在read和write函数里少填写了参数,或者用错函数了,导致数据类型不匹配(你想想,传的参数是个字符串啊,哪能规定数据类型),找到最后,除了在GitHub上找到了一个同样的问题但是没有回答,完全找不到任何的踪迹。于是这个时候我才死心塌地的去对着源码一步一步看。

BACpypes库中没有提供非标准数据类型接口!!!!!

我谢谢他啊…于是没办法,最后根据源码,直接修改了venv/lib/python3.6/site-packages/bacpypes/object.py源码中的数据类型:

@register_object_type
class AnalogValueObject(Object):
    objectType = 'analogValue'
    _object_supports_cov = True

    properties = \
        [ ReadableProperty('presentValue', Integer)  # 原数据类型为Real
        , ReadableProperty('statusFlags', StatusFlags)
        , ReadableProperty('eventState', EventState)
        , OptionalProperty('reliability', Reliability)
        , ReadableProperty('outOfService', Boolean)
        , ReadableProperty('units', EngineeringUnits)
        , OptionalProperty('minPresValue', Real)
        , OptionalProperty('maxPresValue', Real)
        , OptionalProperty('resolution', Real)
        , OptionalProperty('priorityArray', PriorityArray)
        , OptionalProperty('relinquishDefault', Real)
        , OptionalProperty('covIncrement', Real)
        , OptionalProperty('timeDelay', Unsigned)
        , OptionalProperty('notificationClass',  Unsigned)
        , OptionalProperty('highLimit', Real)
        , OptionalProperty('lowLimit', Real)
        , OptionalProperty('deadband', Real)
        , OptionalProperty('limitEnable', LimitEnable)
        , OptionalProperty('eventEnable', EventTransitionBits)
        , OptionalProperty('ackedTransitions', EventTransitionBits)
        , OptionalProperty('notifyType', NotifyType)
        , OptionalProperty('eventTimeStamps', ArrayOf(TimeStamp, 3))
        , OptionalProperty('eventMessageTexts', ArrayOf(CharacterString, 3))
        , OptionalProperty('eventMessageTextsConfig', ArrayOf(CharacterString, 3))
        , OptionalProperty('eventDetectionEnable', Boolean)
        , OptionalProperty('eventAlgorithmInhibitRef', ObjectPropertyReference)
        , OptionalProperty('eventAlgorithmInhibit', Boolean)
        , OptionalProperty('timeDelayNormal', Unsigned)
        , OptionalProperty('reliabilityEvaluationInhibit', Boolean)
        , OptionalProperty('minPresValue', Real)
        , OptionalProperty('maxPresValue', Real)
        , OptionalProperty('resolution', Real)
        , OptionalProperty('faultHighLimit', Real)
        , OptionalProperty('faultLowLimit', Real)
        , OptionalProperty('currentCommandPriority', OptionalUnsigned)
        , OptionalProperty('valueSource', ValueSource)
        , OptionalProperty('valueSourceArray', ArrayOf(ValueSource, 16))
        , OptionalProperty('lastCommandTime', TimeStamp)
        , OptionalProperty('commandTimeArray', ArrayOf(TimeStamp, 16))
        , OptionalProperty('auditablePriorityFilter', OptionalPriorityFilter)
        ]

就是我写了注释的那行,改完,跑起来了。

总结一下就是,我再也不想用python碰BACnet了,为什么都21.9.21版本了,BAC0库还是没有去尝试修改依赖库或是重写这部分的代码,传参竟然用的是字符串,以空格作为split的参数去分割参数…现在BACnet很多名称中都带有空格了,这库完全没有考虑到兼容性问题。

结束

国内基本都是各种复制粘贴的那三行代码,上面这么多很简单的问题,其实踩过一次坑就明白什么情况了,包括也没人对其中的参数做出一个说明,可惜没有人带我了解一遍这库。希望这篇文章能解决一部分人的问题吧。

  • 5
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
### 回答1: BACnet(Building Automation and Control Networks)协议是一种用于建筑自动化和控制网络的通信协议BACnet协议的移植是指将该协议应用于其他系统或设备上的过程。 首先,BACnet协议的移植需要有充分的了解和理解该协议的规范和功能。该协议包含了通信的数据格式、命令、网络拓扑结构等方面的规定。移植过程中需要根据目标系统的要求和限制,进行相应的调整和改编。 其次,BACnet协议的移植需要有适当的软硬件支持。硬件方面,需要确保目标系统具备足够的计算和存储能力来处理BACnet协议的通信和控制任务。软件方面,需要根据目标系统的操作系统和编程语言,进行相应的代码修改和适配。 在具体的移植过程中,需要对BACnet协议的不同功能模块进行适配和实现。例如,对于数据传输和通信协议层,需要根据目标系统的网络环境和传输媒介选择合适的通信方式和协议实现。对于应用层,需要根据目标系统的功能需求和硬件接口实现适当的控制逻辑和数据处理。 在移植后的应用中,需要进行充分的测试和验证,确保移植的BACnet协议在目标系统上的正常运行和稳定性。同时,还需要提供相应的技术支持和维护,确保移植后的系统能够正常使用和运行。 总之,BACnet协议的移植是一项复杂的工程任务,需要充分的规划和准备。通过适当的软硬件支持和合适的实现方法,可以将BACnet协议应用于不同的系统和设备中,提高建筑自动化和控制网络的通信效率和可靠性。 ### 回答2: BACnet(Building Automation and Control Network)是一种用于建筑自动化和控制系统的通信协议BACnet协议移植是指将BACnet协议从一种硬件平台或操作系统移植到另一种硬件平台或操作系统。 BACnet协议移植的目的是为了在不同的硬件平台或操作系统上实现相同的通信功能。这种移植需要进行一定的工作,包括了解目标平台的特性和限制,修改和调整协议的实现代码,以适应目标平台的要求。 首先,要进行BACnet协议移植,需要对目标平台进行仔细的分析和评估。这包括了解目标平台的操作系统类型、内存、处理器、网络接口等硬件参数。只有了解目标平台的特性和限制,才能进行有效的移植工作。 其次,对协议实现代码进行修改和调整。这意味着根据目标平台的要求,对原来的代码进行修改和适配。可能需要进行平台特定的代码优化、更改网络接口实现,以及处理不同的操作系统API调用等。这些修改和调整的目的是确保协议在目标平台上的正确运行和性能提升。 最后,进行测试和验证。移植完毕后,需要对移植后的BACnet协议进行充分的测试和验证。这包括功能测试、性能测试和兼容性测试等。测试的结果将验证移植的协议在目标平台上的正确性和稳定性。 总结而言,BACnet协议移植是一项需要精细分析、修改和测试的工作。它的目的是在不同的硬件平台或操作系统上实现相同的通信功能。移植过程中需要对目标平台进行评估,对协议实现代码进行修改和调整,并进行充分的测试和验证。只有经过有效的移植工作,才能确保BACnet协议在不同平台上的成功应用。 ### 回答3: BACnet协议是一种用于建筑自动化和控制系统之间进行通信的通信协议。它提供了一个标准化的方式,使不同设备和系统能够互相交流和协作。 移植BACnet协议是指将BACnet协议应用到不同的硬件平台或软件环境中。这个过程需要将BACnet协议的代码和功能移植到目标平台,以便与特定的硬件或软件环境进行通信。 移植BACnet协议需要先了解目标平台的特性和限制。例如,如果目标平台是一个嵌入式系统,则需要考虑硬件资源的限制和处理能力。如果目标平台是一个PC或服务器,那么就需要考虑软件环境和操作系统的要求。 一般来说,移植BACnet协议的过程包括以下几个步骤: 1. 硬件或软件环境的调研和评估:了解目标平台的特性和限制,确定是否能够支持BACnet协议的功能和要求。 2. 移植代码:根据目标平台的要求,修改BACnet协议的代码,确保它能够在目标平台上正常运行。 3. 测试和调试:在目标平台上进行测试,确保BACnet协议能够与其他设备和系统进行正常的通信和协作。 4. 性能优化:对移植后的BACnet协议进行性能优化,以提高其在目标平台上的运行效率和响应速度。 需要注意的是,移植BACnet协议可能会面临一些挑战,比如硬件和软件的兼容性、不同平台之间的差异等。因此,在移植BACnet协议时,需要经验丰富的开发人员和专业的工具来支持和指导。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值