Skip to content

原生 TCP 告警

原生 TCP 采用二进制帧格式对接,适合不便使用 HTTP 的嵌入式或工控场景。

本章节介绍通过原生TCP协议触发设备进行声光告警,虽然是TCP对接,但是实际上只是借用了Modbus TCP协议。

本章节只给出直接演示,不详细赘述Modbus TCP协议,请自行搜索Modbus TCP的协议,了解底层实现。

什么时候使用原生TCP告警?

  • 需要将其他厂家的设备更换为此设备
  • 原设备采用TCP协议
  • 不修改原设备的对接方式

总的来说,在不更改原有对接方式的情况下,仅修改发送的数据,即可直接对接本公司设备。

比较简单的对接方法,只需要在界面中配置好播报模板和对应ID,播报时调用对应ID即可播报。

该方法对应Modbus TCP中的指令模板

准备工作

  • 打开Modbus TCP的服务开关,配置端口号
  • 在Modbus TCP中配置指令列表,该功能详细描述请参考指令模板

调用演示

调用指令模板中的0x01模板,发送的字节如下:

0x00 0x01 0x00 0x00 0x00 0x06 0x01 0x06 0x00 0x63 0x00 0x01

其中最后的一个字节为要调用的模板编号,该方式可以调用0x01~0xff范围内的模板。

代码示例

以下为Python脚本模拟发送该TCP请求的实例,需要提前在Modbus Tcp配置页面中配置指令编号1的模板。

def templateModbusTcp():
'''
写单寄存器,触发模板告警,需要提前定义模板
响应报文:MBAP header + 功能码 + 寄存器地址H 寄存器地址L + 寄存器值H 寄存器值L
:return:
'''
# 写单寄存器,100地址(1开始算),指令中以0开始计算,所以为0x63,指令编号为1。
return bytes.fromhex("0001 0000 0006 0106 0063 0001")
# 写单寄存器,100地址,指令编号为2
# return bytes.fromhex("0001 0000 0006 0106 0063 0002")
# 目标 IP 和端口
ip = "192.168.0.66"
port = 502
# 创建一个 socket 对象
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# raw_data = sendModbusTCP("123哈哈哈哈哈-发送的2024年01月16日14:23:04")
raw_data = templateModbusTcp()
# 建立连接
s.connect((ip, port))
# 发送数据
s.sendall(bytes(raw_data))
# 可以接收响应数据
response = s.recv(1024)
print("Received:", response.hex())
# 由于这里不确定服务端是否返回数据,所以暂时注释掉接收部分
print("Data sent successfully.")

响应数据

0001 0000 0006 0106 0063 0001

强大的播报方法,可以自定义灯光样式、颜色、播报内容,但构造数据较为复杂,使用写多寄存器实现。

该方法对应Modbus TCP-自定义声光播报

准备工作

  • 打开Modbus TCP的服务开关,配置端口号

调用演示

使用一下声光告警参数进行调用

