python实现FINS协议的UDP服务端是一件稍微麻烦点的事情。它不像modbusTCP那样,可以使用现成的pymodbus模块去实现。但是,我们可以根据协议帧进行组包,自己去实现帧的格式,而这一切可以基于socket模块。本文基于原先 FINS协议的TCP服务端的文章进行修改。
FINS(Factory Interface Network Service)是欧姆龙(Omron)PLC(可编程逻辑控制器)的通信协议。FINS支持两种主要的传输方式:FINS over TCP(FINS_TCP)和FINS over UDP(FINS_UDP)。
下面是它们之间的主要区别:
(1)传输层协议:
FINS_TCP: 使用TCP(Transmission Control Protocol)作为传输层协议。TCP是面向连接的、可靠的协议,确保数据的可靠性和顺序传输。
FINS_UDP: 使用UDP(User Datagram Protocol)作为传输层协议。UDP是面向无连接的协议,它不保证数据的可靠性和顺序传输,但通常具有更低的延迟。
(2)连接方式:
FINS_TCP: 建立连接后进行通信,类似于常见的TCP通信方式。
FINS_UDP: 无连接,每个数据包独立发送,适用于对实时性要求较高的应用场景。
(3)可靠性和顺序性:
FINS_TCP: 提供TCP的可靠性和顺序性,适用于对数据完整性和传输顺序有要求的应用。
FINS_UDP: 不提供可靠性和顺序性的保证,适用于对实时性要求较高,可以容忍一些数据丢失的场景。
(4)用途:
FINS_TCP: 适用于对数据完整性和传输顺序要求较高的应用,例如需要确保每个数据包都被正确接收的场景。
FINS_UDP: 适用于实时性要求较高,可以容忍一些数据丢失的应用,例如对于实时控制要求较高的系统。
选择使用哪种方式取决于具体的应用场景和对通信特性的要求。
(1)握手包
FINS_TCP有握手包,而FINS_UDP没有握手包。
(2)请求头
FINS_TCP的请求头是FINS,而FINS_UDP没有请求头。
(3)其他部分
一致。相关文档请查阅我之前写的“python实现FINS协议的TCP服务端(篇一)”等文章,现对比如下:
FINS_TCP
46 49 4E 53 00 00 00 1A 00 00 00 02 00 00 00 00 80 00 02 00 01 00 00 01 00 3D 01 01 82 00 64 00 00 01
FINS_UDP
80 00 02 00 FF 00 00 05 00 64 01 01 82 00 64 00 00 01
def recognition_frame(req_bytes_frame, Trigger):
get_frame = req_bytes_frame.hex().upper()
print("设备请求:", get_frame)
SRC_value = get_frame[22:24] # 判断读写,01为读,02为写
Area_value = get_frame[24:26] # 判断寄存器区域,82为保持寄存器
# print(SRC_value)
# print(Area_value)
if SRC_value == "01":
if Area_value == "82":
response_1 = "00000000000000000000010100000001" # Trigger位为True
response_0 = "00000000000000000000010100000000" # Trigger位为False
if Trigger == True:
return bytes().fromhex(response_1)
else:
return bytes().fromhex(response_0)
else:
raise ValueError("Area_value is error!")
elif SRC_value == "02":
if Area_value == "82":
print("***************************************")
# 写保持寄存器的响应
print("扫码器写入的结果数据:", bytes().fromhex(get_frame))
response = "0000000000000000000001020000"
return bytes().fromhex(response)
else:
raise ValueError("Area_value is error!")
else:
raise ValueError("SRC_value is error!")
这个函数是针对读取或写入保持寄存器的请求,以下是对函数的解释:
(1)输入参数:
req_bytes_frame
: 一个字节序列,表示设备请求的原始帧。Trigger
: 一个布尔值,似乎用于确定设备是否处于触发状态。
(2)函数操作:
将输入的字节序列转换为十六进制表示,并转换为大写形式。从帧中提取 SRC(源)和 Area(寄存器区域)的值。
根据 SRC 和 Area 的值执行相应的逻辑:
如果 SRC 是 "01",表示读取请求,继续判断 Area 是否为 "82"(保持寄存器)。
如果不是 "82",抛出 ValueError
异常,表示 Area 值错误。
如果 SRC 是 "02",表示写入请求,同样判断 Area 是否为 "82"。
如果是,打印扫码器写入的结果数据,构建写保持寄存器的响应帧。如果不是 "82",同样抛出 ValueError
异常。
如果 SRC 不是 "01" 或 "02",抛出 ValueError
异常,表示 SRC 值错误。
如果是,根据 Trigger 的值构建响应帧,其中 Trigger 为 True 时触发位为 1,否则为 0。
(3)返回值:
如果是读取请求,返回构建的响应帧(True 触发位或 False 触发位)。
如果是写入请求,返回构建的写保持寄存器的响应帧。
(4)异常处理:
如果 Area 值不是 "82",或者 SRC 值不是 "01" 或 "02",都会抛出 ValueError
异常,提示相应的错误信息。
总体来说,该函数是为了处理设备的读取和写入请求,并根据请求类型和条件构建相应的响应帧。
if __name__ == "__main__":
DM_start = 1000
# 创建FINS服务端
# 创建一个TCP/IP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定套接字到特定地址和端口
server_address = ('192.168.1.188', 9600) # 服务器地址和端口
server_socket.bind(server_address)
try:
num = 0 # 触发标志
Trigger_rec = 0 # Trigger置为True时,对应变为1,表示触发一次
response = "" # 响应
while True:
# 接收客户端请求
request, client_address = server_socket.recvfrom(1024)
if request:
# 如果收到的不是请求头
if "800002" in request.hex():
# print(request.hex()[22:24])
# 实现扫码触发
if request.hex()[22:24] == "01": # 判断读写,01为读触发指令,02为写触发结果
if Trigger_rec != 2:
Trigger_rec += 1
if Trigger_rec == 1:
response = recognition_frame(request, Trigger=False) # 先清空触发信号
server_socket.sendto(response, client_address)
elif Trigger_rec == 2: # 复位Trigger信号
response = recognition_frame(request, Trigger=True) # 再置位触发信号
server_socket.sendto(response, client_address)
# 实现结果接收
elif request.hex()[22:24] == "02":
print(request.hex())
# print("---------------", int(request.hex()[26:30], 16))
if int(request.hex()[26:30], 16) == DM_start + 4:
if any(c != '0' for c in request.hex()[36:]): # 不全为0
print("扫码结果:", request.hex()[36:])
num += 1
Trigger_rec = 0
else:
response = recognition_frame(request, Trigger=True)
server_socket.sendto(response, client_address)
print("还没有收到结果,继续等待扫码结果!")
else:
response = recognition_frame(request, Trigger=True)
server_socket.sendto(response, client_address)
# 处理其他请求
else:
response = recognition_frame(request, Trigger=True)
server_socket.sendto(response, client_address)
print("服务响应:", response.hex()) # 可以响应为空
if num == 1:
assert bytes().fromhex(request.hex()[36:]).decode() != "NG", "实际扫码结果为:{},不符合预期".format(bytes().fromhex(request.hex()[36:]).decode())
break
request = False
finally:
# 清理连接
server_socket.close()
这段代码是一个服务端程序,是套接字UDP服务端。让我们分析主要的部分:
(1)服务端设置:
创建一个UDP套接字(socket.AF_INET, socket.SOCK_DGRAM
)用于与客户端通信。绑定套接字到特定的地址和端口(('192.168.1.188', 9600)
)。
(2)主循环:
在一个无限循环中,服务端等待从客户端接收请求。对于收到的请求,根据请求的内容进行不同的处理。
(3)请求处理:
判断请求是否包含特定的头部标识 "800002"。如果是读触发指令("01"
),则处理触发逻辑。如果是写触发结果指令("02"
),则处理扫码结果。如果是其他请求,统一进行处理。
(4)触发逻辑:
根据触发标志 (Trigger_rec
) 的状态,对触发指令进行相应的处理。首先清空触发信号,然后再置位触发信号。
(5)扫码结果处理:
对于写触发结果指令,检查是否收到了预期的扫码结果。如果扫码结果不全为0,则认为收到有效的扫码结果,增加计数。如果结果为0,继续等待扫码结果。
(6)响应处理:
根据处理后的结果,调用 recognition_frame
函数构建响应帧。将响应发送给客户端。
(7)断言和终止条件:
当计数 num
达到1时,使用断言检查扫码结果是否符合预期,并终止程序。
(8)清理:
在 finally
块中关闭套接字,确保程序退出时资源被释放。
总体来说,这个服务端程序用于处理来自客户端的请求,其中包括了特定的触发指令和扫码结果指令。在处理这些指令时,它通过 recognition_frame
函数构建响应,并对结果进行相应的处理。