Files
auto_control/core/device_pool.py
T

182 lines
6.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""设备池(STF 替代数据源):SQLite devices 表 = 设备清单,本地 adb = 在线状态。
阶段 0:纯新增模块,不改现有行为。task_manager / web_server 的 STF 调用
在阶段 1 逐个切换到本模块,阶段 3 摘除 STF 后删除 seed 相关代码。
设计:
- list_configured() — devices 表里 enabled 的设备(清单,管理页维护)
- list_online() — 本机 `adb devices` 里 state=device 的设备(实时)
- list_ready() — configured ∩ online(调度用,取代 STF list_free_devices)
- CRUD — add/update/remove/set_enabled(设备池管理页用)
- 首次启动自动从 STF 导入现有设备(一次性,app_meta 标记防重复)
红线:全模块不 connect / 不 kill-server / 不 disconnect,遵守既有技术约束。
后台线程(task_manager 等)调用时由本模块自行推 app context,调用方无需关心。
"""
import time
from sqlalchemy import text
from config import USB_ADB_HOST, USB_ADB_PORT
from core.adb_helper import _adb, _adb_remote
from core.logger import get_logger
from core.models import db, Device
_log = get_logger("core.device_pool")
_app = None
def init_app(app):
"""web_server 启动时调用:绑定 Flask app,并做首次设备导入。"""
global _app
_app = app
try:
_seed_from_stf_if_empty()
except Exception as e:
_log.warning(f"设备池初始化异常(不影响启动): {e}")
def _ctx():
"""后台线程访问 db 需自行推 app context(与 task_manager._db 同思路)。"""
if _app is None:
raise RuntimeError("device_pool 未关联 Flask app(web_server 启动时调用 init_app)")
return _app.app_context()
# ================== 清单 ==================
def list_configured(enabled_only=True):
"""devices 表里的设备清单(serial 列表)。"""
with _ctx():
q = Device.query
if enabled_only:
q = q.filter_by(enabled=True)
return [d.serial for d in q.order_by(Device.serial).all()]
def list_devices():
"""全量设备行(设备池管理页用)。"""
with _ctx():
return [d.to_dict() for d in Device.query.order_by(Device.serial).all()]
# ================== 在线状态 ==================
def list_online():
"""本机 adb 中 state=device 的设备 + 池内 USB 设备(经 220 远程 adb server)。
池内无 USB 设备(serial 无冒号)时不查远程 server,零额外开销。
"""
out = _adb("devices") or ""
serials = []
for line in out.splitlines()[1:]:
parts = line.split()
if len(parts) >= 2 and parts[0] and parts[1] == "device":
serials.append(parts[0])
# 池内有 USB 设备时,补查远程 adb server(220,host 网络 5037 全接口监听)
try:
usb = [s for s in list_configured() if ":" not in s]
except Exception:
usb = []
if usb:
try:
out = _adb_remote(USB_ADB_HOST, USB_ADB_PORT, "devices") or ""
for line in out.splitlines()[1:]:
parts = line.split()
if len(parts) >= 2 and parts[0] and parts[1] == "device":
if parts[0] not in serials:
serials.append(parts[0])
except Exception:
pass
return serials
def is_online(serial):
"""设备当前是否在线(本机 adb 可达且状态正常)。"""
return serial in list_online()
def list_ready():
"""可调度设备:清单 ∩ 在线(取代 STF list_free_devices)。"""
online = set(list_online())
return [s for s in list_configured() if s in online]
# ================== 管理(CRUD) ==================
def add_device(serial, name="", note="", enabled=True):
"""添加/更新设备(upsert)。返回 True 新增 / False 已存在并更新。"""
serial = (serial or "").strip()
if not serial:
return False
with _ctx():
d = Device.query.get(serial)
if d:
d.name, d.note, d.enabled = name or "", note or "", enabled
db.session.commit()
return False
db.session.add(Device(serial=serial, name=name or "", note=note or "",
enabled=enabled,
created_at=time.strftime("%Y-%m-%d %H:%M")))
db.session.commit()
_log.info(f"设备池新增设备: {serial}")
return True
def remove_device(serial):
"""删除设备。返回是否删除成功。"""
with _ctx():
d = Device.query.get(serial)
if not d:
return False
db.session.delete(d)
db.session.commit()
_log.info(f"设备池删除设备: {serial}")
return True
def set_enabled(serial, enabled):
"""启用/停用设备(停用后不参与调度)。返回是否成功。"""
with _ctx():
d = Device.query.get(serial)
if not d:
return False
d.enabled = enabled
db.session.commit()
return True
# ================== 首次导入(阶段 3 摘除 STF 后删除) ==================
def _seed_from_stf_if_empty():
"""首次启动把 STF 现有设备导入本地清单。
条件:device 表为空 且 未导入过(app_meta 标记)。STF 不可达时跳过,
不写标记,下次启动重试。摘除 STF 后此函数随 stf_client 一起删除。
"""
try:
from core.stf_client import STFClient
except Exception:
return
with _ctx():
if Device.query.count() > 0:
return
seeded = db.session.execute(text(
"SELECT value FROM app_meta WHERE key='devices_seeded_from_stf'")).scalar()
if seeded:
return
try:
# 导入全部 serial(不限于 present):STF 池 = 配置的舰队,
# present=false 的是掉线/陈旧记录,导入后显示离线,管理页可删
serials = [d["serial"] for d in STFClient().list_all_devices()]
except Exception as e:
_log.warning(f"从 STF 导入设备清单失败(可稍后手动添加): {e}")
return
if not serials:
_log.warning("STF 无在线设备,跳过设备池首次导入")
return
now = time.strftime("%Y-%m-%d %H:%M")
for s in serials:
db.session.add(Device(serial=s, created_at=now))
db.session.execute(text(
"INSERT OR REPLACE INTO app_meta(key,value) VALUES('devices_seeded_from_stf','1')"))
db.session.commit()
_log.info(f"设备池首次导入 {len(serials)} 台设备(来源 STF)")