base_control.py 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. # 定义抽象类
  2. import json
  3. import logging
  4. import tempfile
  5. from abc import ABC, abstractmethod
  6. import uiautomator2 as u2
  7. class AbsControl(ABC):
  8. def __init__(self, name: str):
  9. """
  10. # :param : 设备序列号。 例如 127.0.0.1:6555
  11. # 可以通过 `adb devices` 获取
  12. """
  13. # u2.logger.setLevel(logging.DEBUG)
  14. # 名称
  15. self.name = name
  16. self.d = None
  17. # 屏幕高度
  18. self.height = 0
  19. # 屏幕宽度
  20. self.width = 0
  21. self._points = {}
  22. self.point_path = 'point.json'
  23. self._func = []
  24. self._log_func = None
  25. def connect_adb(self, serial: str):
  26. """
  27. 连接设备
  28. """
  29. self.d = u2.connect(serial)
  30. self.enable_click_monitor()
  31. # 如果连接成功,返回截图,否则返回None
  32. def screenshot(self):
  33. """
  34. 截图
  35. """
  36. # 获取系统/tmp路径
  37. tmp = tempfile.gettempdir()
  38. return self.d.screenshot(f"{tmp}/{self.name}.png")
  39. def enable_click_monitor(self):
  40. """
  41. 启用点击监控,在屏幕上显示点击位置
  42. """
  43. # self.d.settings['operation_delay'] = (0.5, 0.5) # 增加操作延迟以便观察
  44. self.d.debug = True
  45. self.d.toast.show('点击监控已启用') # 显示提示
  46. # 确保有悬浮窗权限
  47. self.d.set_fastinput_ime(True) # 启用ime
  48. self.d.show_float_window(True) # 显示悬浮窗
  49. # 可选:打开开发者选项中的"指针位置"
  50. self.d.shell('settings put system pointer_location 1')
  51. def to_top_swipe(self, sleep=0.1, times=2):
  52. """
  53. 滑动到屏幕最顶部,通过多次滑动确保到达顶部
  54. :param sleep: 滑动后等待时间
  55. :param times: 滑动次数
  56. :return:
  57. """
  58. width, height = self.get_screen_size()
  59. # 循环滑动直到无法继续滑动
  60. for _ in range(times):
  61. self.d.swipe(width // 2, height * 0.8, width // 2, height) # 向上滑动
  62. # 短暂等待确保滑动完成
  63. self.d.sleep(sleep)
  64. def click_point(self, x: int, y: int):
  65. """
  66. 点击指定坐标
  67. :param x: x坐标
  68. :param y: y坐标
  69. """
  70. self.d.click(x, y)
  71. def click_xpath(self, xpath: str):
  72. """
  73. 点击指定xpath
  74. """
  75. el = self.d.xpath(xpath).get()
  76. if el:
  77. el.click()
  78. else:
  79. logging.warning(f"未找到元素: {xpath}")
  80. def to_next_swipe(self, sleep=0.1):
  81. """
  82. 向上滑动一屏
  83. """
  84. width, height = self.get_screen_size()
  85. # 从屏幕下方向上滑动到顶部
  86. self.d.swipe(width // 2, height * 0.8, width // 2, height * 0.2) # 向上滑动
  87. # 短暂等待确保滑动完成
  88. self.d.sleep(sleep)
  89. def get_screen_size(self):
  90. """
  91. 获取屏幕尺寸,宽度和高度
  92. """
  93. self.width, self.height = self.d.window_size()
  94. logging.info(f"屏幕尺寸: {self.width}x{self.height}")
  95. return self.width, self.height
  96. def drag_slider_ext(self, start_x: int, start_y: int, end_x: int, end_y: int, steps: int = 50):
  97. """
  98. 精确拖动滑块
  99. :param start_x: 起始x坐标
  100. :param start_y: 起始y坐标
  101. :param end_x: 结束x坐标
  102. :param end_y: 结束y坐标
  103. :param steps: 步数,值越大滑动越平滑
  104. """
  105. self.d.swipe_ext(start_x, start_y, end_x, end_y, steps)
  106. def add_point(self, point=None):
  107. """
  108. 添加坐标点
  109. point = {
  110. "name": "",
  111. "x": 0,
  112. "y": 0,
  113. "desc": "",
  114. "xpath": ""
  115. }
  116. """
  117. logging.info("保存坐标点: %s", point)
  118. self._points[point['name']] = point
  119. def print_log(self, msg):
  120. """
  121. 打印日志
  122. """
  123. logging.info(f'>>111>{msg}')
  124. print(f'>222>>{msg},{self._log_func}')
  125. if self._log_func:
  126. self._log_func(msg)
  127. def add_func(self, func):
  128. """
  129. 添加坐标点采集函数
  130. """
  131. self._func.append(func)
  132. def set_log_func(self, func):
  133. """
  134. 添加日志函数
  135. """
  136. self._log_func = func
  137. def save_point(self):
  138. """
  139. 保存坐标
  140. """
  141. self.to_top_swipe()
  142. self.d.sleep(2)
  143. for func in self._func:
  144. func()
  145. __tmp__ = {}
  146. for k, v in self._points.items():
  147. __tmp__[k] = v
  148. with open(self.point_path, "w", encoding="utf-8") as f:
  149. json.dump(__tmp__, f, ensure_ascii=False, indent=4)
  150. def get_point(self, info: dict):
  151. """
  152. 获取坐标点
  153. :param info: 数据结构
  154. {
  155. "name": "btn_mairu_kaiduo",
  156. "desc": "买入/开多",
  157. "xpath": '//*[@content-desc="买入/开多"]'
  158. }
  159. :return: x, y, el
  160. """
  161. x, y, el = self.get_point_by_xpath(info['xpath'])
  162. self.add_point({
  163. "name": info['name'],
  164. "x": x,
  165. "y": y,
  166. "desc": info['desc'],
  167. "xpath": info['xpath'],
  168. })
  169. return x, y, el
  170. def get_point_by_xpath(self, xpath: str):
  171. """
  172. :param xpath: xpath
  173. 获取坐标第一个匹配的点
  174. info 数据结构
  175. :return: x, y, el
  176. """
  177. el = self.d.xpath(xpath).get()
  178. x, y = el.center()
  179. return x, y, el
  180. def get_points_by_xpath(self, xpath: str):
  181. """
  182. 获取所有坐标点
  183. info 数据结构
  184. """
  185. els = self.d.xpath(xpath).all()
  186. items = []
  187. for el in els:
  188. x, y = el.center()
  189. items.append((x, y, el))
  190. return items
  191. def check_element_exists(self, xpath: str, timeout: float = 0.05) -> bool:
  192. """
  193. 判断指定XPath的元素是否存在
  194. :param xpath: 元素的XPath
  195. :param timeout: 超时时间(秒)
  196. :return: 是否存在
  197. """
  198. self.d.sleep(timeout)
  199. return self.d.xpath(xpath).exists
  200. def input_xpath(self, xpath: str, text: str, clear: bool = True):
  201. """
  202. 给指定xpath的输入框输入文本
  203. :param xpath: 输入框的xpath
  204. :param text: 要输入的文本
  205. :param clear: 输入前是否清空原有内容
  206. """
  207. element = self.d.xpath(xpath).get()
  208. if element:
  209. if clear:
  210. element.text.set_text("") # 清空原有内容
  211. element.set_text(text) # 输入新内容
  212. else:
  213. logging.warning(f"未找到输入框: {xpath}")
  214. def input_by_position(self, x: int, y: int, text: str, clear: bool = True):
  215. """
  216. 通过坐标点击并在输入框中输入文本
  217. :param x: 输入框x坐标
  218. :param y: 输入框y坐标
  219. :param text: 要输入的文本
  220. :param clear: 是否清空原有内容
  221. """
  222. self.click_point(x, y) # 先点击获取焦点
  223. if clear:
  224. self.d.clear_text() # 清空原有内容
  225. self.d.send_keys(text) # 输入新文本
  226. @abstractmethod
  227. def event_f1(self):
  228. """
  229. F1 开仓界面,仓位滑竿百分比(30-60)
  230. """
  231. pass
  232. @abstractmethod
  233. def event_f2(self):
  234. """
  235. F2 确认开仓,开多 限价职中间数值
  236. 检查撤销限价委托单
  237. """
  238. pass
  239. @abstractmethod
  240. def event_f3(self):
  241. """
  242. F3 平仓界面 平空,仓位滑竿百分比(90-100)确认开仓,限价职中间数值
  243. """
  244. pass
  245. @abstractmethod
  246. def event_f4(self):
  247. """
  248. F4 确认平仓 平多 限价取中间数值 检查撤销限价委托单
  249. """
  250. pass
  251. @abstractmethod
  252. def event_f5(self):
  253. """
  254. F5 开仓界面 仓位滑竿百分比(30-60)
  255. """
  256. pass
  257. @abstractmethod
  258. def event_f6(self):
  259. """
  260. F6 确认开仓, 开空 限价值中间数值
  261. 检查撤销限价委托单
  262. """
  263. pass
  264. @abstractmethod
  265. def event_f7(self):
  266. """
  267. F7 平仓界面二仓位滑竿百分比(90-100)
  268. """
  269. pass
  270. @abstractmethod
  271. def event_f8(self):
  272. """
  273. F8 确认平仓, 平空 限价职中间数值
  274. 检查撤销限价委托单
  275. """
  276. pass