443 lines
14 KiB
Python
443 lines
14 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
SUW Client - Python翻译版本
|
||
原文件: SUWClient.rb
|
||
用途: TCP客户端,与服务器通信
|
||
"""
|
||
|
||
import socket
|
||
import json
|
||
import struct
|
||
import threading
|
||
import time
|
||
from typing import List, Dict, Any, Optional
|
||
|
||
# 常量定义
|
||
TCP_SERVER_PORT = 7999
|
||
OP_CMD_REQ_GETCMDS = 0x01
|
||
OP_CMD_REQ_SETCMD = 0x03
|
||
OP_CMD_RES_GETCMDS = 0x02
|
||
OP_CMD_RES_SETCMD = 0x04
|
||
|
||
|
||
class SUWClient:
|
||
"""SUWood 客户端类"""
|
||
|
||
def __init__(self, host="127.0.0.1", port=TCP_SERVER_PORT):
|
||
self.host = host
|
||
self.port = port
|
||
self.sock = None
|
||
self.seqno = 0
|
||
self.connect()
|
||
|
||
def connect(self):
|
||
"""连接到服务器"""
|
||
try:
|
||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||
self.sock.connect((self.host, self.port))
|
||
print(f"✅ 连接到服务器 {self.host}:{self.port}")
|
||
except Exception as e:
|
||
print(f"❌ 连接失败: {e}")
|
||
self.sock = None
|
||
|
||
def reconnect(self):
|
||
"""重新连接"""
|
||
if self.sock:
|
||
try:
|
||
self.sock.close()
|
||
except:
|
||
pass
|
||
|
||
self.connect()
|
||
|
||
def send_msg(self, cmd: int, msg: str):
|
||
"""发送消息"""
|
||
if not self.sock:
|
||
print("❌ 未连接到服务器")
|
||
return False
|
||
|
||
try:
|
||
opcode = (cmd & 0xffff) | 0x01010000
|
||
self.seqno += 1
|
||
|
||
# 打包消息:[消息长度, 操作码, 序列号, 保留字段]
|
||
msg_bytes = msg.encode('utf-8')
|
||
header = struct.pack('iiii', len(msg_bytes), opcode, self.seqno, 0)
|
||
|
||
full_msg = header + msg_bytes
|
||
self.sock.send(full_msg)
|
||
return True
|
||
|
||
except Exception as e:
|
||
print(f"❌ 发送消息失败: {e}")
|
||
return False
|
||
|
||
def recv_msg(self) -> Optional[str]:
|
||
"""接收消息 - 修复编码问题"""
|
||
if not self.sock:
|
||
print("❌ 未连接到服务器")
|
||
return None
|
||
|
||
try:
|
||
# 接收头部(16字节)
|
||
header = self.sock.recv(16)
|
||
if len(header) < 16:
|
||
return None
|
||
|
||
# 解包获取消息长度
|
||
msg_len = struct.unpack('iiii', header)[0]
|
||
|
||
# 接收消息内容
|
||
msg = b""
|
||
to_recv_len = msg_len
|
||
|
||
while to_recv_len > 0:
|
||
chunk = self.sock.recv(to_recv_len)
|
||
if not chunk:
|
||
break
|
||
msg += chunk
|
||
to_recv_len = msg_len - len(msg)
|
||
|
||
# 【修复】改进编码处理
|
||
if not msg:
|
||
return None
|
||
|
||
# 首先尝试UTF-8
|
||
try:
|
||
return msg.decode('utf-8')
|
||
except UnicodeDecodeError:
|
||
# 尝试其他编码
|
||
encodings = ['latin1', 'gbk', 'gb2312', 'cp1252', 'iso-8859-1']
|
||
for encoding in encodings:
|
||
try:
|
||
decoded = msg.decode(encoding)
|
||
if encoding != 'utf-8':
|
||
print(f"⚠️ 使用 {encoding} 编码解码成功")
|
||
return decoded
|
||
except UnicodeDecodeError:
|
||
continue
|
||
|
||
# 如果所有编码都失败,使用错误处理模式
|
||
print("⚠️ 所有编码都失败,使用错误处理模式")
|
||
return msg.decode('utf-8', errors='ignore')
|
||
|
||
except Exception as e:
|
||
print(f"❌ 接收消息失败: {e}")
|
||
return None
|
||
|
||
|
||
# 全局客户端实例
|
||
_client_instance = None
|
||
|
||
|
||
def get_client():
|
||
"""获取客户端实例"""
|
||
global _client_instance
|
||
if _client_instance is None:
|
||
_client_instance = SUWClient()
|
||
return _client_instance
|
||
|
||
|
||
def get_cmds() -> List[Dict[str, Any]]:
|
||
"""获取命令列表 - 修复错误处理"""
|
||
msg = json.dumps({
|
||
"cmd": "get_cmds",
|
||
"params": {"from": "su"}
|
||
})
|
||
|
||
client = get_client()
|
||
cmds = []
|
||
|
||
try:
|
||
if client.send_msg(OP_CMD_REQ_GETCMDS, msg):
|
||
res = client.recv_msg()
|
||
if res:
|
||
try:
|
||
# 尝试清理响应数据,移除可能的截断部分
|
||
cleaned_res = res.strip()
|
||
|
||
# 如果响应以 } 结尾,尝试找到完整的JSON
|
||
if cleaned_res.endswith('}'):
|
||
# 尝试从后往前找到匹配的 { 和 }
|
||
brace_count = 0
|
||
end_pos = len(cleaned_res) - 1
|
||
|
||
for i in range(end_pos, -1, -1):
|
||
if cleaned_res[i] == '}':
|
||
brace_count += 1
|
||
elif cleaned_res[i] == '{':
|
||
brace_count -= 1
|
||
if brace_count == 0:
|
||
# 找到完整的JSON
|
||
cleaned_res = cleaned_res[:i+1]
|
||
break
|
||
|
||
res_hash = json.loads(cleaned_res)
|
||
if res_hash.get('ret') == 1:
|
||
cmds = res_hash.get('data', {}).get('cmds', [])
|
||
# 只在有命令时输出日志
|
||
if cmds:
|
||
print(f"✅ 成功获取 {len(cmds)} 个命令")
|
||
else:
|
||
print(f"⚠️ 服务器返回错误: {res_hash.get('msg', '未知错误')}")
|
||
|
||
except json.JSONDecodeError as e:
|
||
# 只在调试模式下打印详细错误信息
|
||
if len(res) > 200: # 只对长响应打印详细信息
|
||
print(f"⚠️ JSON解析失败: {e}")
|
||
print(f"原始响应: {res[:200]}...")
|
||
|
||
# 尝试更激进的清理
|
||
try:
|
||
# 查找可能的JSON开始位置
|
||
start_pos = res.find('{')
|
||
if start_pos != -1:
|
||
# 尝试找到匹配的结束位置
|
||
brace_count = 0
|
||
for i in range(start_pos, len(res)):
|
||
if res[i] == '{':
|
||
brace_count += 1
|
||
elif res[i] == '}':
|
||
brace_count -= 1
|
||
if brace_count == 0:
|
||
cleaned_res = res[start_pos:i+1]
|
||
res_hash = json.loads(cleaned_res)
|
||
if res_hash.get('ret') == 1:
|
||
cmds = res_hash.get(
|
||
'data', {}).get('cmds', [])
|
||
# 只在有命令时输出日志
|
||
if cmds:
|
||
print(
|
||
f"✅ 修复后成功获取 {len(cmds)} 个命令")
|
||
# 【关键修复】确保修复后的命令被返回
|
||
return cmds
|
||
break
|
||
except:
|
||
print("❌ 无法修复JSON格式")
|
||
|
||
return []
|
||
|
||
except Exception as e:
|
||
print("========= get_cmds err is: =========")
|
||
print(e)
|
||
print("========= get_cmds res is: =========")
|
||
if 'res' in locals():
|
||
print(f"响应内容: {res[:200]}...")
|
||
else:
|
||
print("No response")
|
||
|
||
# 尝试重新连接
|
||
try:
|
||
client.reconnect()
|
||
except:
|
||
pass
|
||
|
||
return cmds
|
||
|
||
|
||
def set_cmd(cmd: str, params: Dict[str, Any]):
|
||
"""设置命令"""
|
||
cmds = {
|
||
"cmd": "set_cmd",
|
||
"params": params.copy()
|
||
}
|
||
|
||
cmds["params"]["from"] = "su"
|
||
cmds["params"]["cmd"] = cmd
|
||
|
||
msg = json.dumps(cmds)
|
||
client = get_client()
|
||
|
||
try:
|
||
if client.send_msg(OP_CMD_REQ_SETCMD, msg):
|
||
client.recv_msg() # 接收响应但不处理
|
||
|
||
except Exception as e:
|
||
print(f"❌ set_cmd 错误: {e}")
|
||
client.reconnect()
|
||
|
||
|
||
class CommandProcessor:
|
||
"""命令处理器"""
|
||
|
||
def __init__(self):
|
||
self.cmds_queue = []
|
||
self.pause = 0
|
||
self.running = False
|
||
self.timer_thread = None
|
||
|
||
def start(self):
|
||
"""启动命令处理器"""
|
||
if self.running:
|
||
return
|
||
|
||
self.running = True
|
||
self.timer_thread = threading.Thread(
|
||
target=self._timer_loop, daemon=True)
|
||
self.timer_thread.start()
|
||
print("✅ 命令处理器已启动")
|
||
|
||
def stop(self):
|
||
"""停止命令处理器"""
|
||
self.running = False
|
||
if self.timer_thread:
|
||
self.timer_thread.join(timeout=2)
|
||
print("⛔ 命令处理器已停止")
|
||
|
||
def _timer_loop(self):
|
||
"""定时器循环"""
|
||
while self.running:
|
||
try:
|
||
if self.pause > 0:
|
||
self.pause -= 1
|
||
else:
|
||
self._process_commands()
|
||
|
||
time.sleep(1) # 1秒间隔
|
||
|
||
except Exception as e:
|
||
print(f"❌ 命令处理循环错误: {e}")
|
||
time.sleep(1)
|
||
|
||
def _process_commands(self):
|
||
"""处理命令"""
|
||
try:
|
||
# 获取新命令
|
||
swcmds0 = get_cmds()
|
||
swcmds = self.cmds_queue + swcmds0
|
||
self.cmds_queue.clear()
|
||
|
||
# 处理每个命令
|
||
for swcmd in swcmds:
|
||
self._execute_command(swcmd)
|
||
|
||
except Exception as e:
|
||
print(f"❌ 处理命令时出错: {e}")
|
||
|
||
def _execute_command(self, swcmd: Dict[str, Any]):
|
||
"""执行单个命令"""
|
||
try:
|
||
data = swcmd.get("data")
|
||
|
||
if isinstance(data, str):
|
||
# 直接执行字符串命令(注意安全性)
|
||
print(f"执行字符串命令: {data}")
|
||
# 在实际应用中,这里应该更安全地执行命令
|
||
|
||
elif isinstance(data, dict) and "cmd" in data:
|
||
cmd = data.get("cmd")
|
||
print(f"执行命令: {cmd}, 数据: {data}")
|
||
|
||
if self.pause > 0:
|
||
self.cmds_queue.append(swcmd)
|
||
elif cmd.startswith("pause_"):
|
||
self.pause = data.get("value", 1)
|
||
else:
|
||
pre_pause_time = data.get("pre_pause", 0)
|
||
if pre_pause_time > 0:
|
||
data_copy = data.copy()
|
||
del data_copy["pre_pause"]
|
||
swcmd_copy = swcmd.copy()
|
||
swcmd_copy["data"] = data_copy
|
||
self.pause = pre_pause_time
|
||
self.cmds_queue.append(swcmd_copy)
|
||
else:
|
||
# 执行命令
|
||
self._call_suwood_method(cmd, data)
|
||
|
||
after_pause_time = data.get("after_pause", 0)
|
||
if after_pause_time > 0:
|
||
self.pause = after_pause_time
|
||
|
||
except Exception as e:
|
||
print(f"❌ 执行命令时出错: {e}")
|
||
|
||
def _call_suwood_method(self, cmd: str, data: Dict[str, Any]):
|
||
"""调用SUWood方法"""
|
||
try:
|
||
# 这里需要导入suw_core并调用相应方法
|
||
from .suw_core import get_selection_manager, get_machining_manager, get_deletion_manager
|
||
|
||
# 根据命令类型调用相应的管理器
|
||
if cmd.startswith("sel_"):
|
||
# 选择相关命令
|
||
selection_manager = get_selection_manager()
|
||
if hasattr(selection_manager, cmd):
|
||
method = getattr(selection_manager, cmd)
|
||
method(data)
|
||
else:
|
||
print(f"⚠️ 选择管理器方法不存在: {cmd}")
|
||
elif cmd.startswith("mach_"):
|
||
# 加工相关命令
|
||
machining_manager = get_machining_manager()
|
||
if hasattr(machining_manager, cmd):
|
||
method = getattr(machining_manager, cmd)
|
||
method(data)
|
||
else:
|
||
print(f"⚠️ 加工管理器方法不存在: {cmd}")
|
||
elif cmd.startswith("del_"):
|
||
# 删除相关命令
|
||
deletion_manager = get_deletion_manager()
|
||
if hasattr(deletion_manager, cmd):
|
||
method = getattr(deletion_manager, cmd)
|
||
method(data)
|
||
else:
|
||
print(f"⚠️ 删除管理器方法不存在: {cmd}")
|
||
else:
|
||
print(f"⚠️ 未知命令类型: {cmd}")
|
||
|
||
except ImportError:
|
||
print("⚠️ suw_core 模块未找到")
|
||
except Exception as e:
|
||
print(f"❌ 调用SUWood方法时出错: {e}")
|
||
|
||
|
||
# 全局命令处理器实例
|
||
_processor_instance = None
|
||
|
||
|
||
def get_processor():
|
||
"""获取命令处理器实例"""
|
||
global _processor_instance
|
||
if _processor_instance is None:
|
||
_processor_instance = CommandProcessor()
|
||
return _processor_instance
|
||
|
||
|
||
def start_command_processor():
|
||
"""启动命令处理器"""
|
||
processor = get_processor()
|
||
processor.start()
|
||
|
||
|
||
def stop_command_processor():
|
||
"""停止命令处理器"""
|
||
processor = get_processor()
|
||
processor.stop()
|
||
|
||
|
||
# 自动启动命令处理器(可选)
|
||
if __name__ == "__main__":
|
||
print("🚀 SUW客户端测试")
|
||
|
||
# 测试连接
|
||
client = get_client()
|
||
if client.sock:
|
||
print("连接成功,测试获取命令...")
|
||
cmds = get_cmds()
|
||
print(f"获取到 {len(cmds)} 个命令")
|
||
|
||
# 启动命令处理器
|
||
start_command_processor()
|
||
|
||
try:
|
||
# 保持运行
|
||
while True:
|
||
time.sleep(10)
|
||
except KeyboardInterrupt:
|
||
print("\n停止客户端...")
|
||
stop_command_processor()
|
||
else:
|
||
print("连接失败")
|