You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
9.1 KiB
Python

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import socket
import struct
import threading
import time
import logging
import random
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class Box1Simulator:
START_FLAG = b'\xF3\x12'
END_FLAG = b'\xF3\x14'
HEADER_SIZE = 10
# 命令类型
CMD_READ_AI = 1
CMD_WRITE_AO = 2
CMD_READ_DELTAT = 3
RESP_READ_AI = 11
RESP_WRITE_AO = 12
RESP_READ_DELTAT = 13
def __init__(self, host, port):
self.host = host
self.port = port
self.server_socket = None
self.stop_event = threading.Event()
# 模拟设备状态
self.ai_values = [0.0] * 8
self.di_states = [0] * 16
self.ao_values = [0.0] * 36
self.deltat_values = [0] * 16
# 初始化模拟值
self.reset_simulated_values()
# 启动服务
threading.Thread(target=self.run_server, daemon=True).start()
logging.info(f"Box1 simulator started on {host}:{port}")
def reset_simulated_values(self):
"""重置模拟值到初始状态"""
# 模拟AI值 (4-20mA范围单位A)
self.ai_values = [random.uniform(0.004, 0.020) for _ in range(8)]
# 模拟DI状态 (随机0或1)
self.di_states = [random.randint(0, 1) for _ in range(16)]
# 模拟AO值 (初始全0)
self.ao_values = [0.0] * 36
# 模拟DeltaT值 (时间戳差)
self.deltat_values = [random.randint(1000, 5000) for _ in range(16)]
logging.info("Simulated values reset")
def run_server(self):
"""运行TCP服务器"""
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)
while not self.stop_event.is_set():
try:
client_sock, addr = self.server_socket.accept()
logging.info(f"New connection from {addr}")
threading.Thread(target=self.handle_client, args=(client_sock,), daemon=True).start()
except socket.timeout:
pass
except Exception as e:
logging.error(f"Server error: {e}")
break
def handle_client(self, client_sock):
"""处理客户端连接"""
buffer = b''
try:
while not self.stop_event.is_set():
data = client_sock.recv(4096)
if not data:
break
buffer += data
while True:
start_pos = buffer.find(self.START_FLAG)
if start_pos == -1:
break
if start_pos > 0:
buffer = buffer[start_pos:]
if len(buffer) < len(self.START_FLAG) + self.HEADER_SIZE:
break
header = buffer[len(self.START_FLAG):len(self.START_FLAG) + self.HEADER_SIZE]
try:
cmd_type, data_size, packet_num = struct.unpack('>H I I', header)
except struct.error:
logging.warning("Invalid header format")
buffer = buffer[len(self.START_FLAG):]
continue
frame_len = len(self.START_FLAG) + self.HEADER_SIZE + data_size + len(self.END_FLAG)
if len(buffer) < frame_len:
break
full_frame = buffer[:frame_len]
buffer = buffer[frame_len:]
if full_frame[-len(self.END_FLAG):] != self.END_FLAG:
logging.warning("Invalid end flag")
continue
body = full_frame[len(self.START_FLAG) + self.HEADER_SIZE:-len(self.END_FLAG)]
# 处理客户端命令
response = self.process_command(cmd_type, body, packet_num)
if response:
client_sock.sendall(response)
except Exception as e:
logging.error(f"Client handling error: {e}")
finally:
client_sock.close()
logging.info("Client disconnected")
def process_command(self, cmd_type, body, packet_num):
"""处理客户端命令并生成响应"""
try:
# 读取AI命令
if cmd_type == self.CMD_READ_AI:
return self.create_ai_response(packet_num)
# 写入AO命令
elif cmd_type == self.CMD_WRITE_AO:
if len(body) != 288: # 36 doubles * 8 bytes
logging.error(f"Invalid AO data length: {len(body)}")
return None
# 修改:使用小端字节序解包数据
ao_data = struct.unpack('<' + 'd'*36, body)
self.ao_values = list(ao_data)
logging.info(f"接收到的AO: {ao_data}... (total {len(ao_data)} values)")
return self.create_ao_response(True, packet_num)
# 读取DeltaT命令
elif cmd_type == self.CMD_READ_DELTAT:
# 更新一些DeltaT值以模拟变化
for i in range(4):
self.deltat_values[i] += random.randint(1, 10)
print("deltatT为", self.deltat_values)
return self.create_deltat_response(packet_num)
else:
logging.warning(f"Unknown command type: {cmd_type}")
return None
except Exception as e:
logging.error(f"Command processing error: {e}")
return None
def create_ai_response(self, packet_num):
"""创建AI响应数据包"""
# 模拟值在4-20mA范围内轻微波动单位A
for i in range(8):
self.ai_values[i] += random.uniform(-0.001, 0.001)
# 限制在4-20mA范围内 (0.004-0.020A)
self.ai_values[i] = max(0.004, min(0.020, self.ai_values[i]))
# print(self.ai_values)
# 随机翻转一些DI状态
for i in range(4):
if random.random() < 0.2: # 20%几率翻转
self.di_states[i] = 1 - self.di_states[i]
# print(self.di_states, 1111)
# 修改生成500组AI/DI数据
full_body = b''
for i in range(8):
for _ in range(500):
# 修复:移除解包操作符(*),直接传入浮点数
ai_bytes = struct.pack('<d', self.ai_values[i])
# 打包DI状态为一个64位整数
# 添加一组数据到响应体
full_body += ai_bytes
for _ in range(500):
di_state_int = 0
for i, state in enumerate(self.di_states):
di_state_int |= (state << i)
di_bytes = struct.pack('<d', di_state_int)
# print(di_bytes, di_state_int)
full_body += di_bytes
# print(len(full_body))
# 使用logging记录发送的AI数据
# logging.info(f"发送的AI为: AI={self.ai_values}, DI={self.di_states} (共500组)")
return self.create_packet(self.RESP_READ_AI, full_body, packet_num)
def create_ao_response(self, success, packet_num):
"""创建AO写入响应数据包"""
# 使用0.0表示成功1.0表示失败
result = 0.0 if success else 1.0
# 修改:使用小端字节序
body = struct.pack('<d', result)
return self.create_packet(self.RESP_WRITE_AO, body, packet_num)
def create_deltat_response(self, packet_num):
"""创建DeltaT响应数据包"""
# 修改使用小端字节序打包16个64位无符号整数
body = struct.pack('<' + '16Q', *self.deltat_values)
# 使用logging记录发送的DeltaT数据
logging.info(f"发送的DeltaT为: {self.deltat_values}")
return self.create_packet(self.RESP_READ_DELTAT, body, packet_num)
def create_packet(self, cmd_type, body, packet_num):
"""创建完整的数据包"""
data_size = len(body)
header = struct.pack('>H I I', cmd_type, data_size, packet_num)
return self.START_FLAG + header + body + self.END_FLAG
def shutdown(self):
"""停止服务器"""
self.stop_event.set()
if self.server_socket:
self.server_socket.close()
logging.info("Box1 simulator stopped")
if __name__ == "__main__":
# 创建模拟器实例
simulator = Box1Simulator("localhost", 8000)
try:
# 运行模拟器直到手动停止
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\nStopping simulator...")
simulator.shutdown()