增加了异常信息打印,图像显示增加为原来的两倍大小

This commit is contained in:
2026-03-03 10:24:39 +08:00
parent 999443ebd5
commit 9f54a0cb2e
3 changed files with 37 additions and 18 deletions

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"python-envs.defaultEnvManager": "ms-python.python:conda",
"python-envs.defaultPackageManager": "ms-python.python:conda",
"python-envs.pythonProjects": []
}

View File

@@ -52,8 +52,6 @@ CMD_KEEPLIVE, CMD_INIT, CMD_KEY, CMD_LCDMEM = 0, 1, 2, 3
# ============================================================================= # =============================================================================
# _sock与 DTU 装置的 TCP socketNone 表示未连接 # _sock与 DTU 装置的 TCP socketNone 表示未连接
_sock: Optional[socket.socket] = None _sock: Optional[socket.socket] = None
# _init_info初始化信息width/height/mem_size由 CMD_INIT 获取
_init_info: Optional[dict] = None
# _screen_clients当前需要接收屏幕推送的 WebSocket 客户端 sid 集合 # _screen_clients当前需要接收屏幕推送的 WebSocket 客户端 sid 集合
_screen_clients: Set[str] = set() _screen_clients: Set[str] = set()
# _refresh_thread后台刷新线程持续拉取屏幕并推送 # _refresh_thread后台刷新线程持续拉取屏幕并推送
@@ -94,12 +92,15 @@ def parse_frame(raw: bytes) -> Optional[Tuple[int, bytes]]:
帧格式: [TAG(1)][cmd(1)][len_hi(1)][len_lo(1)][data(length)][crc(1)] 帧格式: [TAG(1)][cmd(1)][len_hi(1)][len_lo(1)][data(length)][crc(1)]
""" """
if len(raw) < 6 or raw[0] != TAG_DEVICE: # 至少 6 字节且帧头为 TAG_DEVICE if len(raw) < 6 or raw[0] != TAG_DEVICE: # 至少 6 字节且帧头为 TAG_DEVICE
print("[parse_frame] 错误: 帧太短或帧头不是 TAG_DEVICE(0xBB), len=%d" % len(raw))
return None return None
length = (raw[2] << 8) | raw[3] # 大端序解析数据区长度 length = (raw[2] << 8) | raw[3] # 大端序解析数据区长度
if len(raw) < 4 + length + 1: # 检查是否收齐整帧头4字节+数据+CRC1字节 if len(raw) < 4 + length + 1: # 检查是否收齐整帧头4字节+数据+CRC1字节
print("[parse_frame] 错误: 数据不完整, 需要 %d 字节, 实际 %d 字节" % (4 + length + 1, len(raw)))
return None return None
data = raw[4:4 + length] # 提取数据区 data = raw[4:4 + length] # 提取数据区
if calc_crc(data) != raw[4 + length]: # CRC 校验失败 if calc_crc(data) != raw[4 + length]: # CRC 校验失败
print("[parse_frame] 错误: CRC 校验失败")
return None return None
return (raw[1], data) # 返回 (命令码, 数据) return (raw[1], data) # 返回 (命令码, 数据)
@@ -113,7 +114,7 @@ def connect(host: str, port: int = PORT) -> bool:
建立与 DTU 装置的 TCP 连接。 建立与 DTU 装置的 TCP 连接。
若有旧连接则先关闭;连接时超时 3 秒;连接成功后设为 2 秒。 若有旧连接则先关闭;连接时超时 3 秒;连接成功后设为 2 秒。
""" """
global _sock, _init_info global _sock
try: try:
if _sock: if _sock:
_sock.close() # 关闭旧连接 _sock.close() # 关闭旧连接
@@ -122,18 +123,21 @@ def connect(host: str, port: int = PORT) -> bool:
_sock.connect((host, port)) # 连接目标主机和端口 _sock.connect((host, port)) # 连接目标主机和端口
_sock.settimeout(2.0) # 连接成功后收发超时 2 秒 _sock.settimeout(2.0) # 连接成功后收发超时 2 秒
return True return True
except Exception: except Exception as e:
print("[connect] 错误: 连接失败 - %s" % e)
return False return False
def _send(cmd: int, data: bytes = b"") -> bool: def _send(cmd: int, data: bytes = b"") -> bool:
"""发送一帧到设备""" """发送一帧到设备"""
if not _sock: if not _sock:
print("[_send] 错误: 未连接设备")
return False return False
try: try:
_sock.sendall(build_frame(cmd, data)) # 构造帧并一次性发送 _sock.sendall(build_frame(cmd, data)) # 构造帧并一次性发送
return True return True
except Exception: except Exception as e:
print("[_send] 错误: 发送失败 - %s" % e)
return False return False
@@ -143,16 +147,20 @@ def _recv() -> Optional[Tuple[int, bytes]]:
至少需 5 字节4 字节头+1 字节数据或 CRC根据头中 length 检查是否收齐整帧。 至少需 5 字节4 字节头+1 字节数据或 CRC根据头中 length 检查是否收齐整帧。
""" """
if not _sock: if not _sock:
print("[_recv] 错误: 未连接设备")
return None return None
try: try:
data = _sock.recv(256 * 1024) # 单次接收最多 256KB data = _sock.recv(256 * 1024) # 单次接收最多 256KB
if len(data) < 5: # 至少需要 5 字节4 头 + 1 数据/CRC if len(data) < 5: # 至少需要 5 字节4 头 + 1 数据/CRC
print("[_recv] 错误: 接收数据太短, len=%d" % len(data))
return None return None
length = (data[2] << 8) | data[3] # 解析数据区长度 length = (data[2] << 8) | data[3] # 解析数据区长度
if len(data) < 4 + length + 1: # 数据不完整 if len(data) < 4 + length + 1: # 数据不完整
print("[_recv] 错误: 数据不完整, 需要 %d 字节, 实际 %d 字节" % (4 + length + 1, len(data)))
return None return None
return parse_frame(data[:4 + length + 1]) # 解析并返回 (cmd, data) return parse_frame(data[:4 + length + 1]) # 解析并返回 (cmd, data)
except Exception: except Exception as e:
print("[_recv] 错误: 接收异常 - %s" % e)
return None return None
@@ -167,8 +175,10 @@ def fetch_screen() -> Optional[bytes]:
发送 CMD_LCDMEM + 起始地址 0设备回复 payload 前 4 字节为地址,后续为位图,返回 payload[4:]。 发送 CMD_LCDMEM + 起始地址 0设备回复 payload 前 4 字节为地址,后续为位图,返回 payload[4:]。
""" """
if not _sock: if not _sock:
print("[fetch_screen] 错误: 未连接设备")
return None return None
if not _send(CMD_LCDMEM, struct.pack(">I", 0)): # 发送读取显存命令,起始地址 0大端 4 字节) if not _send(CMD_LCDMEM, struct.pack(">I", 0)): # 发送读取显存命令,起始地址 0大端 4 字节)
print("[fetch_screen] 错误: 发送 CMD_LCDMEM 失败")
return None return None
result = _recv() # 接收设备回复 result = _recv() # 接收设备回复
if result and result[0] == CMD_LCDMEM: # 确认为 LCDMEM 回复 if result and result[0] == CMD_LCDMEM: # 确认为 LCDMEM 回复
@@ -176,6 +186,7 @@ def fetch_screen() -> Optional[bytes]:
if len(payload) >= 4: # 前 4 字节为地址,后面是位图 if len(payload) >= 4: # 前 4 字节为地址,后面是位图
return payload[4:] return payload[4:]
return payload or None return payload or None
print("[fetch_screen] 错误: 未收到有效 LCDMEM 回复")
return None return None
@@ -197,19 +208,19 @@ def mono_to_png(data: bytes, width: int, height: int) -> Optional[bytes]:
img.save(buf, format="PNG") # 保存为 PNG 格式 img.save(buf, format="PNG") # 保存为 PNG 格式
return buf.getvalue() # 返回 PNG 字节流 return buf.getvalue() # 返回 PNG 字节流
except ImportError: except ImportError:
print("[mono_to_png] 错误: 缺少 PIL/Pillow, 请执行 pip install pillow")
return None return None
def disconnect_device(): def disconnect_device():
"""关闭与设备的连接""" """关闭与设备的连接"""
global _sock, _init_info global _sock
try: try:
if _sock: if _sock:
_sock.close() # 关闭 socket _sock.close() # 关闭 socket
_sock = None # 清空引用 _sock = None # 清空引用
_init_info = None # 清空初始化信息 except Exception as e:
except Exception: print("[disconnect_device] 错误: 关闭连接时异常 - %s" % e)
pass
# ============================================================================= # =============================================================================
@@ -226,7 +237,7 @@ def _screen_refresh_loop():
if _screen_clients and _sock: # 有客户端且已连接设备 if _screen_clients and _sock: # 有客户端且已连接设备
data = fetch_screen() # 拉取屏幕位图 data = fetch_screen() # 拉取屏幕位图
if data: if data:
info = _init_info or {} # 获取宽高,缺省 160x160 info = {"width": 160, "height": 160} # 设置长宽 160x160
w, h = info.get("width", 160), info.get("height", 160) w, h = info.get("width", 160), info.get("height", 160)
png = mono_to_png(data, w, h) # 转为 PNG png = mono_to_png(data, w, h) # 转为 PNG
if png: if png:

