#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ _mock_server.py — 仅用于在电脑上测试协议,不需要 ROS。 它模拟板子端:返回一张合成图像、一片合成点云(3 m 处、像素 800,648 附近), 以及一个会随时间变化的位姿,用来验证 session_client 的抓帧、点选、回传、Δ 显示。 """ import io import threading import time import zlib import numpy as np from PIL import Image import pynng import calib import target_math import wire CAL_W, CAL_H = calib.IMAGE_WIDTH, calib.IMAGE_HEIGHT U0, V0 = 800.0, 648.0 DEPTH = 3.0 def build_cloud(): pts = [] for du in range(-12, 13, 4): for dv in range(-12, 13, 4): ray = calib.pixel_to_ray(U0 + du, V0 + dv) p_cam = ray * DEPTH pts.append(calib.cam_to_lidar(p_cam)[0]) pts = np.asarray(pts, dtype=np.float32) # 加一些无效点,模拟真实点云的零值 zeros = np.zeros((97, 3), dtype=np.float32) return np.concatenate([pts, zeros], axis=0) def build_image(): arr = np.zeros((CAL_H, CAL_W, 3), dtype=np.uint8) arr[:, :, 2] = 40 u, v = int(round(U0)), int(round(V0)) arr[v - 6:v + 6, u - 6:u + 6] = (255, 255, 255) b = io.BytesIO() Image.fromarray(arr).save(b, format='JPEG', quality=85) return b.getvalue() class MockServer: def __init__(self, port=5599): self.port = port self.xyz = build_cloud() self.image = build_image() self.target = None self.index = None self.sessions = {} self.stop_event = threading.Event() self.thread = None def start(self): self.thread = threading.Thread(target=self._serve, daemon=True) self.thread.start() def stop(self): self.stop_event.set() if self.thread: self.thread.join(timeout=2) def _odom(self): return { 'stamp_ns': time.time_ns(), 'position': [0.0, 0.0, 0.0], 'orientation_xyzw': [0.0, 0.0, 0.0, 1.0], 'frame_id': 'odom', 'child_frame_id': 'imu', 'delta_ms': 1.0, } def _handle(self, hdr): typ = hdr.get('type') if typ == 'hello': return {'ok': True, 'type': 'hello', 'node': 'mock', 'calib_width': CAL_W, 'calib_height': CAL_H, 'board_can_compute': True, 'compute_modes': ['board', 'pc'], 'topics': {'image': 'mock', 'cloud': 'mock', 'odom': 'mock'}}, b'' if typ == 'capture': sid = (hdr.get('session') or {}).get('id') self.sessions[sid] = {'target': None} return {'ok': True, 'type': 'frame', 'image_format': 'jpeg', 'image_bytes': len(self.image), 'image_width': CAL_W, 'image_height': CAL_H, 'calib_width': CAL_W, 'calib_height': CAL_H, 'image_stamp_ns': time.time_ns(), 'cloud_stamp_ns': time.time_ns(), 'stamp_delta_ms': 1.2, 'n_points': int(self.xyz.shape[0]), 'board_can_compute': True, 'odom': self._odom()}, self.image if typ == 'click': sid = (hdr.get('session') or {}).get('id') s = self.sessions.get(sid) if s is None: return {'ok': False, 'type': 'target', 'error': '先 capture'}, b'' if self.index is None: self.index = target_math.TargetIndex(self.xyz) u = float(hdr['u']) v = float(hdr['v']) res = self.index.locate(u, v, radius=float(hdr.get('radius', 10.0)), fit_plane=bool(hdr.get('fit_plane', True))) out = {'ok': True, 'type': 'target', 'found': bool(res['found']), 'u': u, 'v': v, 'n_cand': int(res['n_cand']), 'plane_used': bool(res['plane_used']), 'multi_layer': bool(res['multi_layer'])} if res['found']: out.update({ 'p_cam': [float(x) for x in res['p_cam']], 'p_lidar': [float(x) for x in res['p_lidar']], 'hit_u': float(res['hit_u']), 'hit_v': float(res['hit_v']), 'px_err': float(res['px_err']), 'confidence': None, }) pm = np.asarray(res['p_lidar'], dtype=np.float64) out['p_map'] = [float(x) for x in pm] out['odom'] = self._odom() s['target'] = pm return out, b'' if typ == 'cloud': body = zlib.compress(self.xyz.astype('