增加了异常信息打印,图像显示增加为原来的两倍大小
This commit is contained in:
5
.vscode/settings.json
vendored
Normal file
5
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"python-envs.defaultEnvManager": "ms-python.python:conda",
|
||||||
|
"python-envs.defaultPackageManager": "ms-python.python:conda",
|
||||||
|
"python-envs.pythonProjects": []
|
||||||
|
}
|
||||||
@@ -52,8 +52,6 @@ CMD_KEEPLIVE, CMD_INIT, CMD_KEY, CMD_LCDMEM = 0, 1, 2, 3
|
|||||||
# =============================================================================
|
# =============================================================================
|
||||||
# _sock:与 DTU 装置的 TCP socket,None 表示未连接
|
# _sock:与 DTU 装置的 TCP socket,None 表示未连接
|
||||||
_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:
|
||||||
|
|||||||
@@ -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);
|
||||||
|
// 绘制到 canvas(2 倍尺寸)
|
||||||
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>
|
||||||
|
|||||||
Reference in New Issue
Block a user