python实现FINS协议的TCP服务端是一件稍微麻烦点的事情。它不像modbusTCP那样,可以使用现成的pymodbus模块去实现。但是,我们可以根据协议帧进行组包,自己去实现帧的格式,而这一切可以基于socket模块。本文为第二篇。
三、定制服务器
1、对比读写保持寄存器的请求
通过对比请求,来判断读写请求,以对应不同的响应。
# 以写为例,赋予读的对比
Header:46 49 4E 53 固定值
Length:00 00 02 30 包的长度 -- 根据实际情况改变
Command:00 00 00 02 固定值
Error Code:00 00 00 00 固定值
ICF:固定值80
RSV:固定值00
GCT:固定值02
DNA:目标网络号00
DA1:目标节点号01
DA2:目标单元号00
SNA:源网络号00
SA1:源节点号01
SA2:目标单元号00
SID:源网络号 3E(也可能是其他) -- 根据获取数量从00 开始变FF后再为00,可忽略
MRC:01
SRC:02 -- 读的时候为01,写的时候为02
Area:82 -- 保持寄存器地址对应82
Address:03 EC 00 -- 实际地址+位地址
length:01 0B -- 写的长度可变!读的长度也可变?
value:...
2、编写程序注意事项
由于一个请求是以2个请求或多个请求进行的,因此在编写服务器的时候,确实加了一些小困难,简单解释如下:
(1)先发请求头
(2)再发指令
但对于响应来说,却是一个完整的包:
(1)请求头与指令一起发
四、程序
1、代码
import socket
def recognition_frame(req_bytes_frame, Trigger):
get_frame = req_bytes_frame.hex().upper()
print("设备请求:", get_frame)
# 判断是否为握手命令
if get_frame == "46494E530000000C000000000000000000000000":
response = "46494E530000001000000000000000000000000100000001"
return bytes().fromhex(response)
# 判断是否为其他FINS命令的请求头,只要是请求头都只反应空
elif "46494E53" in get_frame: # 收到FINS的请求头 == "46494E530000001A0000000200000000" 或 其他请求头
print("只收到请求头,响应将为空!")
response = ""
return bytes().fromhex(response)
else:
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 = "46494E5300000018000000000000000000000000000000000000010100000001" # Trigger位为True
response_0 = "46494E5300000018000000000000000000000000000000000000010100000000" # 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 = "46494E530000001600000000000000000000000000000000000001020000"
return bytes().fromhex(response)
else:
raise ValueError("Area_value is error!")
else:
raise ValueError("SRC_value is error!")
if __name__ == "__main__":
DM_start = 1000
# 创建FINS服务端
# 创建一个TCP/IP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定套接字到特定地址和端口
server_address = ('192.168.1.188', 9600) # 服务器地址和端口
server_socket.bind(server_address)
# 监听连接
server_socket.listen(1)
print('等待客户端连接...')
connection, client_address = server_socket.accept()
print('客户端已连接:', client_address)
try:
num = 0 # 触发标志
Trigger_rec = 0 # Trigger置为True时,对应变为1,表示触发一次
response = "" # 响应
while True:
# 接收客户端请求
request = connection.recv(1024)
if request:
# 如果收到的不是请求头
if "8000020001000001" 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) # 先清空触发信号
connection.sendall(response)
elif Trigger_rec == 2: # 复位Trigger信号
response = recognition_frame(request, Trigger=True) # 再置位触发信号
connection.sendall(response)
# 实现结果接收
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)
connection.sendall(response)
print("还没有收到结果,继续等待扫码结果!")
else:
response = recognition_frame(request, Trigger=True)
connection.sendall(response)
# 处理其他请求
else:
response = recognition_frame(request, Trigger=True)
connection.sendall(response)
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:
# 清理连接
connection.close()
2、解释
这段代码是一个使用FINS协议的服务器端程序。它监听指定地址和端口,接收客户端请求,根据请求内容作出相应的响应。以下是对主要部分的解释:
(1)recognition_frame 函数:
接收一个 req_bytes_frame 参数,这是客户端请求的字节表示。
get_frame 变量将字节表示转换为大写的十六进制字符串。
通过一系列条件判断,判断请求类型并返回相应的响应。
(2)if __name__ == "__main__": 部分:
初始化一些变量,如 DM_start、num、Trigger_rec 和 response。
创建一个 TCP 服务器套接字,绑定地址和端口,然后监听连接。
在一个无限循环中,接收客户端请求,判断请求类型并发送相应的响应。
扫码触发和结果接收部分:
如果接收到的请求的十六进制表示包含特定的模式("8000020001000001"),则执行扫码触发或结果接收的逻辑。
①触发时,通过 recognition_frame 函数发送相应的响应。
②结果接收时,判断是否是指定寄存器的写入,如果写入的内容不全为零,则认为收到了扫码结果。
recognition_frame 函数中的异常处理:
如果在解析请求时发现不符合预期的情况,抛出 ValueError 异常。
finally 块:
在程序结束时关闭连接。
总体来说,这是一个基于 FINS 协议的服务器程序,主要用于处理扫码触发和结果接收,并通过 FINS 协议进行通信。