为BACRouter增加新IP

我们经常在现场面对IP无法修改的第三方设备,而且其IP与BACRouter或其它BIP设备不在同一个子段内。
已有的解决方案是增加一个IP路由器,这样将导致成本与复杂度增加。
通常我们只需要BACRouter与这种第三方设备采用Modbus TCP或BACnet IP协议进行通讯,因此最简单的办法是为BACRouter增加一个专用于与此设备通讯的IP

例如,当前IP子网是192.168.100.0/24,  BACRouter的IP是192.168.100.1, 第三方设备的IP是172.16.1.20, 子网掩码为255.255.255.0

我们将为BACRouter添加一个新IP:172.16.1.1
初始的/etc/rc.local:

root@OpenWrt:~# cat /etc/rc.local
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.

cd /root
./webui&
exit 0

修改/etc/rc.local: (也可以用vi编辑)

root@OpenWrt:~# cat>/etc/rc.local
# Put your custom commands here that should be executed once
# the system init finished. By default this file does nothing.

ifconfig br-lan:1 172.16.1.1 netmask 255.255.255.0
cd /root
./webui&
exit 0
CTRL+D

这里的ifconfig指令将添加br-lan:1 新接口,其IP为172.16.1.1。如果我们需要以BIP协议连接第三方设备,我们还需要将新接口添加到BACRouter的配置文件中:

初始的/root/resource.conf:

~#cat /root/resource.conf
{ “eth0”: { “type”: “ETH”, “ifname”: “br-lan” }, “RS485-1”: { “type”: “USB”, “ifname”: “0:0” }, “RS485-2”: { “type”: “USB”, “ifname”: “0:1” } }

修改/root/resource.conf: (也可以用vi编辑)

~#cat >/root/resource.conf
{ “eth0”: { “type”: “ETH”, “ifname”: “br-lan” }, “eth1”:{“type”:”ETH”, “ifname”:”br-lan:1″}, “RS485-1”: { “type”: “USB”, “ifname”: “0:0” }, “RS485-2”: { “type”: “USB”, “ifname”: “0:1” } }
CTRL+D

现在断电重启BACRouter!

Modbus设备配置 & Lua 脚本 & 配置转换

Modbus 设备配置

Siemens_RDF302.json西门子 RD302 温控器
Schneider_TC500.json施耐德 TC500 温控器
JCI_T8600.json江森 T8600 温控器
JCI_T7000.json江森 T7000 系列温控器
ABB_ACS510.jsonABB ACS510 变频器

Lua 脚本

bcd.luaBCD码。每个字节存储0~9, 例如: 0x05090702 = 5972
字节序由“大整数字节顺序”定义。
compat_bcd.lua紧凑式BCD码。每半字节存储0-9, 例如: 0x78695231 = 78695231
字节序由“大整数字节顺序”定义。
m10k.lua每个寄存器存储 -9999 ~ 9999, 例如: 0x1f740a69 = 0x1f74*10000 + 0x0a69 = 80522665
字节序由“大整数字节顺序”定义。
low10th.lua只有一个寄存器, 低字节单位为1/10, 所以数值范围为 0.0 to 255.9 例如: 0x0503 = 5.3
字节序由“”单寄存器字节顺序“定义。
此表达方式在约克TMS2100 风机盘管温控器上发现。

配置转换

转换BASgatewayLX的CSV格式配置文件

转换X2BACnet的XML格式工程文件

转换X2BACnet的XML格式工程文件

X2BACnet(以下简单X2B)的工程文件采用XML格式,其配置软件自带了一部分工程模板,用户可以自行免费下载。本转换工具将其工程文件转为BACRouter的配置文件

在我们网站上直接转换

下载转换工具解压缩后用浏览器打开index.html

几点注意事项:

X2B的配置似乎不做错误较验,比如从站ID可以输入0或65537。转换工具根据我们的规则做了所有校验。

因为底层机制的不同,X2B的”请求帧间隔”与”写值刷新间隔”无法简单映射到BACRouter,所以BACRouter的从站的”Modbus参数”中的时序参数采用默认值,但”回应超时”参数采用X2B通道的“超时时间”。

X2B可以将部分点配置为“单点写“(功能码5或6),另一部分点采用多点写(功能码15或16)。转换中如果发现任一点为多点写,则其它点也全部采用多点写。如果发现任一AO映射到多个保持寄存器,则采用功能码16(这个逻辑与BACRouter相同)

X2B的寄存器地址可以用“.”引用内置的数据处理,如1.sum, 1.hibyte,1.lobyte,转换中将提示采用脚本处理。

X2B几乎允许所有的数据类型,BACnet类型,取位的组合,但大部分在工程上没有意义,工程模板中也找不到用例,转换中将放弃或跳过处理。

