

近期在调试一套工业环境监控系统时,遇到了关于Modbus连接保持的问题。现场部署了一批网口温湿度变送器,PoE取电、Modbus TCP上云,同时利旧接入存量RS485探头,并开启了SNMP和UDP Trap服务。这次面对的是一个多品牌设备混合接入的典型场景——库房里既有进口品牌恒温恒湿机组,也有国产网口温湿度变送器,还有早年部署的RS485模拟量采集模块,协议五花八门,寄存器定义各说各话。这篇从协议归一化、地址映射、数据标准化到组态兼容配置,完整记录多品牌设备混合接入的工程落地思路。
设备类型 | 品牌/型号 | 数量 | 通信方式 | 协议 | 固件版本 |
|---|---|---|---|---|---|
恒温恒湿机组 | 某进口品牌HCS-8000 | 2台 | RS485 | Modbus RTU | V3.2 |
恒温恒湿机组 | 某国产品牌HF-300 | 3台 | RS485 | Modbus RTU(非标) | V2.1 |
网口温湿度变送器 | 品牌A(SHT30) | 12台 | RJ45 | Modbus TCP | V1.8 |
网口温湿度变送器 | 品牌B(SHT35) | 8台 | RJ45 | Modbus TCP | V2.0 |
温湿度采集模块 | 品牌C(模拟量) | 6台 | RS485 | Modbus RTU | V1.5 |
边缘网关 | 自研(i.MX6ULL) | 4台 | 多接口 | 协议转换 | 定制 |
┌─────────────────────────────────────────────────────────────────┐
│ 协议差异矩阵 │
├─────────────────────────────────────────────────────────────────┤
│ 差异项 │ 进口HCS-8000 │ 国产HF-300 │ 品牌A/B/C │
├───────────────┼──────────────┼─────────────┼────────────────┤
│ 寄存器排列 │ CDAB(浮点) │ ABCD(浮点)│ 直接整数 │
│ 温度比例因子 │ ×0.1 │ ×0.01 │ ×0.01 │
│ 湿度比例因子 │ ×0.1 │ ×0.1 │ ×0.1 │
│ 寄存器基址 │ 40001 │ 30001 │ 40001 │
│ 功能码 │ 0x03/0x06 │ 0x03/0x10 │ 0x03/0x06 │
│ 波特率 │ 19200 │ 9600 │ N/A(TCP) │
│ 数据帧间隔 │ ≥3.5字符 │ ≥5ms │ N/A │
│ 从站地址范围 │ 1~247 │ 固定1~32 │ 任意 │
│ 写寄存器方式 │ 单寄存器 │ 多寄存器 │ 单寄存器 │
└───────────────┴──────────────┴─────────────┴────────────────┘┌─────────────────────────────────────────────────────────────────┐
│ 组态平台(SCADA) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 统一数据模型(标准化变量名、工程单位、量程) ││
│ │ Temp_Area1, Hum_Area1, CoolValve_Unit1, Heat_Unit1 ... ││
│ └──────────────────────────────┬──────────────────────────────┘│
│ │ Modbus TCP(统一接口) │
└─────────────────────────────────┼───────────────────────────────┘
│
══════════════════════════════════╪═══════════════════════════════
│
┌─────────────────────────────────▼───────────────────────────────┐
│ 边缘网关层(协议转换) │
│ │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ 协议驱动层 ││
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────────────┐ ││
│ │ │ Modbus │ │ Modbus │ │ Modbus │ │ Modbus TCP │ ││
│ │ │ RTU │ │ RTU │ │ RTU │ │ Server │ ││
│ │ │ (标准) │ │ (非标) │ │ (模拟量) │ │ │ ││
│ │ │ 19200/8N1│ │ 9600/8N1 │ │ 9600/8E1 │ │ 端口502 │ ││
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └───────┬───────┘ ││
│ └───────┼─────────────┼────────────┼───────────────┼─────────┘│
│ │ │ │ │ │
│ ┌───────▼─────────────▼────────────▼───────────────▼─────────┐│
│ │ 数据标准化层(地址映射 + 比例换算 + 字节序转换) ││
│ │ RawValue → EngineeringValue → StandardizedModel ││
│ └──────────────────────────────┬──────────────────────────────┘│
│ │ │
│ ┌──────────────────────────────▼──────────────────────────────┐│
│ │ 统一接口层(向组态平台暴露标准Modbus TCP服务) ││
│ │ 映射表:物理设备地址 ↔ 虚拟寄存器地址 ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘核心思路:为每种设备类型分配独立的虚拟寄存器区间,边缘网关内部完成"物理地址→虚拟地址"的翻译。
┌─────────────────────────────────────────────────────────────────┐
│ 虚拟寄存器分配表(边缘网关内部) │
├─────────────────────────────────────────────────────────────────┤
│ 区间 │ 设备类型 │ 容量 │ 说明 │
├───────────────┼────────────────────┼───────┼─────────────────┤
│ 40001~40099 │ 品牌A温湿度(Modbus │ 99 │ 每台2寄存器 │
│ │ TCP) │ │ ×12台=24个已用 │
│ 40101~40199 │ 品牌B温湿度(Modbus │ 99 │ 每台2寄存器 │
│ │ TCP) │ │ ×8台=16个已用 │
│ 40201~40299 │ 品牌C模拟量模块 │ 99 │ 每台4通道 │
│ │ (Modbus RTU) │ │ ×6台=24个已用 │
│ 42001~42099 │ 进口机组HCS-8000 │ 99 │ 每台10寄存器 │
│ │ (Modbus RTU) │ │ ×2台=20个已用 │
│ 42101~42199 │ 国产机组HF-300 │ 99 │ 每台10寄存器 │
│ │ (Modbus RTU非标) │ │ ×3台=30个已用 │
└───────────────┴────────────────────┴───────┴─────────────────┘{
"virtual_registers": {
"40001": {
"device": "brandA_01",
"protocol": "modbus_tcp",
"ip": "10.10.10.11",
"port": 502,
"slave_id": 1,
"register_type": "holding",
"address": 0,
"data_type": "int16",
"scale": 0.01,
"offset": 0,
"unit": "°C",
"description": "特藏库A-1温度"
},
"40002": {
"device": "brandA_01",
"protocol": "modbus_tcp",
"ip": "10.10.10.11",
"port": 502,
"slave_id": 1,
"register_type": "holding",
"address": 1,
"data_type": "uint16",
"scale": 0.1,
"offset": 0,
"unit": "%RH",
"description": "特藏库A-1湿度"
},
"42001": {
"device": "hcs8000_01",
"protocol": "modbus_rtu",
"serial_port": "/dev/ttyUSB0",
"baudrate": 19200,
"bytesize": 8,
"parity": "N",
"stopbits": 1,
"slave_id": 1,
"register_type": "holding",
"address": 0,
"data_type": "float32",
"byte_order": "cdab",
"scale": 0.1,
"offset": 0,
"unit": "°C",
"description": "进口机组1回风温度"
},
"42101": {
"device": "hf300_01",
"protocol": "modbus_rtu",
"serial_port": "/dev/ttyUSB1",
"baudrate": 9600,
"bytesize": 8,
"parity": "N",
"stopbits": 1,
"slave_id": 1,
"register_type": "holding",
"address": 0,
"data_type": "int16",
"scale": 0.01,
"offset": 0,
"write_function": "0x10",
"unit": "°C",
"description": "国产机组1回风温度"
}
}
}不同品牌设备对多寄存器数据的字节排列顺序不同,边缘网关需要在驱动层统一处理:
import struct
def convert_float32(raw_bytes: bytes, byte_order: str) -> float:
"""
将4字节原始数据按指定字节序转换为float32
byte_order: 'abcd' | 'cdab' | 'badc' | 'dcba'
"""
if byte_order == 'abcd':
return struct.unpack('>f', raw_bytes)[0]
elif byte_order == 'cdab':
# CDAB: 交换高低字
swapped = raw_bytes[2:4] + raw_bytes[0:2]
return struct.unpack('>f', swapped)[0]
elif byte_order == 'badc':
# BADC: 字节内交换
swapped = raw_bytes[1:2] + raw_bytes[0:1] + raw_bytes[3:4] + raw_bytes[2:3]
return struct.unpack('>f', swapped)[0]
elif byte_order == 'dcba':
# DCBA: 完全反转
swapped = raw_bytes[::-1]
return struct.unpack('>f', swapped)[0]
else:
raise ValueError(f"Unknown byte order: {byte_order}")
# 示例:进口机组HCS-8000回风温度
# 原始寄存器值: [0x4328, 0x3333] (CDAB)
raw = bytes([0x43, 0x28, 0x33, 0x33])
temp = convert_float32(raw, 'cdab')
print(f"回风温度: {temp * 0.1:.1f}°C") # 输出: 回风温度: 21.6°Cdef raw_to_engineering(raw_value: int, scale: float, offset: float) -> float:
"""原始寄存器值 → 工程值"""
return raw_value * scale + offset
def engineering_to_raw(eng_value: float, scale: float, offset: float) -> int:
"""工程值 → 原始寄存器值(写操作使用)"""
return int((eng_value - offset) / scale)
# 各品牌温度换算
devices = [
{"name": "HCS-8000", "raw": 216, "scale": 0.1, "offset": 0}, # → 21.6°C
{"name": "HF-300", "raw": 2160, "scale": 0.01, "offset": 0}, # → 21.6°C
{"name": "BrandA", "raw": 2160, "scale": 0.01, "offset": 0}, # → 21.6°C
]
for dev in devices:
eng = raw_to_engineering(dev["raw"], dev["scale"], dev["offset"])
print(f"{dev['name']}: 原始值{dev['raw']} → {eng}°C")国产HF-300机组的一个特殊之处:写多寄存器功能码为0x10,但数据帧格式在标准基础上增加了一个"校验字节"。边缘网关需要定制驱动:
def hf300_write_register(slave_id: int, address: int, value: int) -> bytes:
"""
HF-300 写寄存器(非标Modbus RTU)
在标准0x10功能码基础上增加1字节校验和
"""
# 标准帧
frame = struct.pack('>BBHH', slave_id, 0x10, address, 1) # 写1个寄存器
frame += struct.pack('>B', 2) # 字节计数
frame += struct.pack('>H', value) # 寄存器值
# 附加校验字节(所有字节异或)
checksum = 0
for b in frame:
checksum ^= b
frame += struct.pack('>B', checksum)
# 添加CRC16
crc = crc16(frame)
frame += struct.pack('<H', crc)
return frame组态平台无需关心底层设备的品牌差异,所有变量统一命名、统一工程单位:
┌─────────────────────────────────────────────────────────────────┐
│ 组态平台变量表(标准化后) │
├──────────────┬──────────┬────────┬──────┬──────────────────────┤
│ 变量名 │ 虚拟地址 │ 数据类型│ 读写 │ 描述 │
├──────────────┼──────────┼────────┼──────┼──────────────────────┤
│ AI_Temp_A1 │ 40001 │ FLOAT │ RO │ A区传感器1温度(°C) │
│ AI_Hum_A1 │ 40002 │ FLOAT │ RO │ A区传感器1湿度(%RH) │
│ AI_Temp_A2 │ 40003 │ FLOAT │ RO │ A区传感器2温度(°C) │
│ ... │ ... │ ... │ ... │ ... │
│ AI_Temp_B1 │ 40101 │ FLOAT │ RO │ B区传感器1温度(°C) │
│ ... │ ... │ ... │ ... │ ... │
│ AI_HVAC1_Ret│ 42001 │ FLOAT │ RO │ 机组1回风温度(°C) │
│ AI_HVAC1_Sup│ 42002 │ FLOAT │ RO │ 机组1送风温度(°C) │
│ AI_HVAC1_Hum│ 42003 │ FLOAT │ RO │ 机组1回风湿度(%RH) │
│ AO_HVAC1_Cool│42004 │ FLOAT │ RW │ 机组1冷水阀(%) │
│ AO_HVAC1_Heat│42005 │ FLOAT │ RW │ 机组1加热(%) │
│ ... │ ... │ ... │ ... │ ... │
│ AI_HVAC3_Ret│ 42101 │ FLOAT │ RO │ 机组3回风温度(°C) │
│ AO_HVAC3_Cool│42104 │ FLOAT │ RW │ 机组3冷水阀(%) │
└──────────────┴──────────┴────────┴──────┴──────────────────────┘┌─────────────────────────────────────────────────────────────────┐
│ 多品牌设备混合接入 — 系统总览画面 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ 进口机组 HCS-8000 #1 │ │ 国产机组 HF-300 #1 │ │
│ │ 状态: 运行(Modbus) │ │ 状态: 运行(Modbus) │ │
│ │ 回风: 21.6°C/50.2% │ │ 回风: 21.4°C/50.8% │ │
│ │ 冷水阀: 35% │ │ 冷水阀: 38% │ │
│ │ 加热: 0% │ │ 加热: 0% │ │
│ │ [启停] [模式] │ │ [启停] [模式] │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────┐ ┌──────────────────────┐ │
│ │ 国产机组 HF-300 #2 │ │ 国产机组 HF-300 #3 │ │
│ │ 状态: 运行(Modbus) │ │ 状态: 待机(Modbus) │ │
│ │ 回风: 21.8°C/49.5% │ │ 回风: --/-- │ │
│ │ 冷水阀: 32% │ │ 冷水阀: 0% │ │
│ │ 加热: 0% │ │ 加热: 0% │ │
│ │ [启停] [模式] │ │ [启停] [模式] │ │
│ └──────────────────────┘ └──────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ 温湿度监测点(品牌A + 品牌B + 品牌C混合) │ │
│ │ A1: 21.2°C/51.0% ████████████░░ A2: 21.5°C/50.5% ████████│ │
│ │ A3: 21.0°C/51.2% ████████████░░ B1: 21.3°C/50.8% ████████│ │
│ │ B2: 21.6°C/49.8% ████████████░░ B3: 21.4°C/50.2% ████████│ │
│ │ C1: 21.1°C/51.5% ████████████░░ C2: 21.7°C/49.2% ████████│ │
│ │ ...(共26个监测点,统一显示,不区分品牌) │ │
│ └─────────────────────────────────────────────────────────────┘ │
│ │
│ 通信状态:网关1[正常] 网关2[正常] 网关3[正常] 网关4[正常] │
│ 协议统计:Modbus TCP×20 | Modbus RTU(标准)×8 | Modbus RTU(非标)×3│
└─────────────────────────────────────────────────────────────────┘#!/usr/bin/env python3
"""
边缘网关 - 多协议驱动管理器
"""
import threading
import queue
import time
from typing import Dict, Any, Callable
class ProtocolDriver:
"""协议驱动基类"""
def read_registers(self, address: int, count: int) -> list:
raise NotImplementedError
def write_register(self, address: int, value: int) -> bool:
raise NotImplementedError
class ModbusTCPDriver(ProtocolDriver):
"""Modbus TCP驱动"""
def __init__(self, ip: str, port: int = 502, slave_id: int = 1):
self.ip = ip
self.port = port
self.slave_id = slave_id
self.socket = None
self.lock = threading.Lock()
self._connect()
def _connect(self):
import socket
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.settimeout(3.0)
self.socket.connect((self.ip, self.port))
def read_registers(self, address: int, count: int) -> list:
with self.lock:
# 构造Modbus TCP请求
transaction_id = int(time.time() * 1000) % 65535
adu = struct.pack(">HHHBBHH",
transaction_id, 0, 6,
self.slave_id, 0x03,
address, count)
self.socket.send(adu)
response = self.socket.recv(256)
# 解析响应
byte_count = response[8]
data = response[9:9+byte_count]
return [struct.unpack(">H", data[i:i+2])[0] for i in range(0, len(data), 2)]
def write_register(self, address: int, value: int) -> bool:
with self.lock:
transaction_id = int(time.time() * 1000) % 65535
adu = struct.pack(">HHHBBHH",
transaction_id, 0, 6,
self.slave_id, 0x06,
address, value)
self.socket.send(adu)
response = self.socket.recv(12)
return len(response) >= 12
class ModbusRTUDriver(ProtocolDriver):
"""Modbus RTU驱动(支持标准与非标)"""
def __init__(self, port: str, baudrate: int, bytesize: int = 8,
parity: str = 'N', stopbits: int = 1, slave_id: int = 1,
custom_handler: Callable = None):
self.slave_id = slave_id
self.custom_handler = custom_handler
import serial
self.ser = serial.Serial(port, baudrate, bytesize, parity, stopbits, timeout=2.0)
self.lock = threading.Lock()
def read_registers(self, address: int, count: int) -> list:
with self.lock:
if self.custom_handler:
return self.custom_handler.read_registers(self, address, count)
# 标准Modbus RTU读取
frame = struct.pack('>BBHH', self.slave_id, 0x03, address, count)
frame += self._crc16(frame)
self.ser.write(frame)
response = self.ser.read(5 + count * 2)
if len(response) < 5 + count * 2:
raise TimeoutError("Modbus RTU read timeout")
byte_count = response[2]
data = response[3:3+byte_count]
return [struct.unpack(">H", data[i:i+2])[0] for i in range(0, len(data), 2)]
def write_register(self, address: int, value: int) -> bool:
with self.lock:
if self.custom_handler:
return self.custom_handler.write_register(self, address, value)
# 标准写单寄存器
frame = struct.pack('>BBHH', self.slave_id, 0x06, address, value)
frame += self._crc16(frame)
self.ser.write(frame)
response = self.ser.read(8)
return len(response) >= 8
@staticmethod
def _crc16(data: bytes) -> bytes:
crc = 0xFFFF
for b in data:
crc ^= b
for _ in range(8):
if crc & 0x0001:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return struct.pack('<H', crc)class DataStandardizer:
"""数据标准化引擎"""
def __init__(self, mapping_table: dict):
self.mapping = mapping_table
self.cache = {} # 虚拟地址 → 最近一次工程值
self.drivers = {} # device_name → ProtocolDriver实例
self._init_drivers()
def _init_drivers(self):
"""初始化协议驱动"""
for vaddr, config in self.mapping.items():
dev = config['device']
if dev not in self.drivers:
if config['protocol'] == 'modbus_tcp':
self.drivers[dev] = ModbusTCPDriver(
config['ip'], config.get('port', 502), config['slave_id'])
elif config['protocol'] == 'modbus_rtu':
self.drivers[dev] = ModbusRTUDriver(
config['serial_port'], config['baudrate'],
config.get('bytesize', 8), config.get('parity', 'N'),
config.get('stopbits', 1), config['slave_id'])
def read_engineering_value(self, virtual_addr: int) -> float:
"""读取虚拟寄存器对应的工程值"""
config = self.mapping.get(virtual_addr)
if not config:
raise ValueError(f"Unknown virtual address: {virtual_addr}")
driver = self.drivers[config['device']]
raw = driver.read_registers(config['address'], 1)[0]
# 工程换算
eng_value = raw * config['scale'] + config['offset']
self.cache[virtual_addr] = eng_value
return eng_value
def write_engineering_value(self, virtual_addr: int, eng_value: float) -> bool:
"""写入工程值(自动转换为原始寄存器值)"""
config = self.mapping.get(virtual_addr)
if not config or config.get('read_only', False):
return False
raw = int((eng_value - config['offset']) / config['scale'])
driver = self.drivers[config['device']]
return driver.write_register(config['address'], raw)
def poll_all(self, interval: float = 5.0):
"""轮询所有虚拟寄存器"""
while True:
for vaddr in self.mapping:
try:
value = self.read_engineering_value(vaddr)
# 发布到组态平台(通过Modbus TCP Server)
self._publish_to_scada(vaddr, value)
except Exception as e:
logger.error(f"Poll {vaddr} failed: {e}")
time.sleep(interval)from socketserver import ThreadingMixIn, TCPServer, BaseRequestHandler
import struct
class ModbusTCPServer(ThreadingMixIn, TCPServer):
allow_reuse_address = True
def __init__(self, server_address, RequestHandlerClass, standardizer):
self.standardizer = standardizer
super().__init__(server_address, RequestHandlerClass)
class ModbusTCPRequestHandler(BaseRequestHandler):
def handle(self):
while True:
try:
data = self.request.recv(1024)
if not data:
break
# 解析Modbus TCP ADU
transaction_id, protocol_id, length = struct.unpack('>HHH', data[:6])
unit_id, function_code = data[6], data[7]
if function_code == 0x03: # 读保持寄存器
start_addr, reg_count = struct.unpack('>HH', data[8:12])
values = []
for i in range(reg_count):
vaddr = start_addr + i + 1 # 虚拟地址从1开始
eng_val = self.server.standardizer.cache.get(vaddr, 0)
# 转换为int16返回
values.append(int(eng_val * 100)) # ×100精度
# 构造响应
response = struct.pack('>HHHBBB',
transaction_id, protocol_id,
3 + reg_count * 2,
unit_id, function_code,
reg_count * 2)
for v in values:
response += struct.pack('>H', v & 0xFFFF)
self.request.send(response)
elif function_code == 0x06: # 写单寄存器
reg_addr, reg_value = struct.unpack('>HH', data[8:12])
vaddr = reg_addr + 1
eng_val = reg_value / 100.0 # 还原工程值
self.server.standardizer.write_engineering_value(vaddr, eng_val)
# 回写响应
self.request.send(data[:12])
except Exception as e:
logger.error(f"Request handler error: {e}")
break
# 启动服务器
standardizer = DataStandardizer(mapping_table)
server = ModbusTCPServer(('0.0.0.0', 502), ModbusTCPRequestHandler, standardizer)
# 启动轮询线程
poll_thread = threading.Thread(target=standardizer.poll_all, args=(5.0,), daemon=True)
poll_thread.start()
# 启动TCP服务器
server.serve_forever()参数 | 值 |
|---|---|
协议 | Modbus TCP |
服务器IP | 边缘网关IP(如10.10.90.100) |
端口 | 502 |
从站地址 | 1 |
扫描周期 | 5000ms(温湿度)/ 2000ms(设备状态) |
超时时间 | 3000ms |
重试次数 | 3 |
组态平台只需配置一个Modbus TCP连接,所有品牌设备的所有数据均通过边缘网关的虚拟寄存器统一访问,底层差异完全屏蔽。
现象 | 原因 | 解决 |
|---|---|---|
进口机组温度值异常(跳变) | 字节序错误(CDAB误配为ABCD) | 修正byte_order配置 |
国产机组写寄存器无响应 | 功能码不匹配(需要0x10而非0x06) | 定制驱动适配 |
品牌C模拟量值漂移 | 校验方式不同(偶校验vs无校验) | 修改serial参数 |
组态平台偶尔读不到数据 | 轮询间隔过短,设备忙 | 增加轮询间隔或分批读取 |
多设备RS485通信冲突 | 从站地址重复 | 重新分配地址 |
┌─────────────────────────────────────────────────────────────────┐
│ 兼容性测试矩阵 │
├─────────────────────────────────────────────────────────────────┤
│ 测试项 │ 方法 │ 结果 │
├──────────────────────┼────────────────────────┼────────────────┤
│ 进口+国产同时运行 │ 同时下发控制指令 │ ✅ 正常 │
│ 品牌A+B+C混合轮询 │ 连续72小时轮询 │ ✅ 无丢包 │
│ 字节序自动识别 │ 切换byte_order配置 │ ✅ 值正确 │
│ 非标协议写操作 │ 写冷水阀开度0→100% │ ✅ 响应正确 │
│ 通信中断恢复 │ 拔插RS485线 │ ✅ 自动重连 │
│ 组态平台热切换 │ 切换主备网关 │ ✅ <3s │
│ 高并发读取 │ 10个客户端同时读 │ ✅ 无阻塞 │
└──────────────────────┴────────────────────────┴────────────────┘多品牌接入、温湿度变送器、恒温恒湿机组、组态兼容、Modbus TCP、Modbus RTU、协议归一化、边缘网关、数据标准化、字节序转换、非标协议、地址映射、SCADA、RS485、工程单位换算
多品牌设备混合接入的核心在于"边缘网关协议归一化+数据标准化"——通过虚拟寄存器映射表屏蔽底层设备的协议差异、字节序差异和比例因子差异,向上对组态平台暴露统一的Modbus TCP接口,使不同品牌、不同协议的设备在组态层面完全透明,实现真正的即插即用与无缝兼容。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。