VWED_server/tests/test_melsec_module.py

350 lines
13 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Melsec 模拟设备服务器
提供模拟的 Melsec 设备用于测试在线脚本中的 Melsec 内置函数
"""
import threading
import socket
import struct
import time
print("Melsec 模拟设备服务器")
class MockMelsecServer:
"""模拟 Melsec PLC 服务器用于测试"""
def __init__(self, host='127.0.0.1', port=5007):
self.host = host
self.port = port
self.server_socket = None
self.running = False
self.thread = None
# 模拟数据存储 (按照Melsec地址格式)
self.memory_areas = {
'M': { # M 位地址
100: True,
101: False,
102: True,
103: False,
200: False,
201: True
},
'D': { # D 字地址
100: 1234,
101: 5678,
200: 9999,
300: 42
},
'D_STRING': { # D 字地址存储的字符串
500: "HELLO",
502: "WORLD",
600: "TEST",
602: "DATA"
}
}
def start(self):
"""启动模拟服务器"""
try:
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(5)
self.running = True
self.thread = threading.Thread(target=self._server_loop)
self.thread.daemon = True
self.thread.start()
print(f"🟢 模拟 Melsec 服务器已启动:{self.host}:{self.port}")
print(f"📊 模拟数据:")
print(f" - M100=True, M101=False, M102=True, M200=False")
print(f" - D100=1234, D101=5678, D200=9999, D300=42")
print(f" - D500-501='HELLO', D600-601='TEST'")
time.sleep(0.1) # 等待服务器完全启动
except Exception as e:
print(f"❌ 启动模拟 Melsec 服务器失败: {str(e)}")
raise
def stop(self):
"""停止模拟服务器"""
self.running = False
if self.server_socket:
self.server_socket.close()
if self.thread:
self.thread.join(timeout=1.0)
print("🔴 模拟 Melsec 服务器已停止")
def _server_loop(self):
"""服务器主循环"""
while self.running:
try:
client_socket, addr = self.server_socket.accept()
print(f"📞 客户端连接: {addr}")
# 处理客户端请求
self._handle_client(client_socket)
except Exception as e:
if self.running:
print(f"❌ 服务器循环错误: {str(e)}")
break
def _handle_client(self, client_socket):
"""处理客户端请求"""
try:
# 接收请求
data = client_socket.recv(1024)
if not data:
return
print(f"📨 收到请求数据: {data.hex()}")
# 解析并响应
response = self._process_melsec_request(data)
if response:
client_socket.send(response)
print(f"📤 发送响应数据: {response.hex()}")
except Exception as e:
print(f"❌ 处理客户端请求失败: {str(e)}")
finally:
client_socket.close()
def _process_melsec_request(self, request_data):
"""处理 Melsec 请求并生成响应"""
try:
# 简化的 MC 协议解析
if len(request_data) < 11:
return None
# MC协议格式 (Binary模式)
# 子头部: 2字节 (50 00)
# 网络号: 1字节 (00)
# PLC号: 1字节 (FF)
# IO号: 2字节 (FF 03)
# 站号: 1字节 (00)
# 请求数据长度: 2字节
# 请求数据: N字节
# 跳过头部,获取命令码
cmd_code = struct.unpack('<H', request_data[9:11])[0]
print(f"🔍 解析命令: CMD=0x{cmd_code:04X}")
# 根据命令码处理
if cmd_code == 0x0401: # 位单位批量读取
return self._handle_bit_read(request_data)
elif cmd_code == 0x1401: # 字单位批量读取
return self._handle_word_read(request_data)
elif cmd_code == 0x1402: # 字单位批量写入
return self._handle_word_write(request_data)
elif cmd_code == 0x0402: # 位单位批量写入
return self._handle_bit_write(request_data)
else:
print(f"❓ 未知命令码: 0x{cmd_code:04X}")
return self._create_error_response()
except Exception as e:
print(f"❌ 处理 Melsec 请求失败: {str(e)}")
return self._create_error_response()
def _handle_bit_read(self, request_data):
"""处理位读取"""
try:
# 解析地址和点数
device_code = request_data[11] # 设备代码
address = struct.unpack('<I', request_data[12:15] + b'\x00')[0] # 地址 (3字节)
points = struct.unpack('<H', request_data[15:17])[0] # 点数
print(f"📖 位读取: 设备=0x{device_code:02X}, 地址={address}, 点数={points}")
# M设备 (0x90)
if device_code == 0x90:
result_data = b''
for i in range(points):
bit_addr = address + i
bit_value = self.memory_areas['M'].get(bit_addr, False)
result_data += struct.pack('B', 1 if bit_value else 0)
return self._create_success_response(result_data)
return self._create_error_response()
except Exception as e:
print(f"❌ 位读取失败: {str(e)}")
return self._create_error_response()
def _handle_word_read(self, request_data):
"""处理字读取"""
try:
# 解析地址和点数
device_code = request_data[11] # 设备代码
address = struct.unpack('<I', request_data[12:15] + b'\x00')[0] # 地址 (3字节)
points = struct.unpack('<H', request_data[15:17])[0] # 点数
print(f"📖 字读取: 设备=0x{device_code:02X}, 地址={address}, 点数={points}")
# D设备 (0xA8)
if device_code == 0xA8:
result_data = b''
for i in range(points):
word_addr = address + i
word_value = self.memory_areas['D'].get(word_addr, 0)
result_data += struct.pack('<H', word_value)
return self._create_success_response(result_data)
return self._create_error_response()
except Exception as e:
print(f"❌ 字读取失败: {str(e)}")
return self._create_error_response()
def _handle_bit_write(self, request_data):
"""处理位写入"""
try:
# 解析地址和点数
device_code = request_data[11] # 设备代码
address = struct.unpack('<I', request_data[12:15] + b'\x00')[0] # 地址 (3字节)
points = struct.unpack('<H', request_data[15:17])[0] # 点数
print(f"✏️ 位写入: 设备=0x{device_code:02X}, 地址={address}, 点数={points}")
# M设备 (0x90)
if device_code == 0x90:
data_start = 17
for i in range(points):
bit_addr = address + i
bit_value = request_data[data_start + i] != 0
self.memory_areas['M'][bit_addr] = bit_value
print(f" M{bit_addr} = {bit_value}")
return self._create_success_response(b'')
return self._create_error_response()
except Exception as e:
print(f"❌ 位写入失败: {str(e)}")
return self._create_error_response()
def _handle_word_write(self, request_data):
"""处理字写入"""
try:
# 解析地址和点数
device_code = request_data[11] # 设备代码
address = struct.unpack('<I', request_data[12:15] + b'\x00')[0] # 地址 (3字节)
points = struct.unpack('<H', request_data[15:17])[0] # 点数
print(f"✏️ 字写入: 设备=0x{device_code:02X}, 地址={address}, 点数={points}")
# D设备 (0xA8)
if device_code == 0xA8:
data_start = 17
for i in range(points):
word_addr = address + i
word_value = struct.unpack('<H', request_data[data_start + i*2:data_start + i*2 + 2])[0]
self.memory_areas['D'][word_addr] = word_value
print(f" D{word_addr} = {word_value}")
return self._create_success_response(b'')
return self._create_error_response()
except Exception as e:
print(f"❌ 字写入失败: {str(e)}")
return self._create_error_response()
def _create_success_response(self, data=b''):
"""创建成功响应"""
# MC协议响应格式
header = b'\xD0\x00' # 子头部
header += b'\x00' # 网络号
header += b'\xFF' # PLC号
header += b'\xFF\x03' # IO号
header += b'\x00' # 站号
# 响应数据长度
response_length = 2 + len(data) # 完成码2字节 + 数据
header += struct.pack('<H', response_length)
# 完成码 (成功)
response = header + b'\x00\x00' + data
return response
def _create_error_response(self):
"""创建错误响应"""
# MC协议错误响应
header = b'\xD0\x00' # 子头部
header += b'\x00' # 网络号
header += b'\xFF' # PLC号
header += b'\xFF\x03' # IO号
header += b'\x00' # 站号
header += struct.pack('<H', 2) # 响应长度
# 错误码
response = header + b'\x01\xC0' # 通用错误
return response
def start_mock_melsec_server():
"""启动模拟 Melsec 服务器"""
server = MockMelsecServer('127.0.0.1', 5007)
server.start()
return server
if __name__ == "__main__":
print("🚀 启动 Melsec 模拟设备服务器")
print("=" * 50)
try:
# 启动模拟服务器
server = start_mock_melsec_server()
print("\n📋 测试说明:")
print(" 可以在在线脚本中使用以下代码测试:")
print("")
print(" # 测试读取 boolean")
print(" result = VWED.melsec.read_boolean('127.0.0.1', 5007, 'M100')")
print(" print(f'读取 M100 结果: {result}') # 期望: True")
print("")
print(" # 测试读取 number")
print(" result = VWED.melsec.read_number('127.0.0.1', 5007, 'D100')")
print(" print(f'读取 D100 结果: {result}') # 期望: 1234")
print("")
print(" # 测试写入 boolean")
print(" success = VWED.melsec.write_boolean('127.0.0.1', 5007, 'M200', True)")
print(" print(f'写入 M200 结果: {success}') # 期望: True")
print("")
print(" # 测试写入 number")
print(" success = VWED.melsec.write_number('127.0.0.1', 5007, 'D200', 8888)")
print(" print(f'写入 D200 结果: {success}') # 期望: True")
print("")
print(" # 测试读取 string")
print(" result = VWED.melsec.read_string('127.0.0.1', 5007, 'D500', 10)")
print(" print(f'读取 D500 字符串结果: {result}') # 期望: 'HELLO'")
print("")
print("💡 按 Ctrl+C 停止服务器")
# 保持服务器运行
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n⏹️ 收到停止信号")
except Exception as e:
print(f"\n❌ 服务器运行失败: {str(e)}")
finally:
if 'server' in locals():
server.stop()
print("👋 再见!")