OptionAction.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596
  1. # -*- coding:utf-8 -*-
  2. import os
  3. from UIT_PathManage import UITPathManage
  4. from BaseLog import CBaseLog
  5. from ExtraData import CExtraData
  6. from OptionExcel import COptionExcel
  7. from OptionConfig import COptionConfig
  8. from OptionFocus import COptionFocus
  9. from OptionOCR import COptionOCR
  10. from ssat_sdk.tv_detect import *
  11. from ssat_sdk.device_manage.capturecard_manager import CCardManager
  12. from ssat_sdk.utils.string_util import strcmp
  13. g_level = ['First', 'Second', 'Third', 'Fourth', 'Fifth', 'Sixth',
  14. 'Seventh', 'Eighth', 'Ninth', 'Tenth', 'Eleventh', 'Twelfth']
  15. def takePicture():
  16. img = os.path.join(getSATTmpDIR(), "menutree_runpath.png")
  17. COptionAction.__ccard.takePicture(img)
  18. return img
  19. def strSplit(text):
  20. ret = []
  21. str_int = ''
  22. str_ch = ''
  23. ch_last = ' '
  24. for ch in text:
  25. if 47 < ord(ch) < 58:
  26. str_int += ch
  27. if str_ch.__len__():
  28. ret.append(str_ch)
  29. str_ch = ''
  30. else:
  31. if 47 < ord(ch_last) < 58 and ch == '.':
  32. str_int += ch
  33. if str_ch.__len__():
  34. ret.append(str_ch)
  35. str_ch = ''
  36. else:
  37. str_ch += ch
  38. if str_int.__len__():
  39. ret.append(str_int)
  40. str_int = ''
  41. ch_last = ch
  42. if str_ch.__len__():
  43. ret.append(str_ch)
  44. if str_int.__len__():
  45. ret.append(str_int)
  46. return ret
  47. # 注意:所有不对外暴露的变量和函数需要私有化,以明确哪些接口和参数是对外的。
  48. # 这样便于后期维护时,根据对外的变量和函数来做处理。
  49. class COptionAction(CBaseLog):
  50. # ==============设备对象============== #
  51. # 红老鼠遥控对象;
  52. __redRat3 = TvOperator()
  53. # 创建视频采集对象
  54. __ccard = CCardManager()
  55. # 图片切割对象
  56. __imgCMP = ImageCMP()
  57. # 发送鲜活键;
  58. @staticmethod
  59. def __sendAliveKey(aliveKey):
  60. if aliveKey is not None and aliveKey.__len__() > 0:
  61. COptionAction.__redRat3.sendKey(aliveKey, 1, 0.1)
  62. def __init__(self, optionName, optionValue, optionConfig, optionExcel):
  63. # 层级位置;
  64. self.__pos = 0
  65. # 目标option;
  66. self.__optionName = optionName
  67. # __optionValue可空;
  68. self.__optionValue = optionValue
  69. self.__optionExcel = optionExcel
  70. self.__optionConfig = optionConfig
  71. # 焦点定位及文字识别;
  72. self.__optionFocus = COptionFocus(optionConfig)
  73. self.__optionOCR = COptionOCR(optionConfig, optionExcel)
  74. if self.__optionExcel is None:
  75. self.error(u"表格对象空")
  76. # ==============常用对象数据==============;
  77. self.__optionPaths = self.__optionExcel.getOptionPaths(self.__optionName)
  78. # 如果__optionValue空则不取value表任何内容;
  79. if self.__optionValue != "":
  80. self.__optionValues = self.__optionExcel.getOptionValueInfo(self.__optionName, self.__optionValue)
  81. self.__optionInfo = self.__optionExcel.getOptionInfo(self.__optionName)
  82. # 当前状态下的变量,与__pos对应;
  83. self.__curOptionName = ''
  84. self.__curOptionInfo = None
  85. # 获取一次当前层级信息;
  86. self.getCurOptionInfo()
  87. @property
  88. def pos(self):
  89. return self.__pos
  90. @property
  91. def curOptionName(self):
  92. return self.__curOptionName
  93. @property
  94. def curOptionInfo(self):
  95. return self.__curOptionInfo
  96. '''
  97. 函数:调用根节点快捷键(中间节点不需要快捷键;);
  98. 参数:
  99. 返回:
  100. '''
  101. def callFirstOptionShortCutKey(self):
  102. if 'shortcut_key' in self.__optionPaths['First']:
  103. COptionAction.__redRat3.sendKey(self.__optionPaths['First']['shortcut_key'])
  104. else:
  105. COptionAction.__redRat3.sendKey(self.__optionPaths['First']['parent'])
  106. self.warn(u"表格没有shortcut_key字段,执行默认的parent按键:%s" % self.__optionPaths['First']['parent'])
  107. '''
  108. 函数:调用当前结点的toparent_key
  109. 参数:
  110. 返回:
  111. '''
  112. def callCurOptionBackKey(self, curOptionName):
  113. curOptionInfo = self.__optionExcel.getOptionInfo(curOptionName)
  114. if 'toparent_key' in self.__optionPaths[curOptionInfo['level']]:
  115. COptionAction.__redRat3.sendKey(self.__optionPaths[curOptionInfo['level']]['toparent_key'])
  116. else:
  117. self.error(u"表格没有toparent_key字段,执行默认按键return")
  118. COptionAction.__redRat3.sendKey('return')
  119. '''
  120. 函数:打开指定的option(到达option后,执行enter_key进入)。
  121. 参数:
  122. 返回:
  123. 示例:
  124. 测试:。
  125. '''
  126. def openOption(self, optionName):
  127. pass
  128. '''
  129. 函数:是否在父节点菜单上。一般在执行了callFirstOptionShortCutKey后调用;
  130. 参数:
  131. 返回:
  132. 注意:由于所有父节点上的子项都共用一个图片定位参数,所以只要随意一个父节点的子项option即可获取定位参数;
  133. 示例:
  134. 测试:。
  135. '''
  136. def isOnFirstOption(self):
  137. pic = takePicture()
  138. return self.__optionFocus.findFocusByIcon(pic, self.__optionPaths['First']['option'])[0]
  139. '''
  140. 函数:是否在目标节点(移动时,每一个目标option,并不仅是终点目标option)上.
  141. 说明:
  142. 每次移动到下一目标节点(option)上时,self.__pos + 1,表示移动到下一层路径。
  143. 当self.__pos >= self.__optionPaths.__len__()时,表示到达value表格;
  144. 所以,该类的重点在self.__pos的移动;
  145. 参数:
  146. 返回:Boolean, 识别的文本/数字;
  147. 示例:
  148. 测试:。
  149. '''
  150. def isOnTargetOption(self):
  151. # 析出参数;
  152. curLevel = g_level[self.__pos]
  153. curParent = self.__optionPaths[curLevel]['parent']
  154. curOption = self.__optionPaths[curLevel]['option']
  155. curOthers = json.loads(self.__optionPaths[curLevel]['others'])
  156. firstParent = self.__optionPaths['First']['option']
  157. # 是否在value表中;
  158. if self.__optionValue == "":
  159. isValueSheet = False
  160. else:
  161. isValueSheet = True if self.__pos >= self.__optionPaths.__len__() else False
  162. self.info(u"当前层级在:%s" % ("value表" if isValueSheet else "路径表"))
  163. # 获取文本识别的参数;
  164. ocrConfigList = self.__optionConfig.getOptionOCRConfig(curOption)
  165. ocrThreshold = self.__optionConfig.getThresholdDict(firstParent) # 注意,此处使用firstParent;
  166. # 获取当前option的ocr值/value name下所有ocr值;
  167. if isValueSheet:
  168. optionTextList = self.__optionExcel.getOptionValueText(curOption)
  169. else:
  170. optionTextList = self.__optionExcel.getOptionText(curOption)
  171. # 获取option下所有兄弟项的ocr:option字典内容;
  172. siblingTextDict = self.__optionExcel.getOptionAllSiblingItemDict(curOption, not isValueSheet)
  173. # 获取所有option兄弟项(包括自己)的ocr值;
  174. siblingTextList = list(siblingTextDict.keys())
  175. # 是否获取数值文本;
  176. isNumberText = False
  177. # 如果是value表,且兄弟项文本为range
  178. # 注:value表中的option实际并没有兄弟项,取的是所有value项
  179. if isValueSheet and siblingTextList.__len__():
  180. if siblingTextList[0].startswith('range('):
  181. self.info(u"识别的内容是value表数字内容(range(0,xx)类型)")
  182. isNumberText = True
  183. # 是否为静态焦点识别(动态则为跑马灯);
  184. if curOthers.__len__() and 'marquee' in curOthers:
  185. return self.__getDynamicPicText(curOption, optionTextList, siblingTextList, ocrConfigList,
  186. ocrThreshold, curOthers['marquee'],
  187. isNumberText, isValueSheet)
  188. else:
  189. return self.__getStaticPicText(takePicture(), curOption, optionTextList, siblingTextList, ocrConfigList,
  190. ocrThreshold,
  191. isNumberText, isValueSheet)
  192. # endif
  193. '''
  194. 函数:移动到下一兄弟节点;
  195. 参数:
  196. 返回:
  197. '''
  198. def move2NextSiblingOption(self):
  199. # 获取当前option信息;
  200. self.getCurOptionInfo()
  201. # 析出move key;
  202. optionMoveKey = self.__curOptionInfo['option_move_key']
  203. if optionMoveKey.__len__() == 0:
  204. self.sendKey(self.__curOptionInfo['move_key'][1], 1, 0.1)
  205. else:
  206. self.sendKey(optionMoveKey[1], 1, 0.1)
  207. '''
  208. 函数:移动到上一兄弟节点
  209. 参数:
  210. 返回:
  211. '''
  212. def move2PrevSiblingOption(self):
  213. # 获取当前option信息;
  214. self.getCurOptionInfo()
  215. # 析出move key;
  216. optionMoveKey = self.__curOptionInfo['option_move_key']
  217. if optionMoveKey.__len__() == 0:
  218. self.sendKey(self.__curOptionInfo['move_key'][0], 1, 0.1)
  219. else:
  220. self.sendKey(optionMoveKey[0], 1, 0.1)
  221. '''
  222. 函数:返回到父节点
  223. 参数:
  224. 返回:
  225. '''
  226. def back2ParentOption(self):
  227. # 获取当前option信息;
  228. self.getCurOptionInfo()
  229. # 析出move key;
  230. backKey = self.__curOptionInfo['back_key']
  231. if backKey.__len__() == 0:
  232. self.sendKey('back', 1, 0.1)
  233. else:
  234. self.sendKey(backKey, 1, 0.1)
  235. '''
  236. 函数:进入当前节点;
  237. 参数:
  238. 返回:
  239. '''
  240. def enterOption(self):
  241. # 获取当前option信息;
  242. self.getCurOptionInfo()
  243. # 析出enter key;
  244. optionEnterKey = self.__curOptionInfo['option_enter_key']
  245. if optionEnterKey.__len__() == 0:
  246. self.sendKey(self.__curOptionInfo['enter_key'], 1, 0.)
  247. else:
  248. self.sendKey(optionEnterKey, 1, 0.1)
  249. '''
  250. 函数:设置当前option的值;
  251. 参数:
  252. 返回:
  253. '''
  254. def setCurOptionValue(self, value):
  255. # 获取当前option信息;
  256. self.getCurOptionInfo()
  257. # 获取当前option的value表信息;
  258. curOptionValueInfo = self.__optionExcel.getOptionValueInfo(self.__curOptionName, value)
  259. '''
  260. 函数:
  261. 参数:
  262. 返回:
  263. '''
  264. def getCurOptionInfo(self):
  265. if self.__pos >= self.__optionPaths.__len__():
  266. self.warn(u"已到达value节点,无法获取路径信息")
  267. return False
  268. # 只有第一次或层级移动了才需要更新;
  269. if self.__curOptionInfo is None or self.__pos != self.__curOptionInfo['layers']:
  270. self.__curOptionName = self.__optionPaths[g_level[self.__pos]]['option']
  271. self.__curOptionInfo = self.__optionExcel.getOptionInfo(self.__curOptionName, self.__optionPaths)
  272. return True
  273. '''
  274. 函数:获取静态图片文本内容
  275. 参数:
  276. 注意:
  277. 返回:
  278. 测试:。
  279. '''
  280. def __getStaticPicText(self, pic, optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold,
  281. isNumberText,
  282. isValueSheet, aliveKey=None):
  283. # 获取图片焦点框;
  284. found, focusBox = self.__optionFocus.findFocusByIcon(pic, optionName, isValueSheet)
  285. if found is False:
  286. self.debug(u"未找到[%s]聚集框" % optionName)
  287. return False, None
  288. # 如果有鲜活键;
  289. COptionAction.__sendAliveKey(aliveKey)
  290. # 获取文本区域框;
  291. textBox = self.__optionFocus.getFocusTextBox(optionName, focusBox, isValueSheet)
  292. # 配置文本图片路径,保存文本区域截图;
  293. text_pic = os.path.join(getSATTmpDIR(), "meuttree_area_text.png")
  294. self.__imgCMP.saveCropImage(pic, text_pic, textBox)
  295. if not os.path.exists(text_pic):
  296. self.error(u"%s截取文本图片失败:%s" % (optionName, text_pic))
  297. return False, None
  298. # 是否在某个兄弟项中;
  299. isOnSibling = False
  300. # 遍历所有ocr识别选项;
  301. for ocrConfig in ocrConfigList:
  302. # 如果有鲜活键;
  303. COptionAction.__sendAliveKey(aliveKey)
  304. # 识别出当前聚焦文本;
  305. curFocusText = self.__optionOCR.getImageText(text_pic, ocrConfig, ocrThreshold)
  306. # 判断识别文本是来正常;
  307. if curFocusText == "ERR<Exp>" or curFocusText.__len__() == 0:
  308. continue
  309. self.info("[%s]当前识别出的文本=%s" % (optionName, curFocusText))
  310. # 是否取数字文本;
  311. if isNumberText is True:
  312. # 特殊情况处理:某些情况下,会将包含数字以外的区域一起识别;
  313. curFocusText = curFocusText.strip('>')
  314. # 将数字分组
  315. numberTextList = strSplit(curFocusText)
  316. # 只判断最后一位是否为数字;
  317. if numberTextList.__len__() < 1:
  318. self.error(u"当前识别的文本不是数字文本:%s" % curFocusText)
  319. continue
  320. try:
  321. numberText = numberTextList[numberTextList.__len__() - 1]
  322. return True, float(numberText)
  323. except Exception:
  324. continue
  325. else:
  326. # 当前option识别的文本与所有兄弟项文本比较;
  327. for siblingText in siblingTextList:
  328. # 转为小写,保证所有比较都是小写;
  329. siblingText = siblingText.lower()
  330. # 兄弟项文本是否被包含在curFocusText中或相同;
  331. if siblingText in curFocusText or strcmp(siblingText, curFocusText):
  332. isOnSibling = True
  333. self.info(u"当前焦点在[%s], 目标焦点为[%s]" % (siblingText, optionName))
  334. # 再判断,该兄弟项是否为目标节点(curOption);
  335. for optionText in optionTextList:
  336. optionText = optionText.lower()
  337. # 若当前兄弟项为目标option返回True、文本;
  338. if strcmp(optionText, siblingText):
  339. return True, curFocusText
  340. # endif
  341. # endfor
  342. # 在兄弟项中,退出循环;
  343. break
  344. # endif
  345. # endfor
  346. if isOnSibling is False:
  347. self.error(u"未聚集到任何[%s]的兄弟项中" % optionName)
  348. else:
  349. self.info("未聚集到目标节点[%s],当前文本=%s" % (optionName, curFocusText))
  350. return False, curFocusText
  351. # endif
  352. # endfor
  353. # 默认返回;
  354. return False, 0 if isNumberText else ""
  355. # 获取动态图片文本内容;
  356. '''
  357. 函数:
  358. 参数:
  359. 返回:
  360. '''
  361. def __getDynamicPicText(self, optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, marqueeDict,
  362. isNumberText, isValueSheet):
  363. # 判断图片是否动态:截图两次,判断两次文本内容是否相同;
  364. firstRetsult, firstText = self.__getStaticPicText(takePicture(), optionName, optionTextList,
  365. siblingTextList, ocrConfigList,
  366. ocrThreshold, isNumberText, isValueSheet)
  367. if firstRetsult is False:
  368. self.error(u"[%s]第一次截图未识别出聚焦框" % optionName)
  369. return False, None
  370. # 发送鲜活键, 保证界面鲜活;
  371. COptionAction.__sendAliveKey(marqueeDict['alive_key'])
  372. # 第二次截图;
  373. secondRetsult, secondText = self.__getStaticPicText(takePicture(), optionName, optionTextList,
  374. siblingTextList, ocrConfigList,
  375. ocrThreshold, isNumberText, isValueSheet)
  376. if secondRetsult is False:
  377. self.error(u"[%s]第二次截图未识别出聚焦框" % optionName)
  378. return False, None
  379. # 发送鲜活键, 保证界面鲜活;
  380. COptionAction.__sendAliveKey(marqueeDict['alive_key'])
  381. # 比较两文本是否相同;
  382. if firstText.__len__() and firstText == secondText:
  383. self.info(u"截图两次,文本(%s)识别相同,聚焦的不是跑马灯Option" % firstText)
  384. return False, firstText
  385. # 文本不相同,为动态图片;
  386. menuList = marqueeDict['menu']
  387. # 如果只有一项跑马灯,且目标option亦在跑马灯列表中,则直接返回成功结果
  388. if menuList.__len__() == 1 and (optionName in menuList):
  389. self.info(u"该层菜单只有一项跑马灯, 找到即成功返回")
  390. return True, firstText
  391. picList = []
  392. # 如果有多项同级option都是跑马灯, 要成功识别文本需要间隔截图5次(大概会成功截图到最全的文本);
  393. for i in range(0, 5):
  394. picList.append(takePicture())
  395. # 间隔多久截图;
  396. time.sleep(marqueeDict['sleep_time'])
  397. # 发送鲜活键;
  398. COptionAction.__sendAliveKey(marqueeDict['alive_key'])
  399. ocrTextList = []
  400. # 对截图进行文本识别分析;
  401. for pic in picList:
  402. result, text = self.__getStaticPicText(pic, optionName, optionTextList, siblingTextList, ocrConfigList,
  403. ocrThreshold, isNumberText, isValueSheet, marqueeDict['alive_key'])
  404. if result is True:
  405. ocrTextList.append(text)
  406. # 发送鲜活键;
  407. COptionAction.__sendAliveKey(marqueeDict['alive_key'])
  408. # 过滤重复的字符;
  409. ocrTextList = self.__removeDuplicateString(ocrTextList)
  410. self.info(u"识别到的跑马灯ocr文字列表:%s" % ocrTextList)
  411. # 获取动态文本的option字典;
  412. dynamicOptionOcrDict = self.__getOptionInfoDict(menuList)
  413. self.info(u"获取到的跑马灯Option对应的ocr字典:%s" % dynamicOptionOcrDict)
  414. # 遍历:识别结果与xls结果进行比较;
  415. for dynamicOption in dynamicOptionOcrDict:
  416. dynamicOptionOcrList = dynamicOptionOcrDict[dynamicOption]
  417. for dynamicOptionOcr in dynamicOptionOcrList:
  418. # 只要有3张满足,判断找到option;
  419. count = 0
  420. for ocrText in ocrTextList:
  421. if ocrText.lower() in dynamicOptionOcr:
  422. count += 1
  423. if count >= 3 and optionName == dynamicOption:
  424. return True, ocrText
  425. else:
  426. self.info(u"当前聚焦的跑马灯实际为:%s" % dynamicOption)
  427. return False, ocrText
  428. # endfor
  429. # endfor
  430. self.info("未能识别到当前聚焦的跑马灯Option")
  431. return False, 0 if isNumberText else None
  432. '''
  433. 函数:
  434. 参数:
  435. 返回:
  436. '''
  437. def __getOptionInfoDict(self, optionNameList):
  438. OptionInfoDict = {}
  439. for optionName in optionNameList:
  440. found, optionDict = self.__optionExcel(optionName)
  441. if found:
  442. OptionInfoDict[optionName] = optionDict
  443. # endif
  444. # endfor
  445. return OptionInfoDict
  446. # 找到两个字符串左边或者右边相同的部分
  447. '''
  448. 函数:
  449. 参数:
  450. 返回:
  451. '''
  452. def __findDuplicateString(self, str1, str2, direction="right"):
  453. index = 0
  454. if direction == "right":
  455. while True:
  456. index -= 1
  457. if abs(index) > str1.__len__() or abs(index) > str2.__len__():
  458. break
  459. if not str1[index] == str2[index]:
  460. break
  461. if index == -1:
  462. self.info(u"没有找到重复文本")
  463. return ""
  464. return str1[index + 1:]
  465. elif direction == "left":
  466. while True:
  467. if abs(index) >= str1.__len__() or abs(index) >= str2.__len__():
  468. break
  469. if not str1[index] == str2[index]:
  470. break
  471. index += 1
  472. return str1[:index]
  473. # 去掉字符串数组中每个字符串 左边或右边相同的部分
  474. '''
  475. 函数:
  476. 参数:
  477. 返回:
  478. '''
  479. def __removeDuplicateString(self, strList):
  480. finishedList = strList
  481. directionList = ["left", "right"]
  482. for direction in directionList:
  483. same_str = self.__findDuplicateString(strList[0], strList[1], direction)
  484. if same_str == "":
  485. continue
  486. else:
  487. for i in range(2, strList.__len__()):
  488. same_str = self.__findDuplicateString(same_str, strList[i], direction)
  489. if same_str == "":
  490. break
  491. if same_str != "":
  492. finishedList = []
  493. for text in strList:
  494. if direction == "left":
  495. text = str[same_str.__len__():]
  496. else:
  497. text = str[:-same_str.__len__()]
  498. finishedList.append(text)
  499. # endfor
  500. # endif
  501. # endif
  502. # endfor
  503. return finishedList
  504. '''
  505. 函数:发送红老鼠按键;
  506. 参数:
  507. key 1、如果是字符串时,当作单个按键; 2、如果是list时,当作多个按键;
  508. count 执行多少次key;
  509. wait 1、执行单个key后,等待时长(因为电视机响应遥控需要时间);
  510. 2、执行list多个key后,每个key的等待时间;
  511. 返回:无
  512. '''
  513. def sendKey(self, key, count, wait):
  514. if key is not None and key.__len__() > 0:
  515. if type(key) == str:
  516. COptionAction.__redRat3.sendKey(key, count, wait)
  517. elif type(key) == list:
  518. for k in key:
  519. COptionAction.__redRat3.sendKey(k, 1, wait)
  520. else:
  521. self.error(u"error:无效按键内容=%s" % key)
  522. else:
  523. self.error(u"error:按键内容空")
  524. if __name__ == "__main__":
  525. exData = CExtraData()
  526. optionExcel = COptionExcel(exData)
  527. optionConfig = COptionConfig(exData, optionExcel)
  528. optionAction = COptionAction('picture', '', optionConfig, optionExcel)
  529. # ====================================== #
  530. optionAction.callFirstOptionShortCutKey()