BACRouter与VPN

基于OpenWRT平台,BACRouter可以支持众多VPN协议,包括OpenVPN, wireguard, IPSec,等等。为了节省板上存储空间以存入应用程序,在默认情况下,VPN的支持并未启用。

我们的 OpenWRT 代码托管于 https://github.com/hvacrcontrol/openwrt, 代码分支 “bacrouter_new” 就是目前的生产代码。

代码仓库内的 vpn.config 就是 VPN 的配置文件,其中已使能 OpenVPN 与 wireguard

以上述配置编译的 OpenWRT 固件文件在这里下载: vpn_bacrouter_19.07_8fe8c902.tar.gz

关于如何使用上述固件文件升级,请参考 升级BACRouter底层固件

有时,我们需要在VPN侧运行BIP,从v4.23起的BACRouter固件 将支持本功能。下面简要说明如何配置它:

更新 Openwrt VPN 版固件,及 BACRouter 固件>= v4.23

先配置好 VPN, 以下以wiregurad 为例:

~#uci show network.vpn
network.vpn.proto=’wireguard’
network.vpn.private_key=’####################################’
network.vpn.addresses=’192.168.231.3/24′

~#uci show network.wg0
network.wg0=wireguard_vpn
network.wg0.public_key=’#####################################’
network.wg0.endpoint_host=’###.###.###.###’
network.wg0.endpoint_port=’####’
network.wg0.route_allowed_ips=’1′
network.wg0.persistent_keepalive=’25’
network.wg0.allowed_ips=’192.168.231.0/24′

有关 wireguard 的配置请参考: https://openwrt.org/docs/guide-user/services/vpn/wireguard/start

~#ifconfig vpn
vpn Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00
inet addr:192.168.231.3 P-t-P:192.168.231.3 Mask:255.255.255.0
UP POINTOPOINT RUNNING NOARP MTU:1420 Metric:1
RX packets:557 errors:0 dropped:0 overruns:0 frame:0
TX packets:3593 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:50348 (49.1 KiB) TX bytes:176736 (172.5 KiB)

将 vpn 接口加入到 BACRouter:
原始的/root/resource.conf:

~#cat /root/resource.conf
{ “eth0”: { “type”: “ETH”, “ifname”: “br-lan” }, “RS485-1”: { “type”: “USB”, “ifname”: “0:0” }, “RS485-2”: { “type”: “USB”, “ifname”: “0:1” } }

修改/root/resource.conf(您也可以用vi编辑)

~#cat >/root/resource.conf
{ “vpn0”: { “type”: “PPP”, “ifname”: “vpn”}, “eth0”: { “type”: “ETH”, “ifname”: “br-lan” }, “RS485-1”: { “type”: “USB”, “ifname”: “0:0” }, “RS485-2”: { “type”: “USB”, “ifname”: “0:1” } }
CTRL+D

因为 wireguard 工作于第三层 (tun 模式), 所以 vpn0 的类型定义为 “PPP”。对于工作于 tap 模式的VPN,必须使用 “ETH” 类型。

重启 WebUI, “系统设定”页面将显示如下:

创建另一个BIP端口,指定硬件为 “vpn0″。因为这是一个”PPP”接口,所以只有“外部设备”与“广播管理设备” 2种模式可用。

注意:当WebUI与BACRouter应用程序启动时,将读取所有的接口IP信息,所以所有接口必须采用静态配置。对于 wireguard 这不是问题,因为其 ip 是静态配置的。

对于 OpenVPN 的 tun 模式,请参考:给OpenVPN客户端分配静态IP地址 。 在OpenVPN客户端启动时,其需要时间与服务器建立连接并得到IP。下面示例启动脚本事先静态配置 tun0 接口的IP地址,以避免webui启动失败:

~#cat /etc/rc.local
ip tuntap add dev tun0 mode tun
ip link set tun0 up
ip addr add 192.168.231.3/24 dev tun0
/etc/init.d/openvpn start
cd /root
./webui&
exit 0

当 OpenVPN 的tap模式可用时,采用以太网网桥方式是最简单的,如此任何应用层设定都不需要调整。

关于BMS与VPN的精彩博客

升级BACRouter底层固件

BACRouter的底层平台是OpenWRT。自2016年发布以来,OpenWRT的版本未曾更新。安全审计机构时有报告内置的dropbear服务器有漏洞,虽然经我们评估,这些漏洞在BACRouter的应用场景下无法被利用。

不管怎样,我们决定将OpenWRT的版本升级到v19.07.10,这一版本发布于2022年4月20日。对于想升级的老客户,将照以下步骤执行:

