# -*- 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 != "" or self.__optionValue is not None: self.__optionValueInfo = self.__optionExcel.getOptionValueInfo(self.__optionName, self.__optionValue) self.__optionInfo = self.__optionExcel.getOptionInfo(self.__optionName) # 当前状态下的变量,与__pos对应; self.__curOptionName = '' self.__curOptionInfo = None self.__prevOptionName = '' self.__prevOptionInfo = None # 到达value节点后,记录value值(一般只用于range() 数值记录,其他值无意义); self.__optionValueText = "" # 获取一次当前层级信息; self.getCurOptionInfo() # 相关返回操作; self.__isEnterKeyInValue = False # 节点在value表时,是否发送enter key. self.__valueType = VType.SelectType # 节点在value表时,value值的类型(0:选择型, 1:输入型,2:range()数值型。) # 是否在Value层 self.__isOnValueSheet = False @property def pos(self): return self.__pos @property def optionName(self): return self.__optionName @property def optionValue(self): return self.__optionValue @property def curOptionName(self): return self.__curOptionName @property def curOptionInfo(self): return self.__curOptionInfo @property def optionValueText(self): return self.__optionValueText @property def optionValueInfo(self): return self.__optionValueInfo @property def optionPaths(self): return self.__optionPaths ''' 函数:截图并返回截图路径,如果当前option/optionValue含有layout坐标参数,则返回坐标截图; 参数:无 返回:截图路径; ''' 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) retImg = img # 判断当前处于option层还是value层 if self.__pos >= self.__optionPaths.__len__(): curInfo = self.__optionValueInfo else: curInfo = self.__curOptionInfo # 判断当前层是否存在layout参数 if "layout" in curInfo and 'bounds' in curInfo['layout']: if curInfo['layout']['bounds'] is ['']: self.info("当前option未配置layout参数") elif curInfo['layout']['bounds'].__len__() != 4: self.info("当前option的layout坐标配置异常:%s" % str(curInfo['layout']['bounds'])) else: self.info("当前option的layout坐标为:%s" % str(curInfo['layout']['bounds'])) retImg = os.path.join(getSATTmpDIR(), "menutree_runpath_layout.png") COptionAction.__imgCMP.saveCropPic(img, retImg, curInfo['layout']['bounds']) return retImg ''' 函数:调用根节点快捷键(中间节点不需要快捷键;); 参数:无 返回:无 ''' def callFirstOptionShortCutKey(self): # 获取父节点等待时间(如果没找到,getParentWaitTime默认返回1)。 waitTime = self.__optionConfig.getParentWaitTime(self.__optionPaths['First']['parent']) # 是否有另配置快捷按键代替根节点按键; ''' 由于NT72561的source区分了tv信源与非tv信源,为了统一脚本,故others(first_key)新增逻辑: 1、优先读取option的others(first_key)信息; 2、options的others信息没有的情况下,读取parent的others(first_key)信息 ''' if 'shortcut_key' in self.__optionPaths['First']: self.sendKey(self.__optionPaths['First']['shortcut_key'], 1, waitTime) elif self.__optionPaths['First']['option_others'] != "": others = json.loads(self.__optionPaths['First']['option_others']) if 'first_key' in others: self.info(u"option:%s包含first_key信息,将使用first_key进入。\noption first_key:%s"%(self.__optionPaths['First']['option'], others['first_key'])) keyList = others['first_key'] for key in keyList: self.sendKey(key, 1, waitTime) elif self.__optionPaths['First']['others'] != "": others = json.loads(self.__optionPaths['First']['others']) if 'first_key' in others: self.info(u"parent:%s包含first_key信息,将使用first_key进入。\nparent first_key:%s"%(self.__optionPaths['First']['parent'], others['first_key'])) keyList = others['first_key'] for key in keyList: self.sendKey(key, 1, waitTime) else: self.sendKey(self.__optionPaths['First']['parent'], 1, waitTime) 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: getStatus, nextOptionInfo = self.getNextOptionInfo() 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 not strcmp(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的移动; 参数: staticPic 静态图片路径。 主要用于将截图和聚焦、ocr识别分离,然后可以截图和聚焦中间添加其他处理。 返回:Boolean, 识别的文本/数字; 示例: ''' def isOnTargetNode(self, staticPic=None): # 是否在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(x,y)类型)") 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: isFocus, isTarget, text = self.__getStaticPicText(self.takePicture() if staticPic is None else staticPic, curOption, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, isNumberText, isValueSheet) return isTarget, text # 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 self.__isOnValueSheet ''' 函数:移动到下一兄弟节点; 参数:无 返回:无 注意: sendKey的等待时间太短,会导致画面未响应,截图时还是截上一状态的,比如0.1秒就很经常出现这问题。 同时,按键等待时间,应该有所区分。 如果不截图,可以不考虑sendKey的等待时间. ''' def move2NextSiblingNode(self): # 析出move key; if self.isOnValueSheet(): valueMoveKey = self.__optionValueInfo['move_key'] self.sendKey(valueMoveKey[1], 1, 1) else: 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) ''' 函数:移动到上一兄弟节点 参数:无 返回:无 注意: sendKey的等待时间太短,会导致画面未响应,截图时还是截上一状态的,比如0.1秒就很经常出现这问题。 同时,按键等待时间,应该有所区分。 如果不截图,可以不考虑sendKey的等待时间. ''' def move2PrevSiblingNode(self): # 析出move key; if self.isOnValueSheet(): valueMoveKey = self.__optionValueInfo['move_key'] self.sendKey(valueMoveKey[0], 1, 1) else: 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) ''' 函数:返回到父节点 参数: 返回:无 注意: sendKey的等待时间太短,会导致画面未响应,截图时还是截上一状态的,比如0.1秒就很经常出现这问题。 同时,按键等待时间,应该有所区分。 如果不截图,可以不考虑sendKey的等待时间. ''' def back2ParentNode(self): # 获取当前option信息; if self.isOnValueSheet(): tKeyDict = self.__optionValueInfo['toparent_key'] peKeyDict = self.__prevOptionInfo['enter_key'] else: tKeyDict = self.__curOptionInfo['toparent_key'] peKeyDict = self.__prevOptionInfo['enter_key'] ''' 说明: toparent_key由于旧menutree无此参数,分以下数种情况: 当toparent_key没有配时: 是否为value层: 如果为value层,上一父层的enter_key是否为default: 如果是default,则toparent_key也按default处理; 如果不是default,则toparent_key按照return处理; 如果不为value层: toparent_key统一按return处理。 如果配了toparent_key: toparent_key是否为default: 如果是,按照default处理: 如果不是,按照配置的key来处理。 tKeyDict中是否包含tolevel字段; 如果不包含,执行完toparent_key后self.__pos自减1 如果包含,执行完toparent_key后self.__pos等于tolevel指定的层级 ''' # 当toparent_key没有配时; if tKeyDict == "" or tKeyDict['key'][0] == "": # 是否为value层: if self.isOnValueSheet(): # 如果为value层,上一父层的enter_key是否为default if strcmp(peKeyDict["key"][0], "default"): # 如果是default,则toparent_key也按default处理; self.info("当前处于value层,未配toparent_key,且父层enter_key为default,默认toparent_key为default") tKeyDict = {'key': ["default", ]} else: self.info("当前处于value层,未配toparent_key,toparent_key为return") # 如果不是default,则toparent_key按照return处理; tKeyDict = {'key': ["return", ]} else: self.info("当前层未配toparent_key,toparent_key为return") # 如果不为value层,toparent_key统一按return处理。 tKeyDict = {'key': ["return", ]} # 如果配了toparent_key else: # toparent_key是否为default if strcmp(tKeyDict['key'][0], "default"): # 如果是default,则toparent_key也按default处理; self.info("当前层toparent_key为default") else: self.info("当前层toparent_key为%s" % str(tKeyDict['key'])) self.__executeToParentKey(tKeyDict) # 从value层执行完返回后,则不在valuesheet里了 self.__isOnValueSheet = False ''' 在忽略大小写的情况下,获取到该level对应的g_level的下标值 ''' def __getLevelIndex(self, level): s_level = [] for lvStr in g_level: s_level.append(lvStr.lower()) return s_level.index(level) ''' 函数:进入当前节点,只对路径节点有效,value节点不处理; 参数:无 返回:无 ''' def enterNode(self): # 析出enter key; if self.isOnValueSheet(): if not self.__isEnterKeyInValue: self.info(u"节点已在value上,且没有触发过enter key") enterKey = self.__optionValueInfo['enter_key'] self.__executeEnterKey(enterKey) self.__isEnterKeyInValue = True else: self.info(u"节点已在value上,已触发过enter key") else: # 优先使用option本身的enter_key optionEnterKey = self.__curOptionInfo['option_enter_key'] # 如果option本身的enter_key为空,则使用该层parent的enter_key if optionEnterKey['key'] == "": optionEnterKey = self.__curOptionInfo['enter_key'] self.__executeEnterKey(optionEnterKey) ''' 函数:设置当前节点位置; 参数: 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 self.getCurOptionInfo() ''' 函数:设置目标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=0.1时,会变成长按的效果。> duration = float(others['duration']) if "duration" in others else 0.2 # 值类型: # 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 not strcmp(enterKey['key'][0], 'default') and enterKey['key'][0] != "": self.info(u"value节点具有enter key") self.__executeEnterKey(enterKey) ''' 函数:获取当前层级的目标option详细信息. 参数:无 返回:Boolean, 获取成功返回True ''' def getCurOptionInfo(self): if self.__optionPaths is None or self.__optionPaths.__len__() == 0: self.error(u"paths路径空") return False if 'First' not in self.__optionPaths: self.error(u"构建的paths不连续,路径存在断点") self.error(u"当前paths内容:%s"%self.__optionPaths) return False if self.__pos >= self.__optionPaths.__len__(): # 有可能存在使用openOption打开某个option,但是下一层未到value层的情况。 # 此时pos会等于整个路径,但是我们要取的数据又不在value层。需要返回该option下任意一个子option的参数 outResult, parentData = self.__optionExcel.getParentInfo(self.__optionName) if outResult: for optionName in parentData['option']: outResult, outData = self.__optionExcel.getOptionInfo(optionName) if outResult: self.__curOptionInfo = outData self.info(u"当前使用openOption方式打开目标option:%s,下一层未到value层,将以其子option:%s的参数信息作为返回值"%(self.__optionName, optionName)) break else: self.warn(u"获取到了目标option:%s在下一层存在parent配置,但无法获取到任何子option信息!"%self.__optionName) return False else: # self.__curOptionInfo = None # 是否需要在到达value层级后置空,可能保留值会有比较多用处! self.warn(u"已到达value节点,无法获取路径信息") self.__prevOptionName = self.__curOptionName self.__prevOptionInfo = self.__curOptionInfo self.__isOnValueSheet = True 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 # 当处于第二层或以后的层级时,读取前一层的数据 if self.__pos > 0: # 是否在value层 if not self.isOnValueSheet(): self.__prevOptionName = self.__optionPaths[g_level[self.__pos - 1]]['option'] outResult, outData = self.__optionExcel.getOptionInfo(self.__prevOptionName, self.__optionPaths) if outResult is False: return False self.__prevOptionInfo = outData # 特殊情况:如果只一层且该层与value相同(信源source,要在value表中配置一份相同配置)。 # if self.__curOptionInfo['first_parent'] == self.__curOptionName: # # 自动进入value表; # self.__pos += 1 return True ''' 函数:返回下一个option的详细信息; 参数:无 返回:Boolean、option info。 ''' def getNextOptionInfo(self): if self.__optionPaths is None or self.__optionPaths.__len__() == 0: self.error(u"paths路径空") return False, None if self.__pos + 1 >= self.__optionPaths.__len__(): self.warn(u"已到达value节点,无法获取路径信息") return False, None nextOptionName = self.__optionPaths[g_level[self.__pos + 1]]['option'] return self.__optionExcel.getOptionInfo(nextOptionName, self.__optionPaths) ''' 函数:获取指定位置的option信息; 参数: pos 指定的层级位置; 返回: 成功返回option信息,失败返回None ''' def getOptionInfo(self, pos): if pos > self.__optionPaths.__len__() and pos < 0: return None outResult, outData = self.__optionExcel.getOptionInfo(self.__optionPaths[g_level[pos]]['option'], self.__optionPaths) if outResult is False: return None return outData ''' 函数:指定option是否存在该路径中; 参数: optionName 指定的option名称; 返回: 存在返回True+位置, 不存在返回False+位置; ''' def isOptionInPaths(self, pos, optionNameOrValueName): bExist = False # 在路径表中; if 0 < pos < self.__optionPaths.__len__(): posOptionName = self.__optionPaths[g_level[pos]]['option'].lower() siblingText = self.__optionExcel.getOptionAllSiblingItemName(posOptionName) for name in siblingText: if name.lower() == optionNameOrValueName.lower(): bExist = True break # 在value表中; if self.isOnValueSheet(): valueOptionName = self.__optionPaths[g_level[self.__optionPaths.__len__() - 1]]['option'] valueNames = self.__optionExcel.getOptionAllValueName(valueOptionName) for name in valueNames: if name.lower() == optionNameOrValueName.lower(): bExist = True break return bExist ''' 函数:检测路径是否有效; 参数:无 返回: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、Boolean、文本识别内容。 是否成功聚焦、是否聚焦在目标节点上、聚焦框识别的文本内容。 ''' 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, False, None # 如果有鲜活键; self.sendAliveKey(aliveKey) # 获取文本区域框; textBox = self.__optionFocus.getFocusTextBox(optionName, focusBox, isValueSheet) # 配置文本图片路径,保存文本区域截图; text_pic = os.path.join(getSATTmpDIR(), "menutree_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, 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, True, numberText except Exception: continue else: # 当前option识别的文本与所有兄弟项文本比较; for siblingText in siblingTextList: # 转为小写,保证所有比较都是小写; siblingText = siblingText.lower() # 兄弟项文本是否被包含在curFocusText中或相同; if siblingText.lower() in curFocusText.lower() or strcmp(siblingText, curFocusText): isOnSibling = True self.info(u"当前焦点在[%s], 目标焦点为[%s]" % (siblingText, optionName)) self.info(u"optionTextList:%s" % optionTextList) # 再判断,该兄弟项是否为目标节点(curOption); for optionText in optionTextList: optionText = optionText.lower() # 若当前兄弟项为目标option返回True、文本; if strcmp(optionText, siblingText): return True, True, curFocusText # endif # endfor # 在兄弟项中,退出循环; break # endif # endfor if isOnSibling is False: self.error(u"未聚集到任何[%s]的兄弟项中" % optionName) else: self.info("未聚集到目标节点[%s],当前文本=%s" % (optionName, curFocusText)) return found, False, curFocusText # endif # endfor # 默认返回; return found, False, 0 if isNumberText else curFocusText ''' 函数:获取动态图片文本内容 参数:(略,请看调用函数) 注意: 返回:Boolean、文本识别内容。成功识别出文本,返回True及文本内容。 ''' def __getDynamicPicText(self, optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, marqueeDict, isNumberText, isValueSheet): # 判断图片是否动态:截图两次,判断两次文本内容是否相同; firstFocus, firstTarget, firstText = self.__getStaticPicText(self.takePicture(), optionName, optionTextList, siblingTextList, [{"lan": "ChinesePRC+English", "type": 10001}], ocrThreshold, isNumberText, isValueSheet) if firstFocus is False: self.error(u"[%s]第一次截图未识别出聚焦框" % optionName) return False, None # 目标option不是跑马灯,且已经聚焦到了目标option elif firstTarget is True: self.info(u"已聚焦到了目标option:%s" % optionName) return firstTarget, firstText # 发送鲜活键, 保证界面鲜活; self.sendAliveKey(marqueeDict['alive_key']) # 第二次截图; secondFocus, secondTarget, secondText = self.__getStaticPicText(self.takePicture(), optionName, optionTextList, siblingTextList, [{"lan": "ChinesePRC+English", "type": 10001}], ocrThreshold, isNumberText, isValueSheet) if secondFocus 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 elif firstText.__len__() == 0: self.warn(u"未能识别出当前option") 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: isFocus, isTarget, text = self.__getStaticPicText(pic, optionName, optionTextList, siblingTextList, ocrConfigList, ocrThreshold, isNumberText, isValueSheet, marqueeDict['alive_key']) if isTarget 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, duration=1.0): 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, duration) else: key = str(key) # 清除前后空格; key = key.lstrip() key = key.rstrip() COptionAction.__redRat3.sendKey(key, count, duration) else: self.error(u"error:按键内容空") ''' 函数:发送鲜活键; 参数: aliveKey 鲜活按键; 返回:无 注意:鲜活键等待时间是0.1秒,因为不考虑截图。 ''' def sendAliveKey(self, aliveKey): self.sendKey(aliveKey, 1, 0.1) ''' 函数:按照新格式执行enter_key。 参数: eKeyDict 即enterKey的字典对象,包含的wait和duration等参数。 wait,duration是为了兼容旧menutree按照others配置参数的处理方法。 如果others中有配waitTime和duration,优先采用others里的配置,否则则采用enterKey里的配置。 ''' def __executeEnterKey(self, eKeyDict): # 先从config读取 wait = self.__optionConfig.getParentWaitTime(self.__curOptionInfo['parent']) # config中读取不到则尝试从others中读取 if wait == 1.0: try: others = json.loads(self.__curOptionInfo['others']) if "waitTime" in others: if others['waitTime'] != "": wait = float(others['waitTime']) except Exception,e: pass # 读出来的enter_key,不为空或者default时,按格式执行enter_key if not strcmp(eKeyDict['key'][0], 'default') and eKeyDict['key'][0] != "": self.__executeKey(eKeyDict, wait=wait) if eKeyDict['tolevel'] != "": level = eKeyDict['tolevel'] else: level = "" # value层的enter_key存在特殊字段,isback。用于判断执行enter_key以后会不会返回父层。 # isback为0或为""时: # 则认为不返回父层; # 为1时: # 如果enter_key中没有配tolevel,则默认返回上一层父层; # 如果有,则默认返回到tolevel指定层级的父层。 # 非value层,则self.__pos默认进入下一层。 if self.isOnValueSheet(): if eKeyDict['isback'] == "" or int(eKeyDict['isback']) == 0: pass else: if level == "": self.__pos -= 1 else: self.__pos = self.__getLevelIndex(level) + 1 else: self.__pos += 1 # 重新获取次信息; self.getCurOptionInfo() def __executeToParentKey(self, tKeyDict): self.__executeKey(tKeyDict) # 层级变化 if 'tolevel' in tKeyDict and tKeyDict['tolevel'] != "": level = tKeyDict['tolevel'] self.__pos = self.__getLevelIndex(level) + 1 else: self.__pos -= 1 # 重新获取次信息; self.getCurOptionInfo() def __executeKey(self, keyDict, wait=1.0, duration=1.0): if wait != 1.0 and keyDict['wait'] != "": wait = float(keyDict['wait']) if duration != 1.0 and keyDict['duration'] != "": duration = float(keyDict['duration']) for key in keyDict['key']: self.sendKey(key, 1, duration) time.sleep(wait) def executeKey(self, keyDict, wait=1.0, duration=1.0): self.__executeKey(keyDict, wait, duration) def setValue(self, optionValue): self.__optionValue = optionValue if self.__optionValue != "" or self.__optionValue is not None: self.__optionValueInfo = self.__optionExcel.getOptionValueInfo(self.__optionName, self.__optionValue) return True if self.__optionValueInfo and self.__optionValueInfo.__len__() else False if __name__ == "__main__": exData = CExtraData() optionExcel = COptionExcel(exData) optionConfig = COptionConfig(exData, optionExcel) optionAction = COptionAction('source', None, optionConfig, optionExcel) # print optionAction.optionValueInfo print optionAction.curOptionInfo # ====================================== # # optionAction.callFirstOptionShortCutKey()