参数名
led_style15
color[“#8A2BE2”]
led_flag[0.03,1]
play_time0
tone0
tts_speed0
text这是TCP调用演示

构造数据

下表数据中,字节113为Modbus TCP协议写多寄存器的包头 1479为声光播报的具体内容,相关介绍参见Modbus TCP-地址表

字节说明实例值
1-4包头’0x00’, ‘0x01’, ‘0x00’, ‘0x00’
5-6包长度
下一个字节开始到最后的字节数
’0x00’, ‘0x49’
7-8固定值’0x01’, ‘0x10’
9-10固定值’0x00’, ‘0x64’
11-12写入的寄存器数量
声光播报内容数据字节数 / 2
本案例内容为14~79
66字节
66 / 2 = 33
转16进制为0x21
’0x00’, ‘0x21’
13写入的字节数
写入的寄存器数量*2即可
’0x42’
14-15Led style’0x00’, ‘0x0f’,
16-31led_color’0x00’, ‘0x8a’, ‘0x2b’, ‘0xe2’,
‘0x00’, ‘0x00’,‘0x00’, ‘0x00’,
‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’,
‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’
32-33play_time 播报时长’0x00’, ‘0x00’
34-35tone 铃声编号’0x00’, ‘0x00’
36-37tts_speed 播报速率’0x00’, ‘0x00’
38-39led_style 样式参数’0x03’, ‘0x01’
40-43保留字节’0x00’, ‘0x00’, ‘0x00’, ‘0x00’
44-47文本内容-”这“‘0x00’, ‘0xe8’, ‘0xbf’, ‘0x99’
48-51文本内容-”是“‘0x00’, ‘0xe6’, ‘0x98’, ‘0xaf’
52-55文本内容-”T“‘0x00’, ‘0x00’, ‘0x00’, ‘0x54’
56-59文本内容-”C“‘0x00’, ‘0x00’, ‘0x00’, ‘0x43’
60-63文本内容-”P“‘0x00’, ‘0x00’, ‘0x00’, ‘0x50
64-67文本内容-”调“‘0x00’, ‘0xe8’, ‘0xb0’, ‘0x83’,
68-71文本内容-”用“‘0x00’, ‘0xe7’, ‘0x94’, ‘0xa8’,
72-75文本内容-”演“‘0x00’, ‘0xe6’, ‘0xbc’, ‘0x94’
76-79文本内容-”示“‘0x00’, ‘0xe7’, ‘0xa4’, ‘0xba’

组合后数据

[‘0x00’, ‘0x01’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x49’, ‘0x01’, ‘0x10’, ‘0x00’, ‘0x64’, ‘0x00’, ‘0x21’, ‘0x42’, ‘0x00’, ‘0x0f’, ‘0x00’, ‘0x8a’, ‘0x2b’, ‘0xe2’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x03’, ‘0x01’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0xe8’, ‘0xbf’, ‘0x99’, ‘0x00’, ‘0xe6’, ‘0x98’, ‘0xaf’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x54’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x43’, ‘0x00’, ‘0x00’, ‘0x00’, ‘0x50’, ‘0x00’, ‘0xe8’, ‘0xb0’, ‘0x83’, ‘0x00’, ‘0xe7’, ‘0x94’, ‘0xa8’, ‘0x00’, ‘0xe6’, ‘0xbc’, ‘0x94’, ‘0x00’, ‘0xe7’, ‘0xa4’, ‘0xba’]

Python代码示例

import socket
import time
write_multi_reg_header = [0x0001, 0x0000, 0x0000, 0x0110]
'''
MBAP header报文头格式:
示例值 长度(byte) 说明
0x0001 2 可以一直写1,返回时会附带此值
0x0000 2 固定值
0x0006 2 从下一个字节开始算,一直到最后的字节数
0x01 1 固定值,1即可
0x10 1 功能码,定义参见Modbus TCP
'''
def wordList2byteList(l):
'''
双字节List转单字节List
:param l:
:return:
'''
byte_list = []
for word in l:
byte_list.append(word >> 8)
byte_list.append(word & 0xff)
return byte_list
def createTcpPkg(start_addr, reg_number, data:list):
'''
创建写多寄存器的pkg
响应报文:MBAP header + 功能码 + 起始地址H 起始地址L + 寄存器数量H 寄存器数量L(共12字节)
:param start_addr:
:param reg_number: 双字节,例如0x000f
:param addr: 双字节list
:return:
'''
# 报文头
pkg = write_multi_reg_header
# 修改报文头的长度,一般为请求的数据从航都字节数+1
pkg[2] = len(data)*2 + 7
# 写多寄存器的起始地址2字节
pkg += [start_addr, reg_number]
# 将之前的数据转为byte的list
byte_list = wordList2byteList(pkg)
# 写字节个数,长度为1字节
byte_list.append(len(data) * 2 & 0xff)
byte_list += wordList2byteList(data)
return byte_list
def encodeStr(s):
'''
字符串编码
:param s:
:return:
'''
raw = []
for bit in s:
bit = int.from_bytes(bit.encode('utf-8'), 'big')
word = [0] * 2
word[0] = (0xffff0000 & bit) >> 16
word[1] = 0xffff & bit
raw += word
return raw
# 触发自定义指令demo
def sendModbusTCP(tts):
'''
TCP发送自定义指令
:param tts:
:return:
'''
data_arr = []
led_color = [0] * 8
# LED样式
led_style = 15
# LED颜色
led_color[0] = 0x8A
led_color[1] = 0x2be2
play_time = 0
tone = 0
tts_speed = 0
led_flag = 0x0301
empty = [0] * 2
tts_str = encodeStr(tts)
data_arr += [led_style] + led_color + [play_time] + [tone] + [tts_speed] + [led_flag] + empty + tts_str
print('播报Payload:')
print([f"0x{x:04x}" for x in data_arr])
print('Payload长度:', len(data_arr))
raw_data = createTcpPkg(100, len(data_arr), data_arr)
print('封装后的原始数据:')
print([f"0x{x:02x}" for x in raw_data])
print(len(raw_data))
return raw_data
def templateModbusTcp():
'''
写单寄存器,触发模板告警,需要提前定义模板
响应报文:MBAP header + 功能码 + 寄存器地址H 寄存器地址L + 寄存器值H 寄存器值L
:return:
'''
# 写单寄存器,100地址(1开始算),指令中以0开始计算,所以为0x63,指令编号为1。
return bytes.fromhex("0001 0000 0006 0106 0063 0001")
# 写单寄存器,100地址,指令编号为2
# return bytes.fromhex("0001 0000 0006 0106 0063 0002")
# 目标 IP 和端口
ip = "192.168.0.216"
port = 502
# 创建一个 socket 对象
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
raw_data = sendModbusTCP("这是TCP调用演示")
# raw_data = templateModbusTcp()
# 建立连接
s.connect((ip, port))
# 发送数据
s.sendall(bytes(raw_data))
# 可以接收响应数据
response = s.recv(1024)
print("Received:", response.hex())
# 由于这里不确定服务端是否返回数据,所以暂时注释掉接收部分
print("Data sent successfully.")

响应数据

最后的两个字节为写入的寄存器数量,本案例为0x0021,其他不变

0x0001 0x0000 0x0006 0x0110 0x0064 0x0021