# -*- coding:utf-8 -*- import os from enum import Enum from BaseLog import CBaseLog from ExtraData import CExtraData from OptionExcel import COptionExcel from OptionConfig import COptionConfig from OptionFocus import COptionFocus from OptionOCR import COptionOCR from ssat_sdk.tv_detect import * from ssat_sdk.device_manage.capturecard_manager import CCardManager from ssat_sdk.utils.string_util import strcmp g_level = ['First', 'Second', 'Third', 'Fourth', 'Fifth', 'Sixth', 'Seventh', 'Eighth', 'Ninth', 'Tenth', 'Eleventh', 'Twelfth'] def strSplit(text): ret = [] str_int = '' str_ch = '' ch_last = ' ' for ch in text: if 47 < ord(ch) < 58: str_int += ch if str_ch.__len__(): ret.append(str_ch) str_ch = '' else: if 47 < ord(ch_last) < 58 and ch == '.': str_int += ch if str_ch.__len__(): ret.append(str_ch) str_ch = '' else: str_ch += ch if str_int.__len__(): ret.append(str_int) str_int = '' ch_last = ch if str_ch.__len__(): ret.append(str_ch) if str_int.__len__(): ret.append(str_int) return ret # 枚举:value类型 class VType(Enum): SelectType = 0 InputType = 1 RangeType = 2 # 注意:所有不对外暴露的变量和函数需要私有化,以明确哪些接口和参数是对外的。 # 这样便于后期维护时,根据对外的变量和函数来做处理。 class COptionAction(CBaseLog): # ==============设备对象============== # # 红老鼠遥控对象; __redRat3 = TvOperator() # 创建视频采集对象 __captureCard = CCardManager() # 图片切割对象 __imgCMP = ImageCMP() def __init__(self, optionName, optionValue, optionConfig, optionExcel): CBaseLog.__init__(self) # 层级位置; self.__pos = 0 # 目标option; self.__optionName = optionName # __optionValue可空; self.__optionValue = optionValue self.__optionExcel = optionExcel self.__optionConfig = optionConfig # 焦点定位及文字识别; self.__optionFocus = COptionFocus(optionConfig) self.__optionOCR = COptionOCR(optionConfig, optionExcel) if self.__optionExcel is None: self.error(u"表格对象空") # ==============常用对象数据==============; self.__optionPaths = self.__optionExcel.getOptionPaths(self.__optionName) # 如果__optionValue空则不取value表任何内容; if self.__optionValue != "": self.__optionValueInfo = self.__optionExcel.getOptionValueInfo(self.__optionName, self.__optionValue) self.__optionInfo = self.__optionExcel.getOptionInfo(self.__optionName) # 当前状态下的变量,与__pos对应; self.__curOptionName = '' self.__curOptionInfo = None # 到达value节点后,记录value值(一般只用于range() 数值记录,其他值无意义); self.__optionValueText = "" # 获取一次当前层级信息; self.getCurOptionInfo() # 相关返回操作; self.__isEnterKeyInValue = False # 节点在value表时,是否发送enter key. self.__valueType = VType.SelectType # 节点在value表时,value值的类型(0:选择型, 1:输入型,2:range()数值型。) @property def pos(self): return self.__pos @property def curOptionName(self): return self.__curOptionName @property def curOptionInfo(self): return self.__curOptionInfo @property def optionValueText(self): return self.__optionValueText ''' 函数:截图并返回截图路径; 参数:无 返回:截图路径; ''' def takePicture(self): img = os.path.join(getSATTmpDIR(), "menutree_runpath.png") COptionAction.__captureCard.takePicture(img) if not os.path.exists(img): self.error(u"截图失败:%s" % img) return img ''' 函数:调用根节点快捷键(中间节点不需要快捷键;); 参数:无 返回:无 ''' def callFirstOptionShortCutKey(self): if 'shortcut_key' in self.__optionPaths['First']: self.sendKey(self.__optionPaths['First']['shortcut_key']) else: self.sendKey(self.__optionPaths['First']['parent']) self.warn(u"表格没有shortcut_key字段,执行默认的parent按键:%s" % self.__optionPaths['First']['parent']) ''' 函数:调用当前结点的toparent_key(叫back_key会简单点) 参数: curOptionName 当前层级的目标节点. 返回:无 ''' def callCurOptionBackKey(self, curOptionName): curOptionInfo = self.__optionExcel.getOptionInfo(curOptionName) if 'toparent_key' in self.__optionPaths[curOptionInfo['level']]: self.sendKey(self.__optionPaths[curOptionInfo['level']]['toparent_key']) else: self.error(u"表格没有toparent_key字段,执行默认按键return") self.sendKey('return') ''' 函数: 参数: 返回: ''' def inputUnlock(self, stdText, password=''): # 如果锁住,按ok会弹出输入密码框; self.sendKey("ok") # 获取密码; if password.__len__() == 0: password = self.__optionConfig.getSuperPassword("Password", "super") # 发送按键; for key in list(password): self.sendKey(key, 1, 0.2) time.sleep(1) # 发送ok键; self.sendKey('ok') # 判断是否成功输入密码; textPic = self.takePicture() # 遍历ocr类型; found = False ocrDict = [{"lan": "ChinesePRC+English", "type": 4}, {"lan": "ChinesePRC+English", "type": 253}, {"lan": "ChinesePRC+English", "type": 10001}] for item in ocrDict: # 识别ocr; ocrText = self.__optionOCR.getImageText(textPic, item, {}) ocrText = unicode(ocrText).lower() self.info("lan=%s,type=%d,ocr=%s" % (item["lan"], item["type"], ocrText)) if ocrText in stdText or stdText == ocrText: found = True break return not found ''' 函数:处理弹出密码框. 参数: 返回:Boolean, Boolean。 如何没有密码框处理,返回False,False。 如果有,且成功输入密码后密码框消失,返回True, True 注意:对应以前的dealOthers函数。 ''' def dealPasswordBox(self): parentOption = '' if self.isOnValueSheet(): if self.__optionValueInfo is None or self.__optionValueInfo.__len__() == 0: self.error(u"当前value[%s]信息空" % str(self.__optionValue)) return False, False others = self.__optionValueInfo['others'] if others is None or others.__len__() == 0: self.info(u"[%s]others字段空" % str(self.__curOptionName)) return False, False # 获取父节点 parentOption = self.__optionValueInfo['option'] else: if self.__curOptionInfo is None or self.__curOptionInfo.__len__() == 0: self.error(u"当前option[%s]信息空" % str(self.__curOptionName)) return False, False others = self.__curOptionInfo['others'] if others is None or others.__len__() == 0: self.info(u"[%s]others字段空" % str(self.__curOptionName)) return False, False # 获取父节点; parentOption = self.__curOptionInfo['parent'] # 转换为字典; others = json.loads(others) if "password" not in others: self.info(u"[%s]others没有密码框处理" % str(self.__curOptionName)) return False, False password = self.__optionConfig.get_value("Password", others["password"]) # 发送密码前,停2秒(因为像6586机芯响应很慢,密码框还没弹出就完成了密码输入的操作); time.sleep(2) # 发送按键; for key in list(password): self.sendKey(key, 1, 0.2) time.sleep(1) # 发送ok键; if others["enter_key"] != "default": self.sendKey(others["enter_key"]) # 判断是否成功输入密码; current_uiPic = self.takePicture() # 此处findRectByIcon参数3传递的不是first_parent,而是当前option的parent; found, contourRect = self.__optionFocus.findFocusByIcon(current_uiPic, parentOption) return True, not found ''' 函数:是否在父节点菜单上。一般在执行了callFirstOptionShortCutKey后调用; 参数:无 返回:Boolean, 数组(坐标)。 如:True, [0,0,1920,1080] 注意:由于所有父节点上的子项都共用一个图片定位参数,所以只要随意一个父节点的子项option即可获取定位参数; 测试:。 ''' def isOnFirstOption(self): pic = self.takePicture() return self.__optionFocus.findFocusByIcon(pic, self.__optionPaths['First']['option'])[0] ''' 函数:是否在当前节点(移动后,判断是否移动到下一目标节点)上. 说明: 每次移动到下一目标节点(option)上时,self.__pos + 1,表示移动到下一层路径。 当self.__pos >= self.__optionPaths.__len__()时,表示到达value表格; 所以,该类的重点在self.__pos的移动; 参数:无 返回:Boolean, 识别的文本/数字; 示例: ''' def isOnTargetNode(self): # 是否在value表中; isValueSheet = self.isOnValueSheet() self.info(u"当前层级在:%s" % ("value表" if isValueSheet else "路径表")) # 析出参数; if isValueSheet: curLevel = 'value' curParent = self.__optionPaths[g_level[self.__pos - 1]]['parent'] curOption = self.__optionValueInfo['option'] curOthers = self.__optionValueInfo['others'] else: curLevel = g_level[self.__pos] curParent = self.__optionPaths[curLevel]['parent'] curOption = self.__optionPaths[curLevel]['option'] curOthers = self.__optionPaths[curLevel]['others'] self.info("当前[%s]others=[%s]" % (curOption, curOthers)) if curOthers.__len__() == 0: curOthers = {} else: curOthers = json.loads(curOthers) firstParent = self.__optionPaths['First']['parent'] # 获取文本识别的参数; ocrConfigList = self.__optionConfig.getOptionOCRConfig(curOption) ocrThreshold = self.__optionConfig.getThresholdDict(firstParent) # 注意,此处使用firstParent; # 获取当前option的ocr值/value name下所有ocr值; if isValueSheet: if curOption.lower() == self.__optionName.lower(): optionTextList = self.__optionExcel.getOptionValueText(curOption, self.__optionValue) else: optionTextList = self.__optionExcel.getOptionValueText(curOption) else: optionTextList = self.__optionExcel.getOptionText(curOption) # 获取option下所有兄弟项的ocr:option字典内容; siblingTextDict = self.__optionExcel.getOptionAllSiblingItemDict(curOption, not isValueSheet) # 获取所有option兄弟项(包括自己)的ocr值; siblingTextList = list(siblingTextDict.keys()) # 是否获取数值文本; isNumberText = False # 如果是value表,且兄弟项文本为range # 注:value表中的option实际并没有兄弟项,取的是所有value项 if isValueSheet and siblingTextList.__len__(): if siblingTextList[0].startswith('range('): self.info(u"识别的内容是value表数字内容(range(0,xx)类型)") isNumberText = True # 清除之前的value值; self.__optionValueText = "" # 是否为静态焦点识别(动态则为跑马灯); if curOthers.__len__() and 'marquee' in curOthers: return self.__getDynamicPicText(curOption, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, curOthers['marquee'], isNumberText, isValueSheet) else: return self.__getStaticPicText(self.takePicture(), curOption, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, isNumberText, isValueSheet) # endif ''' 函数:是否移到目标节点上(在isOnOption后,判断__pos位置是否在__paths最后); 参数:无 返回:Boolean. ''' def isOnTargetOption(self): return True if self.__pos == (self.__optionPaths.__len__() - 1) else False ''' 函数:当前节点是否在value sheet层级中; 参数:无 返回:Boolean ''' def isOnValueSheet(self): if self.__optionValue == "": # 该值空,表明不会移动到value sheet中; return False else: return True if self.__pos >= self.__optionPaths.__len__() else False ''' 函数:移动到下一兄弟节点; 参数:无 返回:无 注意: sendKey的等待时间太短,会导致画面未响应,截图时还是截上一状态的,比如0.1秒就很经常出现这问题。 同时,按键等待时间,应该有所区分。 如果不截图,可以不考虑sendKey的等待时间. ''' def move2NextSiblingNode(self): # 获取当前option信息; if self.getCurOptionInfo(): # 析出move key; optionMoveKey = self.__curOptionInfo['option_move_key'] if optionMoveKey.__len__() == 0: self.sendKey(self.__curOptionInfo['move_key'][1], 1, 1) else: self.sendKey(optionMoveKey[1], 1, 1) else: valueMoveKey = self.__optionValueInfo['move_key'] self.sendKey(valueMoveKey[1], 1, 1) ''' 函数:移动到上一兄弟节点 参数:无 返回:无 注意: sendKey的等待时间太短,会导致画面未响应,截图时还是截上一状态的,比如0.1秒就很经常出现这问题。 同时,按键等待时间,应该有所区分。 如果不截图,可以不考虑sendKey的等待时间. ''' def move2PrevSiblingNode(self): # 获取当前option信息; if self.getCurOptionInfo(): # 析出move key; optionMoveKey = self.__curOptionInfo['option_move_key'] if optionMoveKey.__len__() == 0: self.sendKey(self.__curOptionInfo['move_key'][0], 1, 1) else: self.sendKey(optionMoveKey[0], 1, 1) else: valueMoveKey = self.__optionValueInfo['move_key'] self.sendKey(valueMoveKey[0], 1, 1) ''' 函数:返回到父节点 参数: isReduce 是否需要自减层级。 一般用于value节点返回时,才需要用到该参数(如果value节点设置值后,不是自动返回,要设置False)。 返回:无 注意: sendKey的等待时间太短,会导致画面未响应,截图时还是截上一状态的,比如0.1秒就很经常出现这问题。 同时,按键等待时间,应该有所区分。 如果不截图,可以不考虑sendKey的等待时间. ''' def back2ParentNode(self, isReduce=True): # 获取当前option信息; if self.getCurOptionInfo(): backKey = self.__curOptionInfo['back_key'] else: backKey = self.__optionValueInfo['back_key'] # 同时,根据是否有发送enter key和值类型来判断是否需要多发次返回。 if not self.__isEnterKeyInValue: # 标记为True,防止重复进入. self.__isEnterKeyInValue = True self.info(u"进入了value表但未按enter key, 值类型=%s" % str(self.__valueType)) if self.__valueType == VType.SelectType: # 如果是选择型数据,没有enter键; self.back2ParentNode(False) elif self.__valueType == VType.InputType: pass # 如果是输入型数据,没有enter键; # 可能输入后,自动进入。需要根据autoBackFlag来判断; # if autoBackFlag is False: # self.back2ParentNode(False) # else: # self.__pos -= 1 elif self.__valueType == VType.RangeType: # 如果是进度条数据,没有enter键; self.back2ParentNode(False) if backKey.__len__() == 0: self.sendKey('return', 1, 1) else: self.sendKey(backKey, 1, 1) # 返回,自减; if isReduce: self.__pos -= 1 ''' 函数:进入当前节点,只对路径节点有效,value节点不处理; 参数:无 返回:无 ''' def enterNode(self): # 获取当前option信息; if self.getCurOptionInfo(): # 是否有等待时间 waitTime = self.__optionConfig.getParentWaitTime(self.__curOptionInfo['parent']) # 析出enter key; optionEnterKey = self.__curOptionInfo['option_enter_key'] if optionEnterKey.__len__() == 0: self.sendKey(self.__curOptionInfo['enter_key'], 1, waitTime) else: self.sendKey(optionEnterKey, 1, waitTime) # 进入下层,自增 self.__pos += 1 else: self.info(u"节点已在value上,无法再进入") ''' 函数:设置当前节点位置; 参数: pos: 外部节点位置值。 返回:无 注意:此函数用于外部创建两个【路径具体子集关系】的COptionAction对象,才可以使用此函数。 假设, a对象路径{p,p1,p2,p3,p4, v1},b = {p, p1, p2, p3, p4, p5, p6, v2}, c = {p, p2, p5, p6, v3} 其中, v表示value值,不属于路径。那么,a和b具有子集关系, a与c或b与c都不具有子集关系。 a移动到p4上,完成了v1设置后,a.back2ParentNode()后,此时如果要操作b并设置v2,就要b.SetCurPos(a.pos). ''' def setCurPos(self, pos): if pos < 0 or pos > self.__optionPaths.__len__(): self.error(u"pos值[%d]超出路径范围:[0-%d]" % (pos, self.__optionPaths.__len__())) return self.__pos = pos ''' 函数:设置目标option的值, 只设置数值型value和输入型value(选择型value不需要此步骤). 参数:无 返回:无 注意:此函数必须是已聚焦在目标value节点上,否则无效。 重要: 在此函数enter后,UI是否返回到上一层父节点上,还是停留在本层节点不返回。 建议在excel中配置这个关键信息,以便此函数可以正确更改self.__pos的值。 ''' def setOptionValue(self): self.info(u"【在此函数enter后,UI是否返回到上一层父节点上,还是停留在本层节点不返回。\ 建议在excel中配置这个关键信息,以便此函数可以正确更改self.__pos的值。】") if type(self.__optionValue) == str and self.__optionValue.__len__() == 0: self.error(u"[%s]的值为空,没有设置的值" % self.__optionName) return enterKey = self.__optionValueInfo['enter_key'] moveKey = self.__optionValueInfo['move_key'] valueText = self.__optionValueInfo['value_for_ocr'] others = self.__optionValueInfo['others'] if others.__len__(): others = json.loads(others) else: others = {} # 是否有按键延时值; duration = float(others['duration']) if "duration" in others else 0.1 # 值类型: # 0 表示默认选择类型. # 1 表示输入类型(有些输入完了,正确值会自动进入). # 2 表示进度条数据型. self.__valueType = VType.SelectType # 是否为数字文本(特指:range(0, 100)); isNumberText = self.isNumberText(valueText) # 数值型value; if isNumberText: if moveKey[0] == 'input': # 标记值类型; self.__valueType = VType.InputType # 将数值转成字符; optionValue = self.__optionValue if type(optionValue) == int or type(optionValue) == float: optionValue = str(self.__optionValue) # 再将字符转成list; chList = list(optionValue) self.sendKey(chList, 1, duration) else: # 标记值类型; self.__valueType = VType.RangeType # 相差值; num = int(self.__optionValue) - int(self.__optionValueText) # 正->往右或下,负->往左或上; self.sendKey(moveKey[1] if num > 0 else moveKey[0], abs(num), duration) elif moveKey[0] == 'input': # 标记值类型; self.__valueType = VType.InputType # 将字符转成list; chList = list(self.__optionValue) self.sendKey(chList, 1, duration) # 最后,如果有进入键执行; if enterKey != 'default': self.info(u"value节点具有enter key") self.sendKey(enterKey, 1, 0.1) self.__isEnterKeyInValue = True # enter之后,可能会返回上层菜单,也可能不返回. # if autoBackFlag is False: # self.back2ParentNode(False) ''' 函数:获取当前层级的目标option详细信息. 参数:无 返回:Boolean, 获取成功返回True ''' def getCurOptionInfo(self): if self.__optionPaths is None or self.__optionPaths.__len__() == 0: self.error(u"paths路径空") return False if self.__pos >= self.__optionPaths.__len__(): # self.__curOptionInfo = None # 是否需要在到达value层级后置空,可能保留值会有比较多用处! self.warn(u"已到达value节点,无法获取路径信息") return False # 只有第一次或层级移动了才需要更新; if self.__curOptionInfo is None or self.__pos != self.__curOptionInfo['layers']: self.__curOptionName = self.__optionPaths[g_level[self.__pos]]['option'] outResult, outData = self.__optionExcel.getOptionInfo(self.__curOptionName, self.__optionPaths) if outResult is False: return False self.__curOptionInfo = outData return True ''' 函数:检测路径是否有效; 参数:无 返回:Boolean, 检测正常返回True ''' def checkRunOptionPath(self): outData = self.__optionExcel.checkOptionPaths(self.__optionPaths) if str(outData[1]) == 'NoExit': self.error(u"表格中不存在到达Option:[%s]的路径,在表格中排查到达该Option路径" % self.__optionName) return False if str(outData[1]) == 'Fail': self.error(u"表格中到达Option:[%s]的路径出现数据断层找不到First层级,在表格中排查到达该Option路径" % self.__optionName) return False return True ''' 函数:指定的value_for_ocr或option_for_ocr数组是否为range(xx,xx)类型。 参数: textList value_for_ocr或option_for_ocr数组,一般只可能会是value_for_ocr 返回:Boolean, 如果是range(xx,xx)类型返回True. ''' def isNumberText(self, textList): # 是否获取数值文本; isNumberText = False # 如果是value表,且兄弟项文本为range # 注:value表中的option实际并没有兄弟项,取的是所有value项 if self.isOnValueSheet() and textList.__len__(): if textList[0].startswith('range('): self.info(u"识别的内容是value表数字内容(range(0,xx)类型)") isNumberText = True return isNumberText ''' 函数:获取静态图片文本内容 参数:(略,请看调用函数) 注意: 返回:Boolean、文本识别内容。成功识别出文本,返回True及文本内容。 ''' def __getStaticPicText(self, pic, optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, isNumberText, isValueSheet, aliveKey=None): # 获取图片焦点框; found, focusBox = self.__optionFocus.findFocusByIcon(pic, optionName, isValueSheet) if found is False: self.debug(u"未找到[%s]聚集框" % optionName) return False, None # 如果有鲜活键; self.sendAliveKey(aliveKey) # 获取文本区域框; textBox = self.__optionFocus.getFocusTextBox(optionName, focusBox, isValueSheet) # 配置文本图片路径,保存文本区域截图; text_pic = os.path.join(getSATTmpDIR(), "meuttree_area_text.png") self.__imgCMP.saveCropPic(pic, text_pic, textBox) if not os.path.exists(text_pic): self.error(u"%s截取文本图片失败:%s" % (optionName, text_pic)) return False, None # 是否在某个兄弟项中; isOnSibling = False # 遍历所有ocr识别选项; for ocrConfig in ocrConfigList: # 如果有鲜活键; self.sendAliveKey(aliveKey) # 识别出当前聚焦文本; curFocusText = self.__optionOCR.getImageText(text_pic, ocrConfig, ocrThreshold) # 判断识别文本是来正常; if curFocusText == "ERR" or curFocusText.__len__() == 0: continue # 转成小写; curFocusText = curFocusText.lower() self.info("[%s]当前识别出的文本=%s" % (optionName, curFocusText)) # 是否取数字文本(肯定在value节点上); if isNumberText is True: # 特殊情况处理:某些情况下,会将包含数字以外的区域一起识别; curFocusText = curFocusText.strip('>') # 将数字分组 numberTextList = strSplit(curFocusText) # 只判断最后一位是否为数字; if numberTextList.__len__() < 1: self.error(u"当前识别的文本不是数字文本:%s" % curFocusText) continue try: numberText = float(numberTextList[numberTextList.__len__() - 1]) # 记录value值; self.__optionValueText = numberText return True, numberText except Exception: continue else: # 当前option识别的文本与所有兄弟项文本比较; for siblingText in siblingTextList: # 转为小写,保证所有比较都是小写; siblingText = siblingText.lower() # 兄弟项文本是否被包含在curFocusText中或相同; if siblingText in curFocusText or strcmp(siblingText, curFocusText): isOnSibling = True self.info(u"当前焦点在[%s], 目标焦点为[%s]" % (siblingText, optionName)) # 再判断,该兄弟项是否为目标节点(curOption); for optionText in optionTextList: optionText = optionText.lower() # 若当前兄弟项为目标option返回True、文本; if strcmp(optionText, siblingText): return True, curFocusText # endif # endfor # 在兄弟项中,退出循环; break # endif # endfor if isOnSibling is False: self.error(u"未聚集到任何[%s]的兄弟项中" % optionName) else: self.info("未聚集到目标节点[%s],当前文本=%s" % (optionName, curFocusText)) return False, curFocusText # endif # endfor # 默认返回; return False, 0 if isNumberText else "" ''' 函数:获取动态图片文本内容 参数:(略,请看调用函数) 注意: 返回:Boolean、文本识别内容。成功识别出文本,返回True及文本内容。 ''' def __getDynamicPicText(self, optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, marqueeDict, isNumberText, isValueSheet): # 判断图片是否动态:截图两次,判断两次文本内容是否相同; firstRetsult, firstText = self.__getStaticPicText(self.takePicture(), optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, isNumberText, isValueSheet) if firstRetsult is False: self.error(u"[%s]第一次截图未识别出聚焦框" % optionName) return False, None # 发送鲜活键, 保证界面鲜活; self.sendAliveKey(marqueeDict['alive_key']) # 第二次截图; secondRetsult, secondText = self.__getStaticPicText(self.takePicture(), optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, isNumberText, isValueSheet) if secondRetsult is False: self.error(u"[%s]第二次截图未识别出聚焦框" % optionName) return False, None # 发送鲜活键, 保证界面鲜活; self.sendAliveKey(marqueeDict['alive_key']) # 比较两文本是否相同; if firstText.__len__() and firstText == secondText: self.info(u"截图两次,文本(%s)识别相同,聚焦的不是跑马灯Option" % firstText) return False, firstText # 文本不相同,为动态图片; menuList = marqueeDict['menu'] # 如果只有一项跑马灯,且目标option亦在跑马灯列表中,则直接返回成功结果 if menuList.__len__() == 1 and (optionName in menuList): self.info(u"该层菜单只有一项跑马灯, 找到即成功返回") return True, firstText picList = [] # 如果有多项同级option都是跑马灯, 要成功识别文本需要间隔截图5次(大概会成功截图到最全的文本); for i in range(0, 5): picList.append(self.takePicture()) # 间隔多久截图; time.sleep(marqueeDict['sleep_time']) # 发送鲜活键; self.sendAliveKey(marqueeDict['alive_key']) ocrTextList = [] # 对截图进行文本识别分析; for pic in picList: result, text = self.__getStaticPicText(pic, optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, isNumberText, isValueSheet, marqueeDict['alive_key']) if result is True: ocrTextList.append(text) # 发送鲜活键; self.sendAliveKey(marqueeDict['alive_key']) # 过滤重复的字符; ocrTextList = self.__removeDuplicateString(ocrTextList) self.info(u"识别到的跑马灯ocr文字列表:%s" % ocrTextList) # 获取动态文本的option字典; dynamicOptionOcrDict = self.__getOptionInfoDict(menuList) self.info(u"获取到的跑马灯Option对应的ocr字典:%s" % dynamicOptionOcrDict) # 遍历:识别结果与xls结果进行比较; for dynamicOption in dynamicOptionOcrDict: dynamicOptionOcrList = dynamicOptionOcrDict[dynamicOption] for dynamicOptionOcr in dynamicOptionOcrList: # 只要有3张满足,判断找到option; count = 0 for ocrText in ocrTextList: if ocrText.lower() in dynamicOptionOcr: count += 1 if count >= 3 and optionName == dynamicOption: return True, ocrText else: self.info(u"当前聚焦的跑马灯实际为:%s" % dynamicOption) return False, ocrText # endfor # endfor self.info("未能识别到当前聚焦的跑马灯Option") return False, 0 if isNumberText else None ''' 函数:获取option名称数组内所有option的详细信息. 参数: optionNameList option名称数组。 返回:字典。 { "option1": {self.__optionExcel.getOptionInfo(option1)[1]}, "option2": {self.__optionExcel.getOptionInfo(option2)[1]}, "option3": {self.__optionExcel.getOptionInfo(option3)[1]}, } ''' def __getOptionInfoDict(self, optionNameList): OptionInfoDict = {} for optionName in optionNameList: found, optionDict = self.__optionExcel.getOptionInfo(optionName) if found: OptionInfoDict[optionName] = optionDict # endif # endfor return OptionInfoDict ''' 函数:找到两个字符串左边或者右边相同的部分 参数: str1: str2: direction 方向,默认为right 返回: ''' def __findDuplicateString(self, str1, str2, direction="right"): index = 0 if direction == "right": while True: index -= 1 if abs(index) > str1.__len__() or abs(index) > str2.__len__(): break if not str1[index] == str2[index]: break if index == -1: self.info(u"没有找到重复文本") return "" return str1[index + 1:] elif direction == "left": while True: if abs(index) >= str1.__len__() or abs(index) >= str2.__len__(): break if not str1[index] == str2[index]: break index += 1 return str1[:index] ''' 函数:获取路径长度。 参数: 返回: ''' def getPathLength(self): if self.__optionPaths is None: self.error(u"路径空,返回0") return 0 self.info(u"路径长度:%d" % self.__optionPaths.__len__()) return self.__optionPaths.__len__() ''' 函数:去掉字符串数组中每个字符串 左边或右边相同的部分 参数: strList 返回: ''' def __removeDuplicateString(self, strList): finishedList = strList directionList = ["left", "right"] for direction in directionList: same_str = self.__findDuplicateString(strList[0], strList[1], direction) if same_str == "": continue else: for i in range(2, strList.__len__()): same_str = self.__findDuplicateString(same_str, strList[i], direction) if same_str == "": break if same_str != "": finishedList = [] for text in strList: if direction == "left": text = str[same_str.__len__():] else: text = str[:-same_str.__len__()] finishedList.append(text) # endfor # endif # endif # endfor return finishedList ''' 函数:发送红老鼠按键; 参数: key 1、如果是字符串时,当作单个按键; 2、如果是list时,当作多个按键; count 执行多少次key; wait 1、执行单个key后,等待时长(因为电视机响应遥控需要时间); 2、执行list多个key后,每个key的等待时间; 返回:无 ''' def sendKey(self, key, count=1, wait=1): if key is not None and key.__len__() > 0: if type(key) == list: for k in key: # 清除前后空格; k = k.lstrip() k = k.rstrip() COptionAction.__redRat3.sendKey(k, 1, wait) else: key = str(key) # 清除前后空格; key = key.lstrip() key = key.rstrip() COptionAction.__redRat3.sendKey(key, count, wait) else: self.error(u"error:按键内容空") ''' 函数:发送鲜活键; 参数: aliveKey 鲜活按键; 返回:无 注意:鲜活键等待时间是0.1秒,因为不考虑截图。 ''' def sendAliveKey(self, aliveKey): self.sendKey(aliveKey, 1, 0.1) if __name__ == "__main__": exData = CExtraData() optionExcel = COptionExcel(exData) optionConfig = COptionConfig(exData, optionExcel) optionAction = COptionAction('picture', '', optionConfig, optionExcel) # ====================================== # optionAction.callFirstOptionShortCutKey()