feat: publish workphone SDK deployment and API docs

This commit is contained in:
Manus AI
2026-07-14 18:10:52 +08:00
commit 021d633cc1
534 changed files with 122391 additions and 0 deletions

View File

@@ -0,0 +1,207 @@
"""
传感器模拟 — 伪造加速度计/陀螺仪数据,模拟真人手持特征
功能:
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