请下载并解压缩zip压缩包,内有2个固件文件,一个是BACnet版本,另一个是Modbus版本
对于CCN Gateway 3.00之前的版本,请参见本页的底部
3.06 2025.04.10 下载
修复“IP路由”重启后丢失的问题。
3.05 2025.02.26 下载
修复bug当CCN总线上冲突时可能导致后台进程崩溃。
从BACRouter 6.09同步升级BACnet ms/tp驱动。
BACRouter从v6.00开始改用ARM芯片,新的固件文件格式为:xxxx_xxxx_6.00_arm_xxxx.tar.gz
v6.00之前的固件, 请访问: 老固件升级记录
ARC156: 当下一站点已被证实,如传递令牌失败时,重试一次。
ARC156: 当一帧的时间小于5位时,将其视为噪声。
ARCnet 标准和 COM20019i 的数据手册规定时钟精度应为 0.01% 或更高,因此我们之前的实现采用保守的时钟误差校正逻辑来容忍 0.05% 的时钟误差。在测试 ZN551 和 AAR 模块后,我们发现在解码数据包时,它们实际上可以容忍高达 4.5% 的时钟误差(当然重配置过程允许时钟误差仍须小于0.2%),猜测它们将接收器时钟与每个字节之前的脉冲同步。在此版本中,我们实施了一种激进的逻辑来实现类似或更好的性能。
ARC156: 当2段总线均有活跃节点,在合并时,运行信息中“总线上节点”可能检测到错误的“活跃节点“。其解决方案是两次检测以避免假信号。
ARC156: 当BACRouter是总线上唯一节点时,BACRouter将不停发送ITT包(邀请发言包)以查找其它节点,数据包将无法被发出,所有发往此端口的NPDU将在队列中超时,这将在日志内留下烦人的记录。在此版本中,如果 BACRouter 发现自己是总线上的唯一节点,它会以合理的速率删除队列中的数据包,并将其记录在包捕捉缓冲区中以假装将数据包发送出去。因此,在捕捉到的包记录时,可以看到BACRouter 发送数据包但没有得到响应,此行为模式与其他数据链路类型一致。
Modbus RTU:某些设备不遵守字节之间的 T15空闲时间规定,我们删除了该验证以提高兼容性。
应用程序设置:删除了“客户端模式”以符合 BTL 要求。
Modbus/自由协议:调试映射或脚本时,经常需要从 BACnet 端执行写入,此时用户必须启动 BACnet 客户端,例如 Yabe。而计算机上通常已经有一个 BACnet 服务器在运行,此时用户必须启动 虚拟机 来运行 Yabe。这浪费了很多时间,所以我们在运行信息页面上集成了写入功能。
MSTP bug可能导致特定的NPDU包内容被丢弃。
Modbus WebUI:
当对映射于单个寄存器的Analog对象进行写测试时,其字节序应该引用“单寄存器字节顺序”设定,而不是“大整数字节顺序”设定。
当对映射于2个寄存器的Multi-state对象进行写测试时,其字节序应该引用“大整数字节顺序”设定,而不是“单寄存器字节顺序”设定。
自由协议设备的运行信息页面中,Binary对象的真实值应参考极性设定。
BACRouter高度依赖高精度定时器,当有电磁干扰的场合,板上晶振可能受到干扰,造成定时器偏移,进而导致程序错误。本版本加入了纠正程序。
自由协议的Binary输出对象的bug,由v6.01的“信号极性”特性引入。
Modbus主站模块:当写入到只读数据地址时,有的设备无响应,而不是报告错误码。在这种情况下,BACRouter将会不停尝试造成设备掉线。在本版本中,BACRouter只会重试3次。
重构底层arc156及mstp驱动以简化代码。
ARC156: 当节点对“Free Buffer Enquiry”持续回应NAK时,丢弃发往该节点的包。
WebUI:当IP或子网掩码被修改,但DHCP服务仍保持启用时,提醒用户。
ms/tp错误修正: 当往ms/tp端口发送NPDU接近1497字节时可能触发bug.
ARC156 驱动已可用。
修正bug: Modbus与自由协议模块中总线名称冲突检测。
自由协议:Binary对象增加“信号极性”属性。MultiState对象的状态数量增加最多256限制。
因版本4.26于2023-03-07更新
从固件版本4.x起,BACRouter内置了Modbus网关功能。这篇文章尝试解释其底层运作机制。
BACRouter周期性地读取点数据并缓存。“刷新间隔”定义读取数据的间隔。
为了提高性能,BACRouter将尽可能在一次事务读取更多数据。在Modbus标准中,最多2000个位(指线圈或离散输入)或125个寄存器(指输入或保持寄存器)可以在一次请求中读回,但是特定的Modbus从站可能不支持读取大量的数据。在BACRouter的WebUI的”从站设定“页面的Modbus参数对话框中,”批量读比特位数“与”批量读寄存器数“两个参数定义了从站的对批量读数据的支持。
BACRouter不会分段读取单点的数据,即使”批量读寄存器数“小于该点占用的寄存器数。
通常来说,我们关心的数据的Modbus地址并不连续,比如我们只关心地址为1及2000的线圈,虽然我们可以一次批量读回地址1~2000的数据,然后抛弃地址2~1999的数据,先读取地址1的数据,再发请求读取地址2000的数据的做法可能更有效率。”比特位跳过“与”寄存器跳过“参数定义了在两个我们关心的数据地址中间,有多少个无用的数据允许被抛弃。假设在上述例子中,”比特位跳过“设为1998,BACRouter将尝试一次性读回地址1~2000,抛弃中间1998个数据。倘若”比特位跳过“为1997,则BACRouter将分两次读取地址1与地址2000。因为中间跳过的1998个数据大于设定值1997。
在从站的配置页面,“编组读取”显示读操作如何编组,并可进行测试。请特别关注服务端报告的非法数据地址!
BACRouter不会合并写请求。当接收到写指令时将马上发出Modbus写请求。
对于写单个数据,有两个Modbus功能号可供使用。例如,功能5为写单个线圈,功能号15为写多个线圈但写数量可以为1。有的从站只支持其中一个功能号。”写单个线圈”参数指定采用哪个功能号。
同样地,”写单个寄存器“参数指定用于写单个寄存器的功能号。假设有任一个BACnet输出对象映射到多个保持寄存器,这意味着从站支持功能号16写多个寄存器,”写单个寄存器“将被关闭,功能号16将被采用。
如果任何读写请求失败,BACRouter将会重试,如果连续失败次数达到3次,BACRouter将认为该从站离线。在离线状态下,BACRouter将间隔”离线刷新间隔“时间重试该请求。
在离线事件中,BACnet侧的反应将在后面描述。
BACRouter支持在同一条RS485总线上,不同波特率、奇偶较验、RTU/ASCII模式的从站共存。在发出请求前,BACRouter将根据从站的设定切换串口的参数。
如总线上的两个从站拥有相同的从站地址但是波特率或RTU/ASCII模式不同,BACRouter可以无冲突地通信。但是如果仅依赖不同的奇偶较验来防范冲突并不可靠,因为有的从站并不检查奇偶错误。
如VBUS网络端口未使能,BACRouter将工作于单设备模式,每个Modbus从站将被映射到”应用层设定“中定义的BACnet设备的不同对象实例空间。每个从站将占据1000实例空间。
对象的实际名称为“主站名称|从站名称|对象名称”,名隔分隔符”|”可由用户在数十个字符中选择。
依据BACnet标准的推荐做法,BACRouter将为每一个从站创建一个结构化视图对象。
本模式下当从站切换到离线状态,所有点的可靠性将被设为”COMMUNICATION_FAILURE”,当从站重新上线时,随着点数据的更新,各点的可靠性将被重新置回到”NO_FAULT_DETECTED”。
当VBUS网络端口被使能,每个从站将被映射为VBUS网络上的虚拟BACnet设备。虚拟设备的MAC地址将从1开始。
在本模式下,所有从站必须有一个唯一的“设备实例号”。从站的BACnet设备名称为”主站名称|从站名称”,其中”|”为可选择的名称分隔符。
在本模式下如果从站切换到离线,依据BACnet标准的推荐做法,对应的虚拟BACnet设备将停止收发包以模拟离线状态。当从站重新上线时后,当更新完所有点数据后,虚拟BACnet设备将退出离线状态。
针对每一个从站,BACRouter在缺省BACnet设备内创建3个对象。一个名为”online”的二进制输入对象,代表从站的上线状态。一个名为”update_delay”的模拟输入对象,代表最近平均数据更新延迟(对比”update_delay”), 最后一个是名为”fail_rate”的模拟输入对象,代表相对长时间内的Modbus侧通讯失败率。
这些对象名均有“主站名称|从站名称|comm|”的前缀,其中“|”为可选择的名称分隔符。
对象的实例号单独进行定义。当对象编辑窗口提交时,如对象被启用,将分配其声明的实例号,如该实例号被占用,将自动选择一个可用的实例号。
值对象的特性与属入对象类似,如值对象映射到可写Modbus地址时(0X或4X),此时值对象的当前值可写,写入的值被转发到Modbus侧(有以下2个例外)。
例外1:如果AV对象映射到保持寄存器,其数据类型为自定义,且其绑定的脚本不支持输出,此AV对象为只读。
例外2:如果AV对象映射到多个保持寄存器,但“写单个寄存器“参数生效,此AV对象为只读。
输出对象对应的点值仍然会被定期读回,且设置成对象的Relinguish_Default属性。如果读回的值与上次写入的值不匹配,BACRouter将把该对象的可靠性置为 “UNRELIABLE_OTHER”,BACRouter将尝试每隔一段时间重新写入。如“容忍不匹配”选项被启用,如果读回的值与上次写入的值不匹配,该对象的可靠性将不变,BACRouter将尝试以”离线刷新间隔“重新写入。(于v4.34版更新)
因为以上的验证重写机制,BACRouter禁止2个输出对象映射的可写Modbus地址发生重叠。
NaN是IEEE-754标准规定的特殊值,其意思为非有效数,BACRouter不接受NaN(正负无穷大仍然被视为有效值).
当一个Modbus点被定义为单精度浮点或双精度浮点,且BACRouter读到NaN值时,该点对应对象的可靠性将被设为”UNRELIABLE_OTHER”。
如一个模拟量对象为自定义,当从Modbus换算到BACnet时,脚本报告不支持的数据,此对象的可靠性将被置为”UNRELIABLE_OTHER”。
对于一个AO对象,如从BACnet换算到Modbus时,脚本报告不支持的数据,此对象的可靠性将被置为“PROCESS_RROR”。
对于一个AV对象,如从BACnet换算到Modbus时,脚本报告不支持的数据,此输出尝试被放弃,对象的可靠性保持不变。
因为固件版本3.x,于2020.3.25添加:
因为不能保证所有设备同时上电,目前没有任何一种自动地址分配方案能够完全避免MAC地址冲突,所以我们从固件3.x版中移除了自动地址分配特性。为了帮助确定当前总线的“最大扫描地址”与空闲的MAC地址,用户可以启用“侦听”模式,然后在“运行信息”页面中,找到“当前最大扫描地址”,再通过“最近活动的其它站点”,找到未使用的MAC地址。
MSTP总线上的每一个设备必须有一个独特的MAC地址。对于主站设备,合法的地址范围为0~127,而对于从站设备的范围为128~254。
通常MAC地址采有以下几种方式确定:拨码开关,板上跳线,HMI界面,固件传输等。有的设备支持通过BACnet写属性服务修改MAC地址,但是这之前,设备必须要有合法的MAC地址以接入BACnet网络。
如果独特的MAC地址能够自动获得,就象我们将笔记本接入家庭或办公网络一样通过DHCP服务自动获取,我们就可以节省大量的调试时间。
这里讨论了好几种方案. 目前看来委员会更倾向于 “零配置方案” (附录135-2012bb)
“零配置方案” 只适用于最大扫描地址为127的情况,且自动地址分配范围为64~127。如不满足,可能带来混乱。
为了避免以上限制, BACRouter实现了私有的自动地址分配方案,并且与“零配置方案”保持兼容。它有以下吸引人的特性:
所以用户可以更加自由地规划地址,例如,将0~30留给固定地址的设备,将最大扫描地址设为40。这样自动获取地址的设备接入总线后,将从高到低依次使用40~31的地址。
不管是“零配置方案”还是BACRouter的方案,当已经自动获得地址的设备从总线中断开后,再重新接入总线,非常有可能遇到地址冲突,因为在断开期间,其地址可能被其它新接入的自动获取地址的设备占用。(BACRouter可能更容易遇到问题,因为它的地址分配不是随机的), 所以
一定要在接入总线的情况下,给自动获取地址的设备上电。
最早的BACnet MS/TP设计只支持NPDU长度到501字节,大幅地小于IP与Ethernet链路层的1497字节长度。这限制了MS/TP的传输效率,增加了应用层的复杂度,特别当两个IP或Ethernet子网通过MS/TP子网连接在一起的时候。
扩展帧设计用于解决这个问题。标准附录此处可见. 简要地说, 此附录增加了两种帧类型:
帧类型32由帧类型5(BACnet帧须应答)扩展而来,特殊之处在于它以COBS规则编码及NPDU长度为502到1497字节。
同样地,帧类型33由帧类型6 (BACnet帧无须应答)扩展而来。
扩展帧支持从修订版16开始正式引入标准。但是现场及市场上仍然有大量的旧设备不支持。支持扩展帧设备与旧设备之间的互操作性值得探讨。
BACRouter在非常早的版本就支持了扩展帧。从固件版本3.18开始,我们在BACRouter的MS/TP配置中引入了”扩展帧支持“选项,如果在总线上有不支持扩展帧的旧路由,此选项必须关闭以避免互操作性问题。
值得注意的是,即使”扩展帧支持“选项被关闭,不象旧路由,BACRouter仍然与支持扩展帧的设备有良好的互操作性。
(截图于2021-08-05更新,因为扩展帧是标准修订版16的强制要求,所以从固件4.13起,我们把这个功能选项移入到扩展配置模式中)
如前面的文章 “BACnet MSTP 帧失步” 所指, BACnet MSTP 有一个帧失步的设计缺陷,但是是否可以利用这个缺陷,在完全遵守协议的前提下,对MSTP总线进行破坏呢?
设计这个攻击,我们先做以下几个假设:
设备1的工作流程如下:
A帧是一个合法的私有数据帧,十六进制数据如下:
55 ff 80 ff 01 00 1d a3 02 2b 72 fe 55 ff 03 08 01 00 11 a0 ff 55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf
B帧也是一个合法的私有数据帧,十六进制数据如下:
55 ff 80 ff 01 00 1d a3 02 2b fe dc 55 ff 03 0a 01 00 11 b1 ff 55 ff 21 01 0a 00 09 fd 8a 51 55 ff 00 01 0a 00 00 8c
如果没有帧失步,一切都将正常运行。但是可能几小时,也可能几天后,设备8对设备1发出的A帧失步了,错过了A帧的帧头(设备10如对B帧失步,也是同样的效果),则设备8继续扫描A帧的数据部分,发现另一个有效帧:
55 ff 03 08 01 00 11 a0 ff 55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf
这是发给设备8的Test-Request帧,设备8等待Tturnaround后发送Test-Response帧进行应答:
55 ff 04 01 08 00 11 ae ff 55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf
但是此时,设备1正在传出令牌:
55 ff 00 02 01 00 00 73
令牌帧与Test_Response的前8个字节冲突了,对于设备10来说,收到了几个错误字节后,继续扫描,在Test_Response的数据部分又发现了一个帧:
55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf
对设备10来说,这不是发给它的帧,所以他进入SKIP-DATA状态,抛弃数据,等这个帧结束,但是直到设备8发完数据,设备10还差一个字节来结束帧,他将继续等待。
对设备1来说,它发出令牌帧后,收到如下数据:
55 ff 21 01 08 00 09 ce d4 f3 55 ff 00 01 08 00 00 bf
这是一个扩展数据帧,因为它支持扩展帧,所以他按 Addendum 135-2012an规定的流程校验帧头,发现数据长度过短,中断前帧后又开始扫描,发现新帧:
55 ff 00 01 08 00 00 bf
这是一个发给设备1的令牌帧,设备1又得到令牌,经过Tturnaround后,重新发送B帧:
55 ff 80 ff 01 00 1d a3 02 2b fe dc 55 ff 03 0a 01 00 11 b1 ff 55 ff 21 01 0a 00 09 fd 8a 51 55 ff 00 01 0a 00 00 8c
在前面提到,设备10还差1个字节来结束前面一帧,因Tframe_abort>Ttrurnaround,所以解析没有中断,B帧的第一个55字节被设备10抛弃,然后开始扫描新帧,发现了:
55 ff 03 0a 01 00 11 b1 ff 55 ff 21 01 0a 00 09 fd 8a 51 55 ff 00 01 0a 00 00 8c
这是一个发给设备10的Test-Request帧,事情又开始重复。
从上面可以看出,每个设备都严格地遵守标准,但是一旦帧失步发生,整条总线就永远地瘫痪了。
更多信息见:MSTP帧失步解决方案
于2021.7.13更新
我们曾在下面的文章中讨论过BACnet MSTP协议中有帧失步的弱点:
对于BACRouter 来说,怎么来防范这个漏洞呢?让我们从标准找线索:
9.5.2 变量
SilenceTimer(翻译为静默计时器): 名义精度5ms的计时器,每当总线上有活动或每发送一个字节后清零
9.5.3 参数
Tframe_gap(翻译为字节间隔): 节点在发送一个帧时,在两个字节之间允许的最长的空闲时间: 20位时间。以我们的经验市面上几乎所有MSTP设备的字节间隔为0
Tturnaround: 节点接收到最后一个字节与开始发送之间的最小时间间隔: 40位时间
Tpostdrive: 节点发送完最后一个字节的停止位,到关闭485的驱动器之间的最长时间: 15位时间
9.5.5 发送帧流程
当SilenceTimer小于Tturnaround时, 等待 (Tturnaround – SilenceTimer)时间
9.2.3 时序
驱动器关闭: 节点应该在一个帧的最后字节的停止位起的Tpostdrive时间内关闭驱动器。标准允许但不鼓励,在帧的最后一个字节后再发送一个填充字节,如果填充字节被使用,它必须是0xFF,填充字节不被认为是帧的一部分,且应该包括在Tpostdrive时间内。
(此处并不明确Tturnaround是否包括填充字节的时间,但是在135.1的测试标准的12.1.3.4章节就描述得很清晰:Tturnaround从最后一个字节的停止位后开始计时,如果节点采用了填充字节,则应该由填充字节前一个字节的停止位后开始)
如此可见,在一个正常帧内,2个字节之间的最大空闲时间为20位时间,如果计上前一个字节拖尾的“1”位,则最长的总线空闲时间为29位时间(假设前一个字节为0xFF)。
考虑到填充字节,两帧之间的最小总线空闲时间为:Tturnaround – Tpostdrive + 9 (填充字节拖尾的”1″的位数)= 34位时间
所以BACRouter采用一个改进的接收有限状态机:
在115200波特率下,一个数据位的时间仅8.7微秒,为了精确地测量空闲时间,BACRouter采用了5微秒精度的定时器,它有效地防止帧失步出现,并且 在115.2kbps下达到98.8%的带宽利用率 因为BACRouter发包时精确地遵守40位的Tturnaround,没有浪费多余的等待时间。
从固件版本2.0开始,BACRouter引入一个新的特性:根据令牌占用时间的一次最大发包数。
在BACnet MSTP标准中,一个主站得到令牌后,可以发送“一次最大发包数”的包后,再传递出令牌。“一次最大发包数”的默认值是1。路由器作为流量汇聚点,提高这个值可以改进网络交换带宽,但是会增加令牌占用时间。大多数路由的建议值为5到20。
MSTP作为常见的现场总线,通常由控制器、传感器、执行器互联,这些设备构成直接的控制回路,数据交换延迟通常要得到保证。我们建议设备得到令牌的时间间隔要小于1秒 。
路由器发送的NPDU的长度通常在10~50字节之间。但是最大可达到501字节(或扩展帧的1497字节)。越大的帧需要越长的时间来收发。
对于需要回应的NPDU来说,路由器需要等待目标设备应答。通常目标设备需要更长的时间来处理长帧,即路由器需要等待更长的时间。
所以同样的“一次最大发包数”,每次路由持用令牌的时间变化很大,对MSTP总线的延迟保证非常不利。
为避免这个问题,我们引进“根据令牌占用时间”特性来限制路由器持有令牌的时间。这个特性启用后。路由器不计算发包数,而是计量持有令牌的时间,当时间达到:
每字节发送时长 * 32 * “一次最大发包数”
就不再发送新帧,并传出令牌。例如“一次最大发包数”为10, 波特率为76.8kbps,每字节发送时长为0.13毫秒,则最大令牌持有时间为:
0.13 * 32 * 10 = 41.6 毫秒.
这个特性可以通过WebUI方便地启用与关闭。
BACnet有两种类型的服务,分为无确认与有确认。有确认服务的发送者(客户端)会等待应答直至超时。
通常情况下,无确认服务的包延时不会带来副作用。但对于有确认服务的请求包与应答包,过长的包延时将导致应答包因为超时而被抛弃,浪费了通讯带宽。
更有甚者,过迟到达的应答包可能导致应用层的逻辑错误!原因如下:
有确认服务的请求包头带有一个InvokeID,值范围0~255。应答包有同样的InvokeID。客户端通过这个InvokeID匹配请求与应答。在一个繁忙的客户端,InvokeID会快速耗尽, 此时只能回收重用已完成的服务的InvokeID。
如果一个有确认服务的应答包被过分延迟,客户端可能因为超时而结束服务,其InvokeID被回收,并被重新分配使用。此时被延迟的应答包被收到后,其InvokeID将被匹配到错误的服务。例如:
对高速的链路层如以太网或IP, 包延时通常是可忽略的,但是对于MSTP,有很多原因将造成过长的包延时:
为了避免InvokeID冲突及提高网络性能,版本2.0及以上的BACRouter实现了10秒钟 (参考BACnet的缺省APDU_Timeout,从v4.17起此延时修改为6秒钟) 的包延时保证,在收到后不能在此延时内完全转发的包将被抛弃。
虽然此策略可能造成服务无应答,但是比起错误应答,无应答是可以通过应用层的重试机制处理。
更新于2020-11-20(文末添加江森DDC内容)
MSTP的波特率设置一直是个现场工程师头痛的问题,如果设备的波特率设置有误,就无法加入MSTP网络中。
大部分设备采用固定的波特率,如需修改波特率,工程师必须接触到设备并调整DIP开关。有的设备支持通过BACnet服务修改波特率,但是在这之前,设备必须要能接入到BACnet网络中。
有的厂商实现了自动波特率配置,但是也带来了更多的问题,目前市场上的自动波特率有两种类型:
不管是哪种类型,在总线运行时改变波特率都是非常困难的。仅仅改变所有固定波特率设备的设置是无法影响总线波特率的,因为其它自动波特率设备仍然以旧的波特率运中。唯一可靠的方法是在改变所有固定波特率设备的设置后,关掉所有自动波特率设备,再启动所有自动波特率设备(不能一个接一个的重新启动自动波特率设备!因为仍在运行中的自动波特率设备将维持总线波特率)
BACRouter的新版本固件(版本>=2.0)引入了新的波特率管理机制(专利申请中)。BACRouter有三种波特率模式:固定/自动/强制。
当总线中有一个强制模式设备时,总线的波特率将会强制运行于预设值,而其它自动模式设备将自动同步波特率。固定模式但波特率设置值不同于预设值的设备将无法出现在总线中(通过检查BACRouter的运行信息中的“最近活动设备”项将很快地把这些设备找出)。对于上述的启动时探测设备,如果其探测到的波特率不同于预设值,也无法出现在总线中(同样可以通过检查BACRouter的运行信息中的“最近活动设备”项找出),但只需将其一个接一个的重新启动即可(不需全部断电,再全部上电)。
多个强制模式的设备可以共存在总线中,但这些设备中的波特率预设值必须相同。
江森自控的FEC与IOM模块即采用动态波特率,经测试,当波特率不匹配时间到150秒左右,模块开始重新检测波特率。
在BACRouter与FEC2611及IOM3731组成的测试总线中,双方完美协作,总线波特率可由BACRouter控制在9.6k~76.8k之间动态切换,FEC2611与IOM3731会延迟2.5分钟后刷新波特率。