1.  下载OpenWRT更新包

2.  在WebUI的“升级固件”功能中选用上述更新包。更新将持续二至三分钟时间。请注意,所有的设定,包括IP都将丢失。

3.  用浏览器打开”http://192.168.100.1″,页面将显示:

4. 选择任意版本的BACRouter固件,点击“upgrade”,页面将显示:

5. 等待1分钟之后再次刷新页面,WebUI就又回来了,请从备份中恢复原有的配置。

转换BASgatewayLX的CSV格式配置文件

BASgatewayLX(以下简单LX)的从站配置采用CSV格式,有较详细的文档说明,并且有一个可下载的配置列表。 我们提供了从其CSV配置文件转换到BACRouter的从站json配置文件的迁移工具。

在我们网站上直接转换

下载转换工具解压缩后用浏览器打开index.html

几点注意事项:

虽然LX的CSV文档说明中,对象名是唯一的,但配套的Profile Checker并不检查重复,且我们在测试中发现不少的重复对象名。遇到这种情况,我们将把冲突对象置为停用。

有的CSV配置文件的字符集不是常用的UTF8,可能是ISO-8859-1。我们尽量在转换时进行探测,最后回退到ISO-8859-1

LX支持的最长对象名与描述是64个字符,采用的是ISO-8859-1编码。BACRouter也支持最长64个字符,但采用utf8编码,存在一种可能即满足LX的64个ISO-8859-1个字符,又超过BACRouter的64个utf8编码长度,虽然我们测试中没有发生这种情况。如果转换中发现对象名或描述长度超出限制,我们将进行尾部裁断,并进行提示。

BACRouter不允许输出类型的对象所映射的Modbus地址重叠,但LX似乎允许。遇到这种情况,我们将把冲突对象置为停用。

LX允许对每一个32位数据单独设置字顺序,BACRouter必须在从站参数中统一设置。如果在转换中发现字顺序定义互相矛盾,我们将采用在先的定义,并提示冲突。

BACRouter每个从站的对象实例空间只有0~999,LX似乎没有限制,如果遇到定义的实例号超出999,我们将采用自动分配实例号。

离线配置与导入/导出

从固件4.x起,BACRouter引入了离线配置功能。现在用户可以脱离设备实物进行配置,将配置结果导出为文件。在调试现场,只需导入前面导出的配置文件,再“保存并重启”即可生效。

从固件4.04起,Modbus网关的从站点表可以用CSV格式导入导出,CSV文件可以在Excel/WPS/Libreoffice中进行编辑,适合批量建立对象与Modbus地址的映射,再导入到WebUI进行精确化编辑。

在我们网站上离线配置  (不再支持IE浏览器,但Edge仍然可以)

下载并解压到磁盘,用浏览器打开index.html文件 (因为IE/Edge浏览器不支持本地网页的Local Storage功能,所以用户文本库及常用工程单位信息在浏览器关闭后将丢失)

配置文件分为以下三种:

BACRouter 配置文件

此文件保存了BACRouter内的所有设定. 如果生成配置文件的固件版本与导入文件的固件版本一致,WebUI不应抱怨文件问题。

Modbus 主站配置文件

此文件保存了该主站范围内的配置。当导入时,基于当前映射模式的冲空检测机制将会检查此主站下每一个启用的从站。如果发现冲突,该从站将被停用。

如果该主站是RTU/ASCII类型且发现针对RS485端口的争用,此主站也将被停用。

如出现此类问题,WebUI将提示用户检查配置。

Modbus 从站配置文件

此文件保存了该从站范围内的配轩。当导入时,如基于当前映射模式的冲突检测机制发现问题,该从站将被停用。

WebUI允许将TCP从站的配置文件导入到RTU/ASCII主站下,反之亦然。但是如果从站的Modbus参数不匹配,该从站将被停用。

如出现此类问题,WebUI将提示用户检查配置。

BACRouter内置的Modbus 网关

因版本4.26于2023-03-07更新

从固件版本4.x起,BACRouter内置了Modbus网关功能。这篇文章尝试解释其底层运作机制。

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。

在从站的配置页面,“编组读取”显示读操作如何编组,并可进行测试。请特别关注服务端报告的非法数据地址!

Modbus 写

BACRouter不会合并写请求。当接收到写指令时将马上发出Modbus写请求。

对于写单个数据,有两个Modbus功能号可供使用。例如,功能5为写单个线圈,功能号15为写多个线圈但写数量可以为1。有的从站只支持其中一个功能号。”写单个线圈”参数指定采用哪个功能号。

同样地,”写单个寄存器“参数指定用于写单个寄存器的功能号。假设有任一个BACnet输出对象映射到多个保持寄存器,这意味着从站支持功能号16写多个寄存器,”写单个寄存器“将被关闭,功能号16将被采用。

离线

如果任何读写请求失败,BACRouter将会重试,如果连续失败次数达到3次,BACRouter将认为该从站离线。在离线状态下,BACRouter将间隔”离线刷新间隔“时间重试该请求。

在离线事件中,BACnet侧的反应将在后面描述。

Modbus 串行总线

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

NaN是IEEE-754标准规定的特殊值,其意思为非有效数,BACRouter不接受NaN(正负无穷大仍然被视为有效值).

当一个Modbus点被定义为单精度浮点或双精度浮点,且BACRouter读到NaN值时,该点对应对象的可靠性将被设为”UNRELIABLE_OTHER”。

脚本不支持的数据

如一个模拟量对象为自定义,当从Modbus换算到BACnet时,脚本报告不支持的数据,此对象的可靠性将被置为”UNRELIABLE_OTHER”。

对于一个AO对象,如从BACnet换算到Modbus时,脚本报告不支持的数据,此对象的可靠性将被置为“PROCESS_RROR”。

对于一个AV对象,如从BACnet换算到Modbus时,脚本报告不支持的数据,此输出尝试被放弃,对象的可靠性保持不变。

BACnet MSTP自动地址分配

因为固件版本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)

开源的BACnet stack已经实现了 “零配置方案”.

“零配置方案” 只适用于最大扫描地址为127的情况,且自动地址分配范围为64~127。如不满足,可能带来混乱。

为了避免以上限制, BACRouter实现了私有的自动地址分配方案,并且与“零配置方案”保持兼容。它有以下吸引人的特性:

  1. 从总线流量中学习最大扫描地址。
  2. 从最大的未使用地址开始分配。

所以用户可以更加自由地规划地址,例如,将0~30留给固定地址的设备,将最大扫描地址设为40。这样自动获取地址的设备接入总线后,将从高到低依次使用40~31的地址。

不管是“零配置方案”还是BACRouter的方案,当已经自动获得地址的设备从总线中断开后,再重新接入总线,非常有可能遇到地址冲突,因为在断开期间,其地址可能被其它新接入的自动获取地址的设备占用。(BACRouter可能更容易遇到问题,因为它的地址分配不是随机的), 所以

一定要在接入总线的情况下,给自动获取地址的设备上电。

MSTP支持扩展帧设备与旧设备的互操作问题

最早的BACnet MS/TP设计只支持NPDU长度到501字节,大幅地小于IP与Ethernet链路层的1497字节长度。这限制了MS/TP的传输效率,增加了应用层的复杂度,特别当两个IP或Ethernet子网通过MS/TP子网连接在一起的时候。

扩展帧设计用于解决这个问题。标准附录此处可见. 简要地说, 此附录增加了两种帧类型:

  • 32: BACnet扩展帧须应答
  • 33: BACnet扩展帧无须应答

帧类型32由帧类型5(BACnet帧须应答)扩展而来,特殊之处在于它以COBS规则编码及NPDU长度为502到1497字节。

同样地,帧类型33由帧类型6 (BACnet帧无须应答)扩展而来。

扩展帧支持从修订版16开始正式引入标准。但是现场及市场上仍然有大量的旧设备不支持。支持扩展帧设备与旧设备之间的互操作性值得探讨。

  • 非路由旧设备与支持扩展帧设备:因为所有发往旧设备的NPDU都是应用层包,在旧设备的Device对象或发出的确认服务请求中的”最大可接受APDU长度”参数限制了NPDU包的大小,所以这种配置没有问题。
  • 旧路由与支持扩展帧设备: 须经由旧路由转发到其它子网的NPDU,如果其长度超过501字节将无法被旧路由识别而丢弃,发送方也收不到”包长超出”的网络层拒绝包。甚而,旧路由的”最大可接受APDU长度” 参数有可能是由其它路由口的参数决定的 (BACnet标准允许这种做法),因而其封包长度可能超出501字节,此时发往旧路由的应用层包也可能被丢弃。因此这种配置可能造成互操作问题。

BACRouter在非常早的版本就支持了扩展帧。从固件版本3.18开始,我们在BACRouter的MS/TP配置中引入了”扩展帧支持“选项,如果在总线上有不支持扩展帧的旧路由,此选项必须关闭以避免互操作性问题。

值得注意的是,即使”扩展帧支持“选项被关闭,不象旧路由,BACRouter仍然与支持扩展帧的设备有良好的互操作性。

(截图于2021-08-05更新,因为扩展帧是标准修订版16的强制要求,所以从固件4.13起,我们把这个功能选项移入到扩展配置模式中)