View File

@@ -402,12 +402,15 @@
// 创建临时 URL // 创建临时 URL
const img = new Image(); const img = new Image();
img.onload = () => { img.onload = () => {
// 图片加载完成后 // 图片加载完成后放大一倍显示160x160 -> 320x320
canvas.width = img.width; const scale = 2;
canvas.height = img.height; canvas.width = img.width * scale;
// 设置 canvas 尺寸 canvas.height = img.height * scale;
canvas.getContext('2d').drawImage(img, 0, 0); const ctx = canvas.getContext('2d');
// 绘制到 canvas ctx.imageSmoothingEnabled = false;
// 关闭平滑,像素清晰放大
ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, canvas.width, canvas.height);
// 绘制到 canvas2 倍尺寸)
canvas.style.display = 'block'; canvas.style.display = 'block';
placeholder.style.display = 'none'; placeholder.style.display = 'none';
// 显示 canvas隐藏占位符 // 显示 canvas隐藏占位符
@@ -467,7 +470,7 @@
}); });
document.querySelector('.menu-item.exit').onclick = doDisconnect; document.querySelector('.menu-item.exit').onclick = doDisconnect;
// 断开按钮 -> 执行断开 // 断开按钮 -> 执行断开
document.getElementById('btnAbout').onclick = () => alert('远程显示工具\n与 RemoDispBus 协议兼容\n端口 7003'); document.getElementById('btnAbout').onclick = () => alert('阜阳师范大学物理与电子工程学院\nDTU 远程液晶屏幕通信工具');
// 关于按钮 -> 弹出说明 // 关于按钮 -> 弹出说明
</script> </script>
</body> </body>