python实现MC协议(SLMP 3E帧)的TCP服务端是一件稍微麻烦点的事情。它不像modbusTCP那样,可以使用现成的pymodbus模块去实现。但是,我们可以根据协议帧进行组包,自己去实现帧的格式,而这一切可以基于socket模块。本文为第二篇。
二、读写保持寄存器的完整交互包
# 客户端发送(读) -》
50 00 00 FF FF 03 00 0C 00 10 00 01 04 00 00 00 00 00 A8 05 00
# 《- 服务端应答
D0 00 00 FF FF 03 00 0C 00 00 00 73 00 00 00 00 00 00 00 00 00
# 客户端发送(写) -》
50 00 00 FF FF 03 00 16 00 10 00 01 14 00 00 0A 00 00 A8 05 00 4E 47 00 00 00 00 00 00 00 00
# 《- 服务端应答
D0 00 00 FF FF 03 00 02 00 00 00
1、分析交互包
基于上述交互包,我们查阅官方文档发现交互包使用的是二进制代码。那么,二进制代码与ASCII代码有什么区别呢?
SLMP(Seamless Message Protocol)3E帧有两种表示方式:二进制格式和ASCII格式。它们的区别在于数据的传输方式和呈现形式。
(1)二进制格式:
在二进制格式中,SLMP 3E帧中的各个字段(如帧头、副帧头、命令码、数据等)以二进制形式直接编码和传输。数据在网络中以原始的二进制位模式传输,这种方式效率较高,适用于网络传输。二进制格式通常用于实际的网络通信中,数据以二进制流的形式在网络上传输。
(2)ASCII格式:
在ASCII格式中,SLMP 3E帧中的各个字段被转换成ASCII字符表示。数据以ASCII码的文本形式进行传输,每个字节被转换为两个ASCII字符(通常是十六进制表示)。ASCII格式通常用于调试和人机界面中,方便人们查看和理解数据。
总的来说,二进制格式适用于机器之间的网络通信,而ASCII格式适用于人机交互和调试过程中的数据显示。选择哪种格式取决于具体的应用场景和需求。
因此,本文实现的是二进制格式,如果你会实现二进制格式,那么你也能实现ASCII格式。
2、读写保持寄存器的请求处理
(1)表头
客户端的两个请求,相同部分都为50 00 00 FF FF 03 00,我们姑且称之为表头。
(2)读/写长度(协议帧的长度)
0C 00是固定长度(读的时候报文都是这么长)与16 00 根据实际长度变化,表示后面数据的长度,例如前者,应该以00 0C来看长度,表示后面有12个00那样的长度。
(3)固定值
10 00
(4)读/写指令
01 04 / 01 14
(5)读/写寄存器地址
00 00 00 00 00 A8 05 00 / 00 00 0A 00 00 A8 05 00,其中写的0A 00代表从第10个保持寄存器,05表示读写5个寄存器
3、读写保持寄存器的响应处理
(1)表头
客户端的两个请求,相同部分都为D0 00 00 FF FF 03 00,我们姑且称之为表头。
(2)长度(协议帧的长度)
读:0C 00根据实际长度变化,写:02 00 可以不变化。
(3)固定值
00 00
(4)读/写响应
响应实际读到的数据 / 无
4、程序设计
根据上述内容,实现了一个定制MC服务器,能够处理保持寄存器的读写请求,给出正确的响应。
import socket
import struct
# 创建一个TCP/IP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定套接字到特定地址和端口
server_address = ('192.168.1.188', 12345) # 服务器地址和端口
server_socket.bind(server_address)
# 监听连接
server_socket.listen(1)
print('等待客户端连接...')
connection, client_address = server_socket.accept()
print('客户端已连接:', client_address)
def request_verdict(req_bytes_frame): # req_bytes_frame是字节数据b'\x02\x00\x08\x00\x00\x00\x00\x00\x10\x00\x01\x01\x02\x03\x04\x03'
command = req_bytes_frame.hex()[22:26] # 转成16进制字符串好数据处理
if command in ["0104", "0401"]: # 判断读写
return False # 读
elif command in ["0114", "1401"]:
return True # 写
else:
raise ValueError("读写指令错误!")
def write_response_frame(req_bytes_frame):
response = "D00000FFFF030002000000" # 写成功则返回这一串数据
content = req_bytes_frame.hex()[42:] # 看一下客户端想写的内容
print("客户端想要写入的内容:", bytes.fromhex(content).decode())
return bytes().fromhex(response)
def read_response_frame(req_bytes_frame, res_data):
header = "D00000FFFF03000C000000" # 读的响应头
nums = req_bytes_frame.hex()[38:42] # 获取客户端想要读的寄存器个数
act_nums_hex = nums[2:] + nums[:2] # 涉及大端序和小端序,需要转一下
act_nums = int(act_nums_hex, 16) # 得到实际数量
res_data_hex = ''.join([hex(ord(c))[2:].zfill(2) for c in res_data]) # 将要返回的数据转成16进制字符串
response = header + res_data_hex + '0'*(act_nums*2*2-len(res_data_hex)) # 根据请求数量返回对应的内容
return bytes().fromhex(response)
try:
while True:
# 接收客户端请求
request = connection.recv(1024)
print("001:", request)
if request:
flag = request_verdict(request)
if flag: # 响应写
response = write_response_frame(request)
print("002:",response)
else: # 响应读
response = read_response_frame(request, "start")
print("003:",response)
connection.sendall(response)
finally:
# 清理连接
connection.close()
三、关于MC协议的整体实现
通过“二”,我们实现了一个基于MC协议的保持寄存器的读写服务器,但并没有像pymodbus这种现成模块那样完整实现,这里探讨一下还可以做的事。
1、SLMP 3E帧实现步骤
实现SLMP(Seamless Message Protocol) 3E帧协议涉及到网络通信、数据处理、错误处理等多个步骤。以下是实现SLMP 3E帧的一般步骤:
(1)建立TCP连接:
在服务端,监听指定端口(通常是4999)。
在客户端,连接到服务端的IP地址和端口。
(2)接收请求:
服务端接收客户端发送的SLMP 3E帧请求。
解析SLMP 3E帧,获取命令码、子命令码、数据等信息。
(3)处理请求:
根据SLMP 3E帧中的命令码和数据,执行相应的操作,如读取、写入、控制等。
处理请求可能涉及到对PLC或其他设备进行读写操作,具体实现根据设备和应用需求而定。
(4)生成响应:
根据请求处理的结果,生成SLMP 3E帧的响应数据。
设置响应帧的命令码、子命令码、数据等。
(5)发送响应:
将生成的SLMP 3E帧响应数据发送回客户端。
(6)错误处理:
在处理请求和生成响应的过程中,可能出现各种错误,如无效命令、数据不合法等。
针对不同的错误情况,生成相应的错误响应帧。
(7)关闭连接:
当通信结束或出现错误时,关闭TCP连接,释放资源。
请注意,SLMP 3E帧协议具体的实现步骤和数据格式可能因具体设备和应用而有所不同。在实际开发中,需要参考设备文档和SLMP协议规范来进行具体的实现。
2、SLMP协议规范
查阅三菱PLC关于SLMP的文档!
SLMP的详细规范通常由设备厂商提供,以便开发者能够正确地使用该协议与设备进行通信。规范文件通常包含SLMP协议的命令码、数据格式、通信流程、错误处理等方面的详细信息。
3、为什么会有SLMP协议
SLMP(Seamless Message Protocol)协议是为了在自动化领域(例如工业自动化、制造业、机器人技术等)中实现设备之间的无缝通信而设计的。它提供了一种标准化的通信协议,使不同厂商、不同类型的设备能够在同一个网络上进行通信,实现设备的互联互通。
在现代工业自动化系统中,通常涉及到各种各样的设备,这些设备由不同的厂商制造,可能使用不同的通信协议和数据格式。为了实现这些设备之间的互联互通,需要一种通用的、标准化的通信协议。SLMP就是为了满足这种需求而被开发出来的。
SLMP协议的设计目标包括:
标准化通信: 提供一种通用的通信协议,使得不同厂商生产的设备可以在同一个网络上进行通信。
灵活性: 允许不同类型的数据(例如状态信息、控制命令等)通过同一个协议进行传输。
高效性: 设计为高效的通信协议,以满足工业自动化系统对实时性和响应速度的要求。
易用性: 设计为易于实现和配置,使得工程师能够方便地将SLMP协议集成到他们的设备和系统中。
综上所述,SLMP协议的存在使得不同类型的自动化设备能够方便地相互通信,实现了工业自动化系统的互操作性和灵活性。
4、我是否可以自定义一套协议
你完全可以自定义一套通信协议,以满足特定需求或者应用场景。自定义通信协议通常涉及到以下几个步骤:
确定通信需求: 首先,你需要明确通信双方之间需要传输哪些数据,以及数据的格式和类型。确定通信的数据结构、命令类型、错误处理机制等。
选择传输方式: 确定通信采用的传输方式,可以是基于串口的通信(例如RS-232、RS-485)、基于网络的通信(例如TCP/IP、UDP)、无线通信(例如Wi-Fi、蓝牙)等。
制定协议规范: 定义协议的数据帧格式,包括帧头、帧尾、校验码等信息。确保通信双方遵循相同的协议规范。
实现协议解析和封装: 在通信的发送端和接收端分别实现协议的封装和解析逻辑。封装就是将待发送的数据按照协议格式组织成数据帧,解析则是在接收端将接收到的数据帧按照协议格式解析成可处理的数据。
添加错误处理和安全性: 考虑数据传输中可能出现的错误情况,设计相应的错误处理机制,确保数据的完整性和可靠性。如果通信需要保密性,可以考虑加密通信数据。
测试和验证: 在实际环境中进行测试和验证,确保自定义协议能够正常工作,并且满足通信需求。
请注意,自定义协议需要考虑通信的稳定性、可靠性和安全性。在设计过程中,建议参考现有通信协议的设计经验,以及相关领域的最佳实践。同时,文档化自定义协议的规范,以便未来的维护和扩展。