重构屏幕控制引擎 + 新增 Web 监控面板
- ScreenManager: 基于优先级(9/5/0)的统一屏幕控制引擎,替代 _tick 中的 散乱 if-else 逻辑 - 四个亮屏事件: scheduled(p9), sensor(p9), manual(p5), external(p0) - GPIO 回调: 电平变化立即触发亮屏(异步线程,不阻塞轮询) - _fast_check: 每秒轻量评估,提高息屏超时精度(±1s 替代 ±30s) - padsleep_web: 通过 IPC 读取守护进程状态的实时 Web 面板 (GPIO 电平/屏幕状态/ADB 连接,端口 31400) - padsleep-web.service: 关联 padsleep.service 的自启动服务 - install.sh: 修复 venv 初始化目录上下文,使用 requirements.txt - IpcClient: 修复 socket 复用导致交替失败的问题 - padsleep_tui: 适配 ScreenManager 状态显示 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9940db451f
commit
e6fbe550bb
+271
-75
@@ -282,7 +282,7 @@ class GPIOEventDetector:
|
||||
detector.stop()
|
||||
"""
|
||||
|
||||
POLL_INTERVAL = 0.05 # 轮询间隔 50ms
|
||||
POLL_INTERVAL = 1.0 # 轮询间隔 1s
|
||||
|
||||
def __init__(self, pin: int, callback=None):
|
||||
self.pin = pin
|
||||
@@ -290,6 +290,7 @@ class GPIOEventDetector:
|
||||
self.last_event_time: float = 0.0 # 0 表示从未触发
|
||||
self.last_value: int | None = None
|
||||
self.available = False # 硬件是否可用
|
||||
self.current_value: int = 0 # 当前引脚电平(供外部读取)
|
||||
self._running = False
|
||||
self._thread: threading.Thread | None = None
|
||||
self._reader = None
|
||||
@@ -337,6 +338,7 @@ class GPIOEventDetector:
|
||||
while self._running:
|
||||
try:
|
||||
val = self._gpio.input(self.pin)
|
||||
self.current_value = val # 对外暴露当前电平
|
||||
if val != self.last_value:
|
||||
self.last_value = val
|
||||
self.last_event_time = time.time()
|
||||
@@ -796,6 +798,130 @@ class IpcServer:
|
||||
self.cleanup()
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# ScreenManager — 统一屏幕状态管理(flag 组 + 优先级决策)
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
class ScreenManager:
|
||||
"""统一管理四个亮屏事件,基于优先级决定息屏时机。
|
||||
|
||||
四个事件(优先级从高到低):
|
||||
1. 定时点亮 (scheduled) — p9: 在亮屏时间段内 → 亮屏
|
||||
2. 传感器点亮 (sensor) — p9: GPIO 检测到活动 → 亮屏 + 倒计时
|
||||
3. IPC 控制 (manual) — p5: 收到 IPC screen_on → 亮屏
|
||||
4. 检测到外部亮屏 (external) — p0: 检测到物理按键亮屏
|
||||
|
||||
决策规则:
|
||||
- 每个事件的 flag 记录触发时间戳
|
||||
- 每次 tick 检查最高优先级事件的息屏条件
|
||||
- 同优先级需 ALL 满足息屏条件才息屏
|
||||
- 息屏时清除所有 flag
|
||||
"""
|
||||
|
||||
REASON_SCHEDULED = "scheduled" # 定时点亮, p9
|
||||
REASON_SENSOR = "sensor" # 传感器点亮, p9
|
||||
REASON_MANUAL = "manual" # IPC 控制, p5
|
||||
REASON_EXTERNAL = "external" # 检测到外部亮屏, p0
|
||||
|
||||
PRIORITIES = {
|
||||
REASON_SCHEDULED: 9,
|
||||
REASON_SENSOR: 9,
|
||||
REASON_MANUAL: 5,
|
||||
REASON_EXTERNAL: 0,
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self._flags: dict[str, float] = {} # reason → trigger timestamp
|
||||
self._manual_released = False # manual 是否已要求释放
|
||||
|
||||
# ── 接口 ──
|
||||
|
||||
def trigger(self, reason: str):
|
||||
"""设置亮屏事件 flag(记录当前时间戳)。多次调用刷新时间戳。"""
|
||||
self._flags[reason] = time.time()
|
||||
if reason == self.REASON_MANUAL:
|
||||
self._manual_released = False
|
||||
|
||||
def release_manual(self):
|
||||
"""标记手动息屏请求(收到 IPC screen_off)。"""
|
||||
self._manual_released = True
|
||||
|
||||
def clear(self):
|
||||
"""清除所有 flag(息屏时调用)。"""
|
||||
self._flags.clear()
|
||||
self._manual_released = False
|
||||
|
||||
# ── 核心决策 ──
|
||||
|
||||
def evaluate(self, *, actual_screen_on: bool, within_period: bool,
|
||||
gpio_elapsed: float, gpio_countdown: int,
|
||||
screen_on_duration: float, auto_sleep_delay: float) -> dict:
|
||||
"""综合各事件 flag 判断应执行的操作。
|
||||
|
||||
返回:
|
||||
{"action": "on", "reason": "..."} → 需要亮屏
|
||||
{"action": "off", "reason": "..."} → 需要息屏(调用者负责 clear())
|
||||
{"action": "none", "reason": "..."} → 保持当前状态
|
||||
"""
|
||||
active = list(self._flags.keys())
|
||||
if not active:
|
||||
return {"action": "none", "reason": "no_flags"}
|
||||
|
||||
# 按优先级分组
|
||||
by_pri: dict[int, list[str]] = {}
|
||||
for r in active:
|
||||
p = self.PRIORITIES.get(r, 0)
|
||||
by_pri.setdefault(p, []).append(r)
|
||||
|
||||
max_pri = max(by_pri)
|
||||
top = by_pri[max_pri]
|
||||
|
||||
# 检查最高优先级组:是否 ALL 满足息屏条件
|
||||
all_ok = all(
|
||||
self._can_off(r, within_period, gpio_elapsed, gpio_countdown,
|
||||
screen_on_duration, auto_sleep_delay)
|
||||
for r in top
|
||||
)
|
||||
|
||||
if all_ok:
|
||||
if actual_screen_on:
|
||||
return {"action": "off", "reason": f"p{max_pri}_all_ok"}
|
||||
return {"action": "none", "reason": "already_off"}
|
||||
|
||||
# 有事件仍要求亮屏
|
||||
if not actual_screen_on:
|
||||
return {"action": "on", "reason": f"p{max_pri}_wants_on"}
|
||||
return {"action": "none", "reason": f"p{max_pri}_active"}
|
||||
|
||||
def _can_off(self, reason: str, within_period: bool,
|
||||
gpio_elapsed: float, gpio_countdown: int,
|
||||
screen_on_duration: float, auto_sleep_delay: float) -> bool:
|
||||
"""判断单个事件是否满足息屏条件。"""
|
||||
if reason == self.REASON_SCHEDULED:
|
||||
return not within_period
|
||||
if reason == self.REASON_SENSOR:
|
||||
return gpio_elapsed >= gpio_countdown
|
||||
if reason == self.REASON_MANUAL:
|
||||
return self._manual_released or screen_on_duration >= auto_sleep_delay
|
||||
if reason == self.REASON_EXTERNAL:
|
||||
return screen_on_duration >= auto_sleep_delay
|
||||
return True # 未知事件默认可息屏
|
||||
|
||||
# ── 状态查询 ──
|
||||
|
||||
def get_active_reason(self) -> str | None:
|
||||
"""返回当前最高优先级的 active 事件名(用于显示)。"""
|
||||
if not self._flags:
|
||||
return None
|
||||
return max(self._flags, key=lambda r: self.PRIORITIES.get(r, 0))
|
||||
|
||||
def get_status(self) -> dict:
|
||||
return {
|
||||
"active_reason": self.get_active_reason(),
|
||||
"flags": dict(self._flags),
|
||||
}
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# PadSleepApp — 主应用
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
@@ -808,6 +934,7 @@ class PadSleepApp:
|
||||
self.audit = AuditLogger()
|
||||
self.config_mgr = ConfigManager()
|
||||
self.adb = AdbManager(self._get_config, self.audit)
|
||||
self.screen_mgr = ScreenManager()
|
||||
self.gpio: GPIOEventDetector | None = None
|
||||
self.ipc = IpcServer(self)
|
||||
self._lock = threading.Lock()
|
||||
@@ -826,6 +953,8 @@ class PadSleepApp:
|
||||
"gpio_triggered": False,
|
||||
"gpio_countdown": 0,
|
||||
"gpio_pin": 17,
|
||||
# ── ScreenManager 状态 ──
|
||||
"active_reason": None,
|
||||
}
|
||||
|
||||
# ── 辅助 ──
|
||||
@@ -843,6 +972,32 @@ class PadSleepApp:
|
||||
s["uptime"] = int(time.time() - self._start_time)
|
||||
return s
|
||||
|
||||
# ── GPIO 回调(在 50ms 轮询线程中触发)──
|
||||
|
||||
def _on_gpio_event(self, value):
|
||||
"""GPIO 电平变化回调:设置传感器 flag,异步处理亮屏。
|
||||
|
||||
运行在 GPIOEventDetector 的 50ms 轮询线程中,
|
||||
必须尽快返回以避免阻塞电平检测。
|
||||
"""
|
||||
self.screen_mgr.trigger(ScreenManager.REASON_SENSOR)
|
||||
# 独立线程处理亮屏(screen_on 有 2s ADB 验证延迟,不阻塞 GPIO 轮询)
|
||||
threading.Thread(target=self._gpio_screen_on, daemon=True).start()
|
||||
|
||||
def _gpio_screen_on(self):
|
||||
"""在独立线程中检查屏幕状态,灭屏则立即亮屏。"""
|
||||
try:
|
||||
screen_on, _ = self.adb.get_screen_state()
|
||||
if not screen_on:
|
||||
config = self.config_mgr.get()
|
||||
gpio_pin = config.get("gpio_pin", 17)
|
||||
gpio_countdown = config.get("gpio_countdown_seconds", 60)
|
||||
log.info("GPIO 回调 → 亮屏")
|
||||
self.audit.log("GPIO触屏", f"BCM {gpio_pin} 电平变化,{gpio_countdown}s 倒计时")
|
||||
self.adb.screen_on()
|
||||
except Exception as e:
|
||||
log.error("GPIO 亮屏异常: %s", e)
|
||||
|
||||
# ── IPC 命令处理 ──
|
||||
|
||||
def handle_ipc(self, request: dict) -> dict:
|
||||
@@ -859,6 +1014,9 @@ class PadSleepApp:
|
||||
status["connection_type"] = self.adb.get_connection_type()
|
||||
status["wireless_host"] = cfg.get("adb_wireless_host", "")
|
||||
status["wireless_port"] = cfg.get("adb_wireless_port", 5555)
|
||||
# 实时 GPIO 电平(由 GPIOEventDetector 50ms 轮询线程更新)
|
||||
if self.gpio and self.gpio.available:
|
||||
status["gpio_level"] = self.gpio.current_value
|
||||
return {"ok": True, "data": status}
|
||||
|
||||
if cmd == "get_config":
|
||||
@@ -893,10 +1051,12 @@ class PadSleepApp:
|
||||
return {"ok": True}
|
||||
|
||||
if cmd == "screen_off":
|
||||
self.screen_mgr.release_manual()
|
||||
ok = self.adb.screen_off()
|
||||
return {"ok": ok}
|
||||
|
||||
if cmd == "screen_on":
|
||||
self.screen_mgr.trigger(ScreenManager.REASON_MANUAL)
|
||||
ok = self.adb.screen_on()
|
||||
return {"ok": ok}
|
||||
|
||||
@@ -960,7 +1120,7 @@ class PadSleepApp:
|
||||
# ── 启动 GPIO 电平检测 ──
|
||||
if config.get("gpio_enabled", True):
|
||||
gpio_pin = config.get("gpio_pin", 17)
|
||||
self.gpio = GPIOEventDetector(pin=gpio_pin)
|
||||
self.gpio = GPIOEventDetector(pin=gpio_pin, callback=self._on_gpio_event)
|
||||
self.gpio.start()
|
||||
if self.gpio.available:
|
||||
log.info("GPIO 电平检测已启用 (BCM %d, 倒计时 %ds)",
|
||||
@@ -996,12 +1156,13 @@ class PadSleepApp:
|
||||
# 检测配置文件外部变更
|
||||
self.config_mgr.reload_if_changed()
|
||||
|
||||
# 分段睡眠以快速响应退出信号
|
||||
# 分段睡眠,每秒做一次轻量决策检查(evaluate 纯内存运算)
|
||||
interval = self.config_mgr.get().get("check_interval_seconds", 30)
|
||||
for _ in range(max(1, interval)):
|
||||
if not self.running:
|
||||
break
|
||||
time.sleep(1)
|
||||
self._fast_check()
|
||||
|
||||
# 清理
|
||||
if self.gpio:
|
||||
@@ -1023,49 +1184,82 @@ class PadSleepApp:
|
||||
self._last_adb_warn = now
|
||||
return prev_screen_on if prev_screen_on is not None else False
|
||||
|
||||
# ── 获取屏幕状态 ──
|
||||
# ── 获取屏幕实际状态 ──
|
||||
screen_on, wakefulness = self.adb.get_screen_state()
|
||||
periods = config.get("screen_on_periods", [])
|
||||
within_period = TimeChecker.is_within_any_period(periods)
|
||||
auto_sleep_delay = config.get("auto_sleep_delay_minutes", 5) * 60
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# GPIO 触发检测(人来亮屏 / 人走息屏)
|
||||
#
|
||||
# 由于购买的模块有问题,无法稳定输出电平,但是人来人走时会发生
|
||||
# 短暂电平切换,因此采用这个折中的法子:检测到 GPIO 电平变化
|
||||
# 就亮屏并重置 gpio_countdown_seconds 秒倒计时,倒计时结束
|
||||
# 且无新变化则息屏(除非当前在亮屏时段)。
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ── 1. 设置定时 flag ──
|
||||
if within_period:
|
||||
self.screen_mgr.trigger(ScreenManager.REASON_SCHEDULED)
|
||||
|
||||
# ── 2. 设置传感器 flag ──
|
||||
gpio_triggered = False
|
||||
gpio_countdown_remain = 0
|
||||
gpio_elapsed = float("inf")
|
||||
gpio_countdown = config.get("gpio_countdown_seconds", 60)
|
||||
if self.gpio and self.gpio.available:
|
||||
gpio_pin = config.get("gpio_pin", 17)
|
||||
countdown = config.get("gpio_countdown_seconds", 60)
|
||||
elapsed = self.gpio.seconds_since_last_event
|
||||
gpio_triggered = elapsed < countdown
|
||||
gpio_countdown_remain = max(0, countdown - int(elapsed))
|
||||
|
||||
gpio_elapsed = self.gpio.seconds_since_last_event
|
||||
gpio_triggered = gpio_elapsed < gpio_countdown
|
||||
gpio_countdown_remain = max(0, gpio_countdown - int(gpio_elapsed))
|
||||
if gpio_triggered:
|
||||
# GPIO 刚刚触发过 → 确保屏幕亮起
|
||||
if not screen_on:
|
||||
log.info("GPIO 检测到电平变化 → 亮屏 (%ds 倒计时)", gpio_countdown_remain)
|
||||
self.audit.log("GPIO触屏", f"BCM {gpio_pin} 电平变化,{countdown}s 倒计时")
|
||||
self.adb.screen_on()
|
||||
screen_on = True
|
||||
else:
|
||||
# 屏幕已亮,仅做调试日志
|
||||
if gpio_countdown_remain >= countdown - 1:
|
||||
log.info("GPIO 电平变化 → 重置倒计时 %ds", countdown)
|
||||
else:
|
||||
# GPIO 倒计时已结束且屏幕不在亮屏时段 → 息屏
|
||||
if screen_on and not within_period and elapsed > countdown:
|
||||
log.info("GPIO 倒计时结束(%.0fs 无变化)→ 息屏", elapsed)
|
||||
self.audit.log("GPIO息屏", f"BCM {gpio_pin} 在 {countdown}s 内无电平变化")
|
||||
self.adb.screen_off()
|
||||
screen_on = False
|
||||
self.screen_mgr.trigger(ScreenManager.REASON_SENSOR)
|
||||
|
||||
# ── 更新状态(含 GPIO 信息)──
|
||||
# ── 3. 检测外部状态变化(用户按电源键)──
|
||||
if prev_screen_on is not None and prev_screen_on != screen_on:
|
||||
if screen_on:
|
||||
# 屏幕被外部点亮——设置 external flag 让 ScreenManager 追踪
|
||||
self.screen_mgr.trigger(ScreenManager.REASON_EXTERNAL)
|
||||
log.info("检测到屏幕被点亮 (外部操作)")
|
||||
self.audit.log("检测到屏幕被点亮", "外部操作或用户手动")
|
||||
else:
|
||||
log.info("检测到屏幕被熄灭 (外部操作)")
|
||||
self.audit.log("检测到屏幕被熄灭", "外部操作或用户手动")
|
||||
|
||||
# ── 4. ScreenManager 统一决策 ──
|
||||
decision = self.screen_mgr.evaluate(
|
||||
actual_screen_on=screen_on,
|
||||
within_period=within_period,
|
||||
gpio_elapsed=gpio_elapsed,
|
||||
gpio_countdown=gpio_countdown,
|
||||
screen_on_duration=self.adb.get_screen_on_duration(),
|
||||
auto_sleep_delay=auto_sleep_delay,
|
||||
)
|
||||
|
||||
# ── 5. 执行决策 ──
|
||||
if decision["action"] == "on" and not screen_on:
|
||||
log.info("ScreenManager 决策 → 亮屏 (%s)", decision["reason"])
|
||||
# 区分亮屏原因
|
||||
if ScreenManager.REASON_SENSOR in self.screen_mgr._flags:
|
||||
gpio_pin = config.get("gpio_pin", 17)
|
||||
countdown = config.get("gpio_countdown_seconds", 60)
|
||||
self.audit.log("GPIO触屏", f"BCM {gpio_pin} 电平变化,{countdown}s 倒计时")
|
||||
elif ScreenManager.REASON_SCHEDULED in self.screen_mgr._flags:
|
||||
period_desc = TimeChecker.nearest_period_description(periods)
|
||||
self.audit.log("亮屏", f"在亮屏时间段 ({period_desc}) 内检测到屏幕熄灭,自动恢复")
|
||||
elif ScreenManager.REASON_MANUAL in self.screen_mgr._flags:
|
||||
self.audit.log("亮屏", "手动 IPC 控制")
|
||||
self.adb.screen_on()
|
||||
screen_on = True
|
||||
|
||||
elif decision["action"] == "off" and screen_on:
|
||||
reason = decision["reason"]
|
||||
log.info("ScreenManager 决策 → 息屏 (%s)", reason)
|
||||
if "sensor" in str(self.screen_mgr._flags):
|
||||
self.audit.log("GPIO息屏",
|
||||
f"BCM {config.get('gpio_pin', 17)} 在 {gpio_countdown}s 内无电平变化")
|
||||
elif reason == "p5_all_ok" or reason == "p0_all_ok":
|
||||
period_desc = TimeChecker.nearest_period_description(periods)
|
||||
self.audit.log("息屏",
|
||||
f"不在亮屏时间段 ({period_desc}),超时 {config.get('auto_sleep_delay_minutes')} 分钟")
|
||||
self.screen_mgr.clear()
|
||||
self.adb.screen_off()
|
||||
screen_on = False
|
||||
|
||||
# ── 6. 更新状态 ──
|
||||
device = self.adb.get_ready_device()
|
||||
gpio_level = self.gpio.current_value if self.gpio and self.gpio.available else -1
|
||||
self._update_status(
|
||||
adb_ready=True,
|
||||
device=device or "无",
|
||||
@@ -1075,49 +1269,51 @@ class PadSleepApp:
|
||||
within_period=within_period,
|
||||
gpio_triggered=gpio_triggered,
|
||||
gpio_countdown=gpio_countdown_remain,
|
||||
gpio_level=gpio_level,
|
||||
**self.screen_mgr.get_status(),
|
||||
)
|
||||
|
||||
# ── 检测外部屏幕状态变化 ──
|
||||
if prev_screen_on is not None and prev_screen_on != screen_on:
|
||||
if screen_on:
|
||||
log.info("检测到屏幕被点亮 (外部操作)")
|
||||
self.audit.log("检测到屏幕被点亮", "外部操作或用户手动")
|
||||
else:
|
||||
log.info("检测到屏幕被熄灭 (外部操作)")
|
||||
self.audit.log("检测到屏幕被熄灭", "外部操作或用户手动")
|
||||
|
||||
# ── 原有决策逻辑(仅在非 GPIO 触发时有效) ──
|
||||
|
||||
delay_sec = config.get("auto_sleep_delay_minutes", 5) * 60
|
||||
period_desc = TimeChecker.nearest_period_description(periods)
|
||||
|
||||
if within_period and not screen_on:
|
||||
# 在亮屏时段但屏幕灭着 → 立即亮屏(自动恢复)
|
||||
log.info("在亮屏时间段内但屏幕已熄灭 → 立即亮屏")
|
||||
self.audit.log("亮屏", f"在亮屏时间段 ({period_desc}) 内检测到屏幕熄灭,自动恢复")
|
||||
self.adb.screen_on()
|
||||
return True
|
||||
|
||||
if not within_period and screen_on and not gpio_triggered:
|
||||
# 非亮屏时段、非 GPIO 触发、屏幕亮着 → 超时后息屏
|
||||
on_duration = self.adb.get_screen_on_duration()
|
||||
if on_duration >= delay_sec:
|
||||
log.info("不在亮屏时段(已亮 %.0fs)→ 自动息屏", on_duration)
|
||||
self.audit.log("息屏", f"不在亮屏时间段 ({period_desc}),超时 {config.get('auto_sleep_delay_minutes')} 分钟")
|
||||
self.adb.screen_off()
|
||||
return False
|
||||
else:
|
||||
remaining = delay_sec - on_duration
|
||||
if remaining <= 10:
|
||||
log.info("不在亮屏时段,%.0f 秒后将息屏", remaining)
|
||||
|
||||
elif within_period and screen_on:
|
||||
# 在亮屏时段且屏幕已亮 — 一切正常
|
||||
if prev_screen_on is False:
|
||||
pass
|
||||
|
||||
return screen_on
|
||||
|
||||
def _fast_check(self):
|
||||
"""每秒轻量决策(不查 ADB,纯内存 evaluate)。
|
||||
|
||||
仅在缓存状态显示屏幕亮、且时间条件接近超时时,
|
||||
才做一次 ADB 验证并执行息屏,避免 30 秒 tick 的超时误差。
|
||||
"""
|
||||
config = self.config_mgr.get()
|
||||
delay_sec = config.get("auto_sleep_delay_minutes", 5) * 60
|
||||
on_duration = self.adb.get_screen_on_duration() # 纯内存
|
||||
if on_duration < delay_sec:
|
||||
return # 远未超时,跳过
|
||||
|
||||
# 缓存状态显示屏幕灭 → 无需操作
|
||||
if not self._status.get("screen_on", False):
|
||||
return
|
||||
|
||||
within_period = TimeChecker.is_within_any_period(
|
||||
config.get("screen_on_periods", []))
|
||||
gpio_elapsed = self.gpio.seconds_since_last_event if self.gpio else 999
|
||||
gpio_countdown = config.get("gpio_countdown_seconds", 60)
|
||||
|
||||
decision = self.screen_mgr.evaluate(
|
||||
actual_screen_on=True,
|
||||
within_period=within_period,
|
||||
gpio_elapsed=gpio_elapsed,
|
||||
gpio_countdown=gpio_countdown,
|
||||
screen_on_duration=on_duration,
|
||||
auto_sleep_delay=delay_sec,
|
||||
)
|
||||
|
||||
if decision["action"] == "off":
|
||||
# 验证屏幕是否真的亮着(一次 ADB 调用)
|
||||
is_on, _ = self.adb.get_screen_state()
|
||||
if is_on:
|
||||
log.info("快速决策 → 息屏 (%s)", decision["reason"])
|
||||
self.screen_mgr.clear()
|
||||
self.adb.screen_off()
|
||||
self._update_status(screen_on=False)
|
||||
|
||||
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
# 入口
|
||||
|
||||
Reference in New Issue
Block a user