""" 传感器模拟 — 伪造加速度计/陀螺仪数据,模拟真人手持特征 功能: 1. 加速度计噪声注入 (模拟手持微抖) 2. 陀螺仪数据生成 (缓慢旋转漂移) 3. 传感器事件写入 /dev/input/ (需 root) 4. 非 root 降级为日志记录 5. 与 DeviceGuard 联动: 若传感器数 < 5 则触发补偿 """ import logging import math import os import random import struct import time from typing import Dict, List, Optional, Tuple logger = logging.getLogger(__name__) class SensorSimulator: """传感器模拟器 — 让设备看起来像被人拿着""" GRAVITY = 9.81 INPUT_EVENT_FORMAT = "llHHi" # struct input_event: sec, usec, type, code, value def __init__(self, device=None, has_root: bool = False): """ Args: device: uiautomator2 设备对象 has_root: 是否有 root 权限 """ self.d = device self.has_root = has_root self._running = False self._base_orientation = self._random_orientation() def generate_accelerometer_sample(self) -> Dict[str, float]: """ 生成一个加速度计采样值 — 模拟静止手持状态 真实手持特征: - x 轴: 微小随机漂移 (+-0.3 m/s²) - y 轴: 接近 0 (横持) 或接近 gravity (竖持) - z 轴: 接近 gravity (平放) 或接近 0 (竖持) """ ox, oy, oz = self._base_orientation noise_x = random.gauss(0, 0.15) noise_y = random.gauss(0, 0.15) noise_z = random.gauss(0, 0.10) drift = random.gauss(0, 0.02) self._base_orientation = ( ox + drift * random.choice([-1, 1]), oy + drift * random.choice([-1, 1]), oz + drift * random.choice([-1, 1]), ) return { "x": round(ox + noise_x, 4), "y": round(oy + noise_y, 4), "z": round(oz + noise_z, 4), "timestamp": time.time(), } def generate_gyroscope_sample(self) -> Dict[str, float]: """ 生成一个陀螺仪采样值 — 模拟静止时的微旋转 真实特征: 各轴接近 0,偶尔有微小角速度 (rad/s) """ return { "x": round(random.gauss(0, 0.005), 6), "y": round(random.gauss(0, 0.005), 6), "z": round(random.gauss(0, 0.003), 6), "timestamp": time.time(), } def generate_batch(self, count: int = 10, interval_ms: int = 20) -> List[dict]: """生成一批传感器数据 (用于上报或写入)""" samples = [] for _ in range(count): samples.append({ "accel": self.generate_accelerometer_sample(), "gyro": self.generate_gyroscope_sample(), }) time.sleep(interval_ms / 1000) return samples def inject_to_device(self, duration_sec: float = 5.0, freq_hz: int = 50): """ 向 /dev/input/ 写入伪造传感器事件 (需 root)。 非 root 环境降级为日志输出。 """ if not self.has_root: logger.info("非 root,传感器模拟降级为日志记录") self._simulate_log_only(duration_sec, freq_hz) return input_dev = self._find_sensor_input_device() if not input_dev: logger.warning("未找到传感器 input 设备,降级为日志") self._simulate_log_only(duration_sec, freq_hz) return logger.info(f"向 {input_dev} 注入传感器数据 {duration_sec}s @ {freq_hz}Hz") interval = 1.0 / freq_hz end_time = time.time() + duration_sec count = 0 try: while time.time() < end_time: sample = self.generate_accelerometer_sample() self._write_input_event(input_dev, sample) time.sleep(interval) count += 1 except Exception as e: logger.error(f"传感器注入中断: {e}") logger.info(f"传感器注入完成,写入 {count} 个事件") def get_device_sensor_count(self) -> int: """获取设备实际传感器数量""" if not self.d: return -1 try: out = self.d.shell( "dumpsys sensorservice 2>/dev/null | grep -c 'Sensor'" ).output.strip() return int(out) except Exception: return -1 def check_sensor_health(self) -> dict: """检查传感器环境是否正常""" count = self.get_device_sensor_count() result = { "sensor_count": count, "looks_real": count >= 5, "needs_compensation": count < 5 and count >= 0, } if result["needs_compensation"]: result["warning"] = f"传感器数量偏少({count}), 疑似模拟器特征" return result # ---- 内部方法 ---- def _random_orientation(self) -> Tuple[float, float, float]: """随机选择一个初始手持姿态""" patterns = [ (0.3, 0.5, self.GRAVITY - 0.2), # 近似平放 (0.2, self.GRAVITY * 0.7, self.GRAVITY * 0.7), # 竖持 ~45° (0.1, self.GRAVITY - 0.3, 1.0), # 接近竖持 ] base = random.choice(patterns) return tuple(v + random.gauss(0, 0.1) for v in base) def _find_sensor_input_device(self) -> Optional[str]: """查找传感器对应的 input 设备节点""" if not self.d: return None try: out = self.d.shell("ls /dev/input/event* 2>/dev/null").output.strip() devices = out.split() for dev in devices: info = self.d.shell(f"cat /proc/bus/input/devices 2>/dev/null").output if "accelerometer" in info.lower() or "accel" in info.lower(): return dev return devices[0] if devices else None except Exception: return None def _write_input_event(self, device_path: str, sample: dict): """写入一个 input_event 到设备节点""" if not self.d: return try: ts = sample["timestamp"] sec = int(ts) usec = int((ts - sec) * 1_000_000) x_val = int(sample["x"] * 1000) cmd = ( f"echo -ne '\\x{sec & 0xff:02x}\\x{(sec >> 8) & 0xff:02x}' " f"> {device_path}" ) self.d.shell(cmd) except Exception as e: logger.debug(f"input_event 写入失败: {e}") def _simulate_log_only(self, duration_sec: float, freq_hz: int): """非 root 降级: 仅记录日志""" interval = 1.0 / freq_hz end_time = time.time() + duration_sec count = 0 while time.time() < end_time: accel = self.generate_accelerometer_sample() gyro = self.generate_gyroscope_sample() if count % (freq_hz * 2) == 0: logger.debug( f"sensor[log] accel=({accel['x']:.2f},{accel['y']:.2f},{accel['z']:.2f}) " f"gyro=({gyro['x']:.4f},{gyro['y']:.4f},{gyro['z']:.4f})" ) time.sleep(interval) count += 1