如何在Python中同一IP端口下创建多个BACnet/IP设备以支持YABE自动发现
在同一IP:端口下模拟多个BACnet/IP设备的解决方案
你遇到的核心问题在于:每个独立的BAC0.lite或bacpypes3 Application实例都会尝试独占UDP端口47808,所以直接创建多个实例会触发端口冲突。要在同一个IP和端口下模拟多个BACnet设备,我们需要利用BACnet的虚拟网络(VLAN)机制,在单个网络接口上托管多个虚拟设备节点,它们共享同一个UDP端口,通过唯一的设备ID来区分。
下面是基于bacpypes3的完整可实现方案,同时解释你之前踩坑的原因:
为什么之前的尝试失败?
- 多BAC0.lite实例:BAC0.lite本质是封装了单个bacpypes3 Application,每个实例都会绑定到指定端口,所以第二个实例启动时会发现端口已被占用,抛出初始化错误。
- bacpypes3绑定错误:你尝试使用虚拟网络时,没有正确完成「物理接口→虚拟网络→虚拟节点→应用」的绑定链,导致服务未正确注册,触发
unbound server或already bound错误。
完整实现代码(模拟10个独立BACnet设备)
这个脚本会在192.168.1.54/24的IP和47808端口上创建10个虚拟设备,每个设备都有独立的Device ID和自定义的温度/压力对象,YABE可以一次性扫描到所有设备:
import asyncio import logging import sys # 配置日志,方便调试BACnet通信细节 logging.basicConfig( level=logging.INFO, format="%(levelname)-8s %(message)s", handlers=[logging.StreamHandler(sys.stdout)], ) # Pure bacpypes3 imports from bacpypes3.pdu import IPv4Address, LocalStation from bacpypes3.comm import bind from bacpypes3.vlan import Network as VirtualNetwork, Node as VirtualNode from bacpypes3.app import Application from bacpypes3.appservice import ApplicationServiceAccessPoint from bacpypes3.local.device import DeviceObject from bacpypes3.local.analog import AnalogValueObject, AnalogInputObject from bacpypes3.primitivedata import Real, ObjectIdentifier from bacpypes3.basetypes import EngineeringUnits async def create_virtual_device(node: VirtualNode, device_id: int, device_name: str): """创建单个虚拟BACnet设备并添加自定义对象""" # 创建设备对象(必须有唯一的device_id) device_obj = DeviceObject( objectIdentifier=("device", device_id), objectName=device_name, vendorIdentifier=15, # 自定义厂商ID,可修改 ) # 创建应用服务点和应用实例 asap = ApplicationServiceAccessPoint() app = Application(device_obj, asap) # 添加自定义对象:温度传感器 temp_sensor = AnalogValueObject( objectIdentifier=ObjectIdentifier("analogValue", 1), objectName=f"{device_name}_Room_Temp", presentValue=Real(22.0 + device_id * 0.5), # 每个设备温度略有不同,方便区分 units=EngineeringUnits.degreesCelsius, description="Room Temperature Sensor", ) app.add_object(temp_sensor) # 添加自定义对象:压力传感器 pressure_sensor = AnalogInputObject( objectIdentifier=ObjectIdentifier("analogInput", 2), objectName=f"{device_name}_Room_Pressure", presentValue=Real(1013.25 + device_id * 1.0), units=EngineeringUnits.pascals, description="Room Pressure Sensor", ) app.add_object(pressure_sensor) # 将应用绑定到虚拟节点 bind(asap, node) print(f"✅ 设备 {device_name} (ID: {device_id}) 创建完成") async def main(): try: print("🚀 启动多BACnet设备模拟服务...") # 1. 创建物理网络接口:绑定到指定IP和BACnet标准端口47808 local_ip = IPv4Address("192.168.1.54/24") local_station = LocalStation(local_ip, port=47808) # 2. 创建虚拟网络(VLAN),用于托管多个虚拟设备节点 vlan = VirtualNetwork() # 3. 将物理接口绑定到虚拟网络 bind(local_station, vlan) # 4. 创建多个虚拟设备节点并启动 tasks = [] for dev_idx in range(1, 11): # 每个节点需要唯一的设备ID和名称 device_id = 1230 + dev_idx device_name = f"Virtual_Device_{dev_idx}" # 创建虚拟节点,绑定到VLAN node = VirtualNode() bind(node, vlan) # 添加创建设备的任务 tasks.append(create_virtual_device(node, device_id, device_name)) # 等待所有设备创建完成 await asyncio.gather(*tasks) print("\n📋 所有虚拟设备已启动,等待YABE扫描...") print(" 设备ID范围:1231-1240") print(" 物理地址:192.168.1.54:47808") # 保持服务运行,直到手动中断 await asyncio.Future() except KeyboardInterrupt: print("\n🛑 服务已手动停止") except Exception as e: print(f"❌ 启动失败:{str(e)}") if __name__ == "__main__": asyncio.run(main())
关键代码解释
- 虚拟网络(VLAN):
VirtualNetwork是bacpypes3中实现多设备共享同一物理端口的核心,它负责转发不同虚拟节点之间的BACnet报文。 - 虚拟节点(Node):每个虚拟设备对应一个
VirtualNode,节点绑定到VLAN,实现设备的逻辑隔离。 - 绑定链:必须严格遵循
LocalStation → VirtualNetwork → VirtualNode → ApplicationServiceAccessPoint → Application的绑定顺序,否则会出现服务未绑定的错误。 - 唯一设备ID:每个
DeviceObject的device_id必须全局唯一,这是YABE区分不同设备的核心标识。
测试步骤
- 在第一台计算机上运行上述脚本;
- 在第二台计算机上打开YABE,确保配置正确:
- 通信方式:
BACnet/IP V4 & V6 over Udp - 端口:
47808 - 本地端点:
192.168.1.38
- 通信方式:
- 执行「Who Is」扫描,即可看到所有10个虚拟设备,点击每个设备可以查看对应的温度、压力对象。
内容的提问来源于stack exchange,提问作者Alix
相关产品推荐
相关产品推荐

