|  |  | 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() |