# -*- coding:utf-8 -*- import time from BaseLog import CBaseLog from ExtraData import CExtraData from OptionExcel import COptionExcel from OptionConfig import COptionConfig from OptionAction import COptionAction from ssat_sdk.source_input import SourceGenInput from ssat_sdk.tv_detect import * from ssat_sdk.device_manage.capturecard_manager import CCardManager from ssat_sdk.utils.string_util import strcmp # 测试使用到 import random class CTMenu(CBaseLog): sourceInput = SourceGenInput() # 红老鼠遥控对象; redRat3 = TvOperator() # 创建视频采集对象 captureCard = CCardManager() # 图片切割对象 imgCMP = ImageCMP() rgbColor = RGBColor() CIE = CIEluvCaculator() def __init__(self, ocrDict=None): CBaseLog.__init__(self) self.__exData = CExtraData() self.__optionExcel = COptionExcel(self.__exData) self.__optionConfig = COptionConfig(self.__exData, self.__optionExcel) self.__curOpa = None self.__lastOpa = None self.__lastTime = None @property def exData(self): return self.__exData @property def optionExcel(self): return self.__optionExcel @property def optionConfig(self): return self.__optionConfig @property def opa(self): return self.__curOpa @property def pos(self): return self.__curOpa.pos def setOpa(self, opa): self.__curOpa = opa def setCurPos(self, pos): self.__curOpa.setCurPos(pos) def clearOpa(self): self.__lastOpa = None self.__curOpa = None ''' 函数:同步两个opa信息; 参数: 返回: ''' def SyncOptionInfo(self): # 如果上次同步超过5秒,认为菜单退出; 意义不大 # if self.__lastTime and time.time() - self.__lastTime > 5: # self.info(u"前后两次同步时间差超过5秒") # self.__lastOpa = None if self.__lastOpa: self.info(u"lastOpa.pos=%d" % self.__lastOpa.pos) diffValue = self.__lastOpa.pos - self.__curOpa.getPathLength() # 前者位置大于当前opa长度; if diffValue == 0: if self.__curOpa.isOptionInPaths(self.__lastOpa.pos, self.__lastOpa.optionValue) is True: self.__curOpa.setCurPos(self.__lastOpa.pos) elif diffValue > 0: # 判断路径是否为子集关系; if self.__lastOpa.isOptionInPaths(self.__lastOpa.getPathLength(), self.__curOpa.curOptionName) is True: while diffValue > 0: diffValue -= 1 self.__lastOpa.back2ParentNode() # 同时设置当前opa位置; self.__curOpa.setCurPos(self.__curOpa.getPathLength()) elif diffValue < 0: # 判断路径是否为子集关系; if self.__curOpa.isOptionInPaths(self.__lastOpa.pos, self.__lastOpa.curOptionName) is True: self.__curOpa.setCurPos(self.__lastOpa.pos) # 记录; # self.__lastTime = time.time() self.__lastOpa = self.__curOpa def takePicture(self): img = os.path.join(getSATTmpDIR(), "menutree_runpath.png") self.captureCard.takePicture(img) if not os.path.exists(img): self.error(u"截图失败:%s" % img) return img ''' 函数:判断两值(for_ocr与ocr识别结果)是否相等; 参数: srcValue 源值,excel表格中读取的value值。 tagValue 目标值,ocr识别的结果值。 返回:相同返回True。 ''' def __isValueEqual(self, srcValue, tagValue): self.info(u"源值=%s, 目标值=%s" % (str(srcValue), str(tagValue))) ''' 函数:移动到目标节点上(路径节点和value节点) 参数: opa COptionAction对象; moveDirection 移动方向,True表示调用move_key[1](向下或向右), False表示调用move_key[0]。 maxTry 最大移动次数,防止死循环。 返回:Boolean是否在目标节点上, 文本或数值。 ''' def __move2TargetNode(self, opa, moveDirection, maxTry=15): tryCount = 0 # 是否到目标结点了; isOnTargetNode = False # 目标节点聚焦状态; targetFocus = False # 目标文本或数值; targetText = None # 移动到目标节点; while True: # 判断是否移动成功; targetFocus, targetText = opa.isOnTargetNode() if targetFocus is True: # 如果已在目标结点,退出循环; if opa.isOnTargetOption() or opa.isOnValueSheet(): self.info(u"<到达目标路径节点或value节点>") isOnTargetNode = True break # 未到目标,进入下一节点; self.info(u"<进入下一节点>") opa.enterNode() # 先处理密码框; hasBox, dealStatus = opa.dealPasswordBox() if hasBox is True and dealStatus is False: break continue # endif # 移动到下/上一节点; if moveDirection: opa.move2NextSiblingNode() else: opa.move2PrevSiblingNode() tryCount += 1 if tryCount > maxTry: self.warn(u"已%s遍历超过%d次,仍未找到焦点" % ("正向" if moveDirection else "逆向", maxTry)) break # end-while return isOnTargetNode, targetText def move2TargetNode(self, opa, moveDirection, maxTry=15): return self.__move2TargetNode(opa, moveDirection, maxTry) def back2Home(self): return self.__back2Home(self.__curOpa) ''' 函数:退出 usb 信源界面; 参数: opa COptionAction对象; 返回:如果可以直接调用信源菜单返回True,否则返回False; ''' def __exitUsbSource(self, opa): # 有些信源,需要手动enter enterKey = opa.curOptionInfo['enter_key'] # 因为信源界面消失了,需要再调用一次. opa.callFirstOptionShortCutKey() # 判断是否聚焦成功; if opa.isOnFirstOption() is True: return True self.redRat3.sendKey("ok", duration=2) # 因为信源界面消失了,需要再调用一次. opa.callFirstOptionShortCutKey() # 判断是否聚焦成功; if opa.isOnFirstOption() is True: self.redRat3.sendKey("ok", duration=2) return False self.redRat3.sendKey("exit", duration=2) # 因为信源界面消失了,需要再调用一次. opa.callFirstOptionShortCutKey() # 判断是否聚焦成功; if opa.isOnFirstOption() is True: self.redRat3.sendKey("ok", duration=2) return False self.redRat3.sendKey("return", duration=2) # 因为信源界面消失了,需要再调用一次. opa.callFirstOptionShortCutKey() # 判断是否聚焦成功; if opa.isOnFirstOption() is True: self.redRat3.sendKey("ok", duration=2) return False ''' 函数:移动到目标信源节点上。 参数: opa COptionAction对象; moveDirection 移动方向,True表示调用move_key[1](向下或向右), False表示调用move_key[0]。 maxTry 最大移动次数,防止死循环。 返回:Boolean是否在目标节点上, 文本或数值。 ''' def __move2SourceNode(self, opa, moveDirection, maxTry=15): tryCount = 0 # 目标节点聚焦状态; targetFocus = False # 目标文本或数值; targetText = None self.info(u"===========maxTry=%d" % maxTry) # 移动到目标节点; while True: # 截图,截图完后马上进入信源; pic = self.takePicture() # 有些信源,需要手动enter enterKey = opa.curOptionInfo['enter_key'] if enterKey != "default": opa.executeKey(enterKey) # 判断是否移动成功; targetFocus, targetText = opa.isOnTargetNode(pic) if targetFocus is True: self.info(u"===========到达目标信源节点===========") break # endif # 如果是usb信源,特殊处理; if targetText and 'usb' in targetText.lower(): if not self.__exitUsbSource(opa): self.info(u"///////////////////////退出USB成功。///////////////////////") # 因为信源界面消失了,需要再调用一次. opa.callFirstOptionShortCutKey() # 2次:移动到下/上一节点; opa.move2NextSiblingNode() if moveDirection else opa.move2PrevSiblingNode() opa.move2NextSiblingNode() if moveDirection else opa.move2PrevSiblingNode() opa.executeKey(enterKey) # 因为信源界面消失了,需要再调用一次. opa.callFirstOptionShortCutKey() continue # 因为信源界面消失了,需要再调用一次. opa.callFirstOptionShortCutKey() # 移动到下/上一节点; opa.move2NextSiblingNode() if moveDirection else opa.move2PrevSiblingNode() tryCount += 1 if tryCount > maxTry: self.warn(u"已%s遍历超过%d次,仍未找到焦点" % ("正向" if moveDirection else "逆向", maxTry)) break # end-while return targetFocus, targetText ''' 函数:聚焦到指定option中。 参数: opa COptionAction对象。 返回:Boolean,成功返回True. ''' def __focusOption(self, opa): # 检测路径是否有效; if opa.checkRunOptionPath() is False: self.__back2Home(opa) return False # 只有从0层开始才需要调用根菜单; if self.__curOpa.pos == 0: # 首先,调用根菜单; opa.callFirstOptionShortCutKey() # time.sleep(1) if opa.isOnFirstOption() is False: self.error(u"未聚焦到根节点上,退出") self.__back2Home(opa) return False # 遍历到目标option中; if self.__move2TargetNode(opa, True)[0] is False: if self.__move2TargetNode(opa, False)[0] is False: self.__back2Home(opa) return False return True ''' 函数:进入到指定的option中。 参数: opa COptionAction对象。 返回:Boolean,成功返回True. ''' def __openOption(self, opa): if self.__focusOption(opa) is False: return False # 到达目标option后; self.info(u"进入option") opa.enterNode() # 先处理密码框; hasBox, dealStatus = opa.dealPasswordBox() if hasBox is True and dealStatus is False: self.error(u"密码框处理失败") return False return True ''' 函数:返回到主页(不一定就是Home页,一般指返回运行前的那个画面) 参数: opa COptionAction对象; 返回: ''' def __back2Home(self, opa): self.info(u"返回主页") while opa.pos > 0: opa.back2ParentNode() self.info(u"【如果设置value时,会自动回退到上一层父节点,此处操作正常。否则要多返回一层。】") # value层的enter_key,如果按了ok会返回父层,则少退出一层return。但是目前menutree要兼容适配的旧问题,只能多退出一次。 # 如果未来出现了新的机芯,多按return键会导致退出,需要把这个机制完善 self.redRat3.sendKey("return", 1, 1) ''' 函数:设置指定的option的值为optionValue。 参数: optionName 要设置的option; optionValue option要设置的值; 返回: Boolean。 ''' def setOptionValue(self, optionName, optionValue): self.info(u"【%s】【%s】" % (optionName, optionValue)) CTMenu.sourceInput.setPattern(11) self.__curOpa = COptionAction(optionName, optionValue, self.__optionConfig, self.__optionExcel) # 检测路径是否有效; if self.__openOption(self.__curOpa) is False: return False # 遍历到value节点中; if self.__move2TargetNode(self.__curOpa, True)[0] is False: if self.__move2TargetNode(self.__curOpa, False)[0] is False: self.__back2Home(self.__curOpa) return False # 找到value节点,设置值; self.info(u"设置value值:一般只适用于Number类型或Input类型,不适合选择类型。但都必须调用,因为里面有enterkey操作") self.__curOpa.setOptionValue() # 返回主页; self.__back2Home(self.__curOpa) return True ''' 函数:移动到目标option后,读取该option的value值,与optionValue进行比较,相等表示检测值与目标值一致; 参数: optionName 要设置的option; optionValue option要设置的值; 返回: Boolean。 ''' def checkOptionValue(self, optionName, optionValue): self.info(u"【%s】【%s】" % (optionName, optionValue)) CTMenu.sourceInput.setPattern(11) self.__curOpa = COptionAction(optionName, optionValue, self.__optionConfig, self.__optionExcel) # 进入节点; if self.__openOption(self.__curOpa) is False: return False isValueEqual = False # 获取value节点值; valueFocus, valueText = self.__curOpa.isOnTargetNode() self.info(u"===当前捕获的文本内容=%s,目标文本=%s,状态:%d===" % (str(valueText), str(optionValue), valueFocus)) # 如果是数值,则判断获取的值是否相等;否则,只判断是否聚焦. if type(optionValue) == int or type(optionValue) == float: isValueEqual = True if float(optionValue) == float(valueText) else False else: isValueEqual = valueFocus # 返回主页; self.__back2Home(self.__curOpa) # 聚焦状态就是结果; self.info( u"checkOptionValue结果:optionValue=%s, ocrValue=%s, 结果=%d" % (str(optionValue), str(valueText), isValueEqual)) return isValueEqual ''' 函数:进入指定的option,并且不返回主页。 参数: optionName 指定要进入的option节点。 返回:Boolean。成功进入返回True. ''' def openOption(self, optionName): self.info(u"【%s】" % optionName) CTMenu.sourceInput.setPattern(11) self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel) # 同步前后信息; self.SyncOptionInfo() if self.__openOption(self.__curOpa) is False: return False # 聚焦状态就是结果; self.info(u"成功open指定option=%s" % optionName) return True ''' 函数:设置self.__curOpa的值; 参数: 返回: 注意:一般在使用了openOption后,才调用该函数.且该函数不返回(不调用back2Home); ''' def setValue(self, optionValue): self.info(u"开始设置值:optionName=%s, optionValue=%s" % (self.__curOpa.curOptionName, str(optionValue))) if self.__curOpa.setValue(optionValue) is False: self.info(u"该value[%s]不存在" % optionValue) return False # 遍历到value节点中; if self.__move2TargetNode(self.__curOpa, True)[0] is False: if self.__move2TargetNode(self.__curOpa, False)[0] is False: self.__back2Home(self.__curOpa) return False # 找到value节点,设置值; self.info(u"设置value值:一般只适用于Number类型或Input类型,不适合选择类型。但都必须调用,因为里面有enterkey操作") self.__curOpa.setOptionValue() return True ''' 函数:聚焦到指定option的value上,不设置不打开不返回。 参数: optionName optionValue 返回: 注意:如果optionValue是数值类型,将不会变更值。 如optionValue=10,实现该optionName的value是100,则不变更其值。 ''' def focusOptionValue(self, optionName, optionValue): self.info(u"【%s】【%s】" % (optionName, optionValue)) CTMenu.sourceInput.setPattern(11) self.__curOpa = COptionAction(optionName, optionValue, self.__optionConfig, self.__optionExcel) # 进入节点; if self.__openOption(self.__curOpa) is False: return False # 遍历到value节点中; if self.__move2TargetNode(self.__curOpa, True)[0] is False: if self.__move2TargetNode(self.__curOpa, False)[0] is False: self.__back2Home(self.__curOpa) return False return True ''' 函数:获取当前option节点的value值。 参数: optionName 要获取值的option; 返回:Boolean, str/int/float ''' def getOptionValue(self, optionName): self.info(u"【%s】" % optionName) CTMenu.sourceInput.setPattern(11) self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel) # 检测路径是否有效; if self.__openOption(self.__curOpa) is False: return False, None # 获取value节点值; valueFocus, valueText = self.__curOpa.isOnTargetNode() self.info(u"===当前捕获的文本内容=%s,聚焦状态:%d===" % (str(valueText), valueFocus)) # 返回主页; self.__back2Home(self.__curOpa) return valueFocus, valueText ''' 函数: 参数: 返回: ''' def focusOption(self, optionName): self.info(u"【%s】" % optionName) CTMenu.sourceInput.setPattern(11) self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel) return self.__focusOption(self.__curOpa) ''' 函数: 参数: 返回: ''' def inputUnlock(self, stdText, password=''): self.__curOpa = COptionAction(None, None, self.__optionConfig, self.__optionExcel) return self.__curOpa.inputUnlock(stdText, password) ''' 函数:当焦点已经在option层级上时,移动到指定的option上。 参数: optionName 目标option. 返回:Boolean,成功移动到目标节点返回True。 注意:该函数不是从根节点开始移动到目标节点,而是假定焦点已经在目标节点层级菜单上。 ''' def moveToOption(self, optionName): self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel) # 已经在该层级上. self.__curOpa.setCurPos(self.__curOpa.getPathLength() - 1) # 检测路径是否有效; if self.__curOpa.checkRunOptionPath() is False: self.__back2Home(self.__curOpa) return False # 遍历到目标option中; if self.__move2TargetNode(self.__curOpa, True)[0] is False: if self.__move2TargetNode(self.__curOpa, False)[0] is False: self.__back2Home(self.__curOpa) return False self.info(u"已移动到目标节点[%s]" % optionName) return True ''' 函数:检测输入的频道列表,是否都存在,返回不存在的频道列表。 参数: channelList 要检测的目标频道列表。 返回:Boolean, [] 如果有频道没找到,返回False,并返回没找到的频道列表。如果全部找到返回True。 ''' def checkChannelList(self, channelList, maxTry=15): failChannelList = [] checkResult = True if not channelList: self.error(u"传入的待检测的频道列表为空!") return False, failChannelList for channel in channelList: isSearched = self.checkOptionValue("ok", channel) if not isSearched: self.warn(u"频道:%s查找失败!" % str(channel)) failChannelList.append(channel) checkResult = False # 部分机芯项目(如6586外协松下)按return键无法退出频道,故改为exit键 # self.redRat3.sendKey("return") self.redRat3.sendKey("exit") else: self.info(u"频道:%s查找成功!" % str(channel)) self.redRat3.sendKey("exit") self.info(u"频道列表检测结果checkResult:%s,检测失败频道列表failChannelLis:%s" % (str(checkResult), str(failChannelList))) return checkResult, failChannelList ''' 函数:移动到黑屏的频道。 参数: channelCount 要查找多少次频道。 blackThreshold 黑屏阈值。 blackRate 黑屏比率。 返回:Boolean。找到返回True。 ''' def getBlackChannel(self, channelCount, blackThreshold=20, blackRate=2.0): result = False # 根据频道数量遍历; for i in range(0, channelCount): # 截图,取左半边; pic_path = self.takePicture() img = cv.imread(pic_path) pic_path2 = os.path.join(getSATTmpDIR(), "menutree_focus_area_half.png") self.imgCMP.saveCropPic(pic_path, pic_path2, (0, 0, img.shape[1] / 2, img.shape[0])) # 是否符合要求; result = self.imgCMP.isBlack(pic_path2, blackThreshold, 10, 1 - blackRate) if result is True: break # 下一频道号; self.redRat3.sendKey('C+') time.sleep(4) # endfor return result ''' 函数:设置信源。 参数: optionName 目标信源; 返回: 注意:切换信源是特殊的情况处理: 1、信源菜单消失过快,会导致ocr识别完成后,菜单已消失。 2、usb信源下,可能会调用source按键失败,所以需要调用其他退出键(ok、exit、return) ''' def setSourceValue(self, optionParent, optionName): self.info(u"【%s】" % optionName) CTMenu.sourceInput.setPattern(11) sourceList = self.__optionExcel.getOptionAllSiblingItemTextList1d(optionName) self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel) # 检测路径是否有效; if not self.__curOpa.checkRunOptionPath(): return False # 首先,调用根菜单; self.__curOpa.callFirstOptionShortCutKey() # 遍历到目标option中; if self.__move2SourceNode(self.__curOpa, True, sourceList.__len__() * 2)[0] is False: if self.__move2SourceNode(self.__curOpa, False, sourceList.__len__() * 2)[0] is False: self.__back2Home(self.__curOpa) return False self.info(u"成功进入指定信源:%s" % optionName) return True ''' 函数:识别传入的option / optionValue,是否在当前页面中存在 参数: option:在value不传入的情况下,option为非value层的option名; value:传入的情况下,option为value层的value_name,value则为value名 返回: boolean,是否识别到该option / optionValue ''' def checkOptionExist(self, option, value=None): self.__curOpa = COptionAction(option, value, self.optionConfig, self.optionExcel) self.__curOpa.setCurPos(self.__curOpa.getPathLength()) return self.__curOpa.isOnTargetNode()[0] ''' 调用当前opa的back2ParentNode() ''' def back2parentNode(self): return self.__curOpa.back2ParentNode() if __name__ == "__main__": ml = CTMenu() if 0: st = 2 ml.setOptionValue('picture_preset', 'personal') time.sleep(st) ml.setOptionValue('picture_preset', 'dynamic') time.sleep(st) ml.setOptionValue('picture_preset', 'stadium') time.sleep(st) ml.setOptionValue('picture_preset', 'standard') time.sleep(st) ml.setOptionValue('picture_preset', 'natural') time.sleep(st) ml.setOptionValue('picture_preset', 'movie') ml.openOption('picture_preset') ml.setValue('movie') print "--------------------------------------------------" ml.openOption('picture_preset') ml.setValue('dynamic') print "--------------------------------------------------" ml.openOption('brightness') ml.setValue(10) ml.back2Home() if 0: st = 5 value = random.randint(0, 100) time.sleep(st) ml.setOptionValue('brightness', value) time.sleep(st) ml.setOptionValue('contrast', value) time.sleep(st) ml.setOptionValue('saturation', value) time.sleep(st) ml.setOptionValue('tint', value) time.sleep(st) ml.setOptionValue('sharpness', value) time.sleep(st) ml.setOptionValue('backlight', value) if 0: st = 2 ml.checkOptionValue('picture_preset', 'personal') time.sleep(st) ml.checkOptionValue('picture_preset', 'dynamic') time.sleep(st) ml.checkOptionValue('picture_preset', 'stadium') time.sleep(st) ml.checkOptionValue('picture_preset', 'standard') time.sleep(st) ml.checkOptionValue('picture_preset', 'natural') time.sleep(st) ml.checkOptionValue('picture_preset', 'movie') if 0: st = 1 value = random.randint(0, 100) ml.setOptionValue('backlight', value) time.sleep(st) ml.checkOptionValue('backlight', value + 10) time.sleep(st) ml.checkOptionValue('backlight', value) time.sleep(st) ml.setOptionValue('brightness', value) time.sleep(st) ml.checkOptionValue('brightness', value + 10) time.sleep(st) ml.checkOptionValue('brightness', value) time.sleep(st) ml.setOptionValue('contrast', value) time.sleep(st) ml.checkOptionValue('contrast', value + 10) time.sleep(st) ml.checkOptionValue('contrast', value) time.sleep(st) ml.setOptionValue('saturation', value) time.sleep(st) ml.checkOptionValue('saturation', value + 10) time.sleep(st) ml.checkOptionValue('saturation', value) time.sleep(st) ml.setOptionValue('tint', value) time.sleep(st) ml.checkOptionValue('tint', value + 10) time.sleep(st) ml.checkOptionValue('tint', value) time.sleep(st) ml.setOptionValue('sharpness', value) time.sleep(st) ml.checkOptionValue('sharpness', value + 10) time.sleep(st) ml.checkOptionValue('sharpness', value) time.sleep(st) if 0: st = 30 ml.openOption('picture_preset') time.sleep(st) ml.openOption('brightness') time.sleep(st) ml.openOption('contrast') time.sleep(st) ml.openOption('tint') time.sleep(st) ml.openOption('sharpness') time.sleep(st) ml.openOption('backlight') time.sleep(st) if 0: st = 30 value = random.randint(0, 100) ml.focusOptionValue('brightness', value) time.sleep(st) ml.focusOptionValue('contrast', value) time.sleep(st) ml.focusOptionValue('saturation', value) time.sleep(st) ml.focusOptionValue('tint', value) time.sleep(st) ml.focusOptionValue('sharpness', value) time.sleep(st) ml.focusOptionValue('backlight', value) time.sleep(st) ml.focusOptionValue('picture_preset', 'personal') time.sleep(st) ml.focusOptionValue('picture_preset', 'dynamic') time.sleep(st) ml.focusOptionValue('picture_preset', 'stadium') time.sleep(st) ml.focusOptionValue('picture_preset', 'standard') time.sleep(st) ml.focusOptionValue('picture_preset', 'natural') time.sleep(st) ml.focusOptionValue('picture_preset', 'movie') if 0: st = 2 ml.getOptionValue('brightness') time.sleep(st) ml.getOptionValue('contrast') time.sleep(st) ml.getOptionValue('saturation') time.sleep(st) ml.getOptionValue('tint') time.sleep(st) ml.getOptionValue('sharpness') time.sleep(st) ml.getOptionValue('backlight') time.sleep(st) ml.getOptionValue('picture_preset') if 0: ml.openOption('picture') ml.moveToOption('brightness') ml.moveToOption('picture_reset') ml.moveToOption('picture_preset') if 0: # ml.setSourceValue('hdmi1') ml.setSourceValue('hdmi2') # ml.setSourceValue('dtv') print u'------------------------------' ml.setSourceValue('av') if 0: # ml.setOptionValue('auto_volume_control', 'off') # ml.setOptionValue('lock', 'on') ml.setSourceValue('dtv')