Ver Fonte

同步最新sdk;

Jeff há 5 anos atrás
pai
commit
db3f63db26

+ 75 - 41
ssat_sdk/MenuTree3/OptionAction.py

@@ -96,7 +96,8 @@ class COptionAction(CBaseLog):
         # 相关返回操作;
         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
@@ -149,7 +150,7 @@ class COptionAction(CBaseLog):
 
         # 判断当前层是否存在layout参数
         if "layout" in curInfo and 'bounds' in curInfo['layout']:
-            if curInfo['layout']['bounds'] is "":
+            if curInfo['layout']['bounds'] is ['']:
                 self.info("当前option未配置layout参数")
             elif curInfo['layout']['bounds'].__len__() != 4:
                 self.info("当前option的layout坐标配置异常:%s" % str(curInfo['layout']['bounds']))
@@ -171,6 +172,10 @@ class COptionAction(CBaseLog):
         # 是否有另配置快捷按键代替根节点按键;
         if 'shortcut_key' in self.__optionPaths['First']:
             self.sendKey(self.__optionPaths['First']['shortcut_key'], 1, waitTime)
+        elif self.__optionPaths['First']['others'] != "":
+            others = json.loads(self.__optionPaths['First']['others'])
+            if 'first_key' in others:
+                self.sendKey(others['first_key'], 1, waitTime)
         else:
             self.sendKey(self.__optionPaths['First']['parent'], 1, waitTime)
             self.warn(u"表格没有shortcut_key字段,执行默认的parent按键:%s" % self.__optionPaths['First']['parent'])
@@ -392,7 +397,7 @@ class COptionAction(CBaseLog):
         if self.__optionValue == "":  # 该值空,表明不会移动到value sheet中;
             return False
         else:
-            return True if self.__pos >= self.__optionPaths.__len__() else False
+            return self.__isOnValueSheet
 
     '''
     函数:移动到下一兄弟节点;
@@ -407,18 +412,17 @@ class COptionAction(CBaseLog):
     '''
 
     def move2NextSiblingNode(self):
-        # 获取当前option信息;
-        if self.getCurOptionInfo():
-            # 析出move key;
+        # 析出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)
-        else:
-            valueMoveKey = self.__optionValueInfo['move_key']
-            self.sendKey(valueMoveKey[1], 1, 1)
-
+				
     '''
     函数:移动到上一兄弟节点
     参数:无
@@ -432,17 +436,16 @@ class COptionAction(CBaseLog):
     '''
 
     def move2PrevSiblingNode(self):
-        # 获取当前option信息;
-        if self.getCurOptionInfo():
-            # 析出move key;
+        # 析出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)
-        else:
-            valueMoveKey = self.__optionValueInfo['move_key']
-            self.sendKey(valueMoveKey[0], 1, 1)
 
     '''
     函数:返回到父节点
@@ -458,11 +461,11 @@ class COptionAction(CBaseLog):
 
     def back2ParentNode(self):
         # 获取当前option信息;
-        if self.getCurOptionInfo():
-            tKeyDict = self.__curOptionInfo['toparent_key']
+        if self.isOnValueSheet():
+            tKeyDict = self.__optionValueInfo['toparent_key']
             peKeyDict = self.__prevOptionInfo['enter_key']
         else:
-            tKeyDict = self.__optionValueInfo['toparent_key']
+            tKeyDict = self.__curOptionInfo['toparent_key']
             peKeyDict = self.__prevOptionInfo['enter_key']
 
         '''
@@ -485,18 +488,20 @@ class COptionAction(CBaseLog):
                     如果包含,执行完toparent_key后self.__pos等于tolevel指定的层级
         '''
         # 当toparent_key没有配时;
-        if tKeyDict == "":
+        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("当前层未配toparent_key,且父层enter_key为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
@@ -508,6 +513,8 @@ class COptionAction(CBaseLog):
             else:
                 self.info("当前层toparent_key为%s" % str(tKeyDict['key']))
         self.__executeToParentKey(tKeyDict)
+        # 从value层执行完返回后,则不在valuesheet里了
+        self.__isOnValueSheet = False
 
     '''
         在忽略大小写的情况下,获取到该level对应的g_level的下标值
@@ -526,22 +533,22 @@ class COptionAction(CBaseLog):
     '''
 
     def enterNode(self):
-        # 获取当前option信息;
-        if self.getCurOptionInfo():
-            # 析出enter key;
-            # 优先使用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)
-        else:
+        # 析出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)
 
     '''
     函数:设置当前节点位置;
@@ -643,13 +650,33 @@ class COptionAction(CBaseLog):
             self.error(u"paths路径空")
             return False
 
-        if self.__pos >= self.__optionPaths.__len__():
-            # self.__curOptionInfo = None  # 是否需要在到达value层级后置空,可能保留值会有比较多用处!
-            self.warn(u"已到达value节点,无法获取路径信息")
-            self.__prevOptionName = self.__curOptionName
-            self.__prevOptionInfo = self.__curOptionInfo
+        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']
@@ -731,7 +758,7 @@ class COptionAction(CBaseLog):
                     break
 
         # 在value表中;
-        if pos == self.__optionPaths.__len__():
+        if self.isOnValueSheet():
             valueOptionName = self.__optionPaths[g_level[self.__optionPaths.__len__() - 1]]['option']
             valueNames = self.__optionExcel.getOptionAllValueName(valueOptionName)
             for name in valueNames:
@@ -800,7 +827,7 @@ class COptionAction(CBaseLog):
         # 获取文本区域框;
         textBox = self.__optionFocus.getFocusTextBox(optionName, focusBox, isValueSheet)
         # 配置文本图片路径,保存文本区域截图;
-        text_pic = os.path.join(getSATTmpDIR(), "meuttree_area_text.png")
+        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))
@@ -872,7 +899,7 @@ class COptionAction(CBaseLog):
         # endfor
 
         # 默认返回;
-        return found, False, 0 if isNumberText else ""
+        return found, False, 0 if isNumberText else curFocusText
 
     '''
     函数:获取动态图片文本内容
@@ -891,6 +918,10 @@ class COptionAction(CBaseLog):
         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'])
@@ -905,10 +936,14 @@ class COptionAction(CBaseLog):
 
         # 发送鲜活键, 保证界面鲜活;
         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']
@@ -1114,7 +1149,6 @@ class COptionAction(CBaseLog):
         wait,duration是为了兼容旧menutree按照others配置参数的处理方法。
         如果others中有配waitTime和duration,优先采用others里的配置,否则则采用enterKey里的配置。
     '''
-
     def __executeEnterKey(self, eKeyDict):
         # 先从config读取
         wait = self.__optionConfig.getParentWaitTime(self.__curOptionInfo['parent'])
@@ -1125,7 +1159,7 @@ class COptionAction(CBaseLog):
                 if "waitTime" in others:
                     if others['waitTime'] != "":
                         wait = float(others['waitTime'])
-            except Exception, e:
+            except Exception,e:
                 pass
         # 读出来的enter_key,不为空或者default时,按格式执行enter_key
         if not strcmp(eKeyDict['key'][0], 'default') and eKeyDict['key'][0] != "":

+ 65 - 4
ssat_sdk/MenuTree3/OptionExcel.py

@@ -743,7 +743,7 @@ class COptionExcel(CBaseLog):
                 dict_option['first_parent'] = option
         else:
             for level in paths:
-                if paths[level]['option'] == optionName.lower():
+                if paths[level]['option'].lower() == optionName.lower():
                     found = True
                     dict_option['layers'] = g_level.index(level)
                     dict_option['first_parent'] = paths['First']['parent']
@@ -771,6 +771,67 @@ class COptionExcel(CBaseLog):
         # 返回结果;
         return found, dict_option
 
+    '''
+        函数:获取指定parent的详细信息;
+        参数:
+            parentName      指定的路径表中的parent名称
+        返回:字典
+        示例:
+        {
+            "layers":"第几层",
+            "level":"",
+            "others":"",
+            "toparent_key": "",
+            "enter_key":"",
+            "move_key":"",
+			"layout":"",
+            "option":[parent下的optionName列表],
+        }
+
+        测试:通过。对应UIT_tree中的get_option
+    '''
+    def getParentInfo(self, parentName):
+        # 参数校验;
+        if parentName is None or parentName.__len__() == 0:
+            self.error(u"optionName空,退出执行")
+            return False, {}
+        # 编码转换;
+        if type(parentName) == str:
+            parentName = unicode(parentName)
+
+        found = False
+        dict_parent = {}
+        # 先读取非value层的parent;
+        paths = reversed(self.__pathParams.paths)
+        for level in paths:
+            for parent in self.__pathParams.paths[level]:
+                if parent.lower() == parentName.lower():
+                    found = True
+                    dict_parent['level'] = level
+                    dict_parent['others'] = self.__pathParams.paths[level][parent]['others']
+                    dict_parent['enter_key'] = self.__pathParams.paths[level][parent]['enter_key']
+                    dict_parent['move_key'] = self.__pathParams.paths[level][parent]['move_key']
+                    # 读取toparent_key
+                    if 'toparent_key' in self.__pathParams.paths[level][parent]:
+                        dict_parent['toparent_key'] = self.__pathParams.paths[level][parent]['toparent_key']
+                    else:
+                        dict_parent['toparent_key'] = ''
+                    # 读取layout
+                    if 'layout' in self.__pathParams.paths[level][parent]:
+                        dict_parent['layout'] = self.__pathParams.paths[level][parent]['layout']
+                    else:
+                        dict_parent['layout'] = ''
+                    dict_parent['layers'] = g_level.index(level)
+                    optionList = []
+                    for val in self.__pathParams.paths[level][parent]['value']:
+                        if 'option' in val:
+                            optionList.append(val['option'])
+                    dict_parent['option'] = optionList
+                    return found, dict_parent
+
+        return found, dict_parent
+
+
     '''
     函数:获取指定的option在value表中的所有value字段;
     参数:
@@ -954,9 +1015,9 @@ if __name__ == "__main__":
     # print "getOptionValueInfo", opxls.getOptionValueInfo('source')
     # print "getOptionValueText", opxls.getOptionValueText('source', 'atv')
     # print "getOptionValueText", opxls.getOptionValueText('source', 'dtv')
-    print "getOptionInfo", opxls.getOptionInfo('lock')
-    print "getOptionInfo", opxls.getOptionInfo('picture_preset')
-    print "getOptionInfo", opxls.getOptionInfo('picture')
+    print "getParentInfo", opxls.getParentInfo('picture_preset')
+    # print "getOptionInfo", opxls.getOptionInfo('picture_preset')
+    # print "getOptionInfo", opxls.getOptionInfo('picture')
     # print "getOptionAllValueName", opxls.getOptionAllValueName('source')
     # ##########################################
     # paths = opxls.getOptionPaths('source')

+ 41 - 199
ssat_sdk/MenuTree3/TMenu.py

@@ -186,19 +186,23 @@ class CTMenu(CBaseLog):
         # 有些信源,需要手动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
+        '''
+            现在测试要插上U盘测试,而插上U盘以后,USB信源下的聚焦框会干扰到信源列表的聚焦框识别;
+            因此注掉下面两段可能会干扰信源切换的代码,一旦进入USB信源,统一按exit键退出。
+        '''
+        # # 因为信源界面消失了,需要再调用一次.
+        # 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)
         # 因为信源界面消失了,需要再调用一次.
@@ -360,10 +364,17 @@ class CTMenu(CBaseLog):
         CTMenu.sourceInput.setPattern(11)
         self.__curOpa = COptionAction(optionName, optionValue, self.__optionConfig, self.__optionExcel)
 
+        # 检测value值是否有效
+        optionValueInfo = self.__optionExcel.getOptionValueInfo(optionName, optionValue)
+        if optionValueInfo == []:
+            self.error(u"当前optionName:[%s],optionValue:[%s],读取相关参数异常!!!请检查带入参数或者MenuTree配置的有效性!!!"%(optionName, optionValue))
+            return False
+
         # 检测路径是否有效;
         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:
@@ -417,16 +428,18 @@ class CTMenu(CBaseLog):
     函数:进入指定的option,并且不返回主页。
     参数:
         optionName          指定要进入的option节点。
+        fromFirst           是否从第一层开始执行。默认从第一层执行,如果传入False,则以上一次执行所在层级来执行。
     返回:Boolean。成功进入返回True.
     '''
 
-    def openOption(self, optionName):
+    def openOption(self, optionName, fromFirst = True):
         self.info(u"【%s】" % optionName)
         CTMenu.sourceInput.setPattern(11)
         self.__curOpa = COptionAction(optionName, None, self.__optionConfig, self.__optionExcel)
 
-        # 同步前后信息;
-        self.SyncOptionInfo()
+        if not fromFirst:
+            # 同步前后信息;
+            self.SyncOptionInfo()
 
         if self.__openOption(self.__curOpa) is False:
             return False
@@ -580,7 +593,16 @@ class CTMenu(CBaseLog):
             self.error(u"传入的待检测的频道列表为空!")
             return False, failChannelList
         for channel in channelList:
-            isSearched = self.checkOptionValue("ok", channel)
+            # isSearched = self.checkOptionValue("ok", channel)
+            opa = COptionAction(channel, None, self.__optionConfig, self.__optionExcel)
+            # 检查路径是否存在
+            if opa.checkRunOptionPath() is False:
+                self.error(u"频道:%s读取失败!!!请检查MenuTree配置!!!"%str(channel))
+                failChannelList.append(channel)
+                checkResult = False
+                continue
+            self.__curOpa = opa
+            isSearched = self.__focusOption(opa)
             if not isSearched:
                 self.warn(u"频道:%s查找失败!" % str(channel))
                 failChannelList.append(channel)
@@ -603,7 +625,7 @@ class CTMenu(CBaseLog):
     返回:Boolean。找到返回True。
     '''
 
-    def getBlackChannel(self, channelCount, blackThreshold=20, blackRate=2.0):
+    def getBlackChannel(self, channelCount, blackThreshold=20, blackRate=0.8):
         result = False
         # 根据频道数量遍历;
         for i in range(0, channelCount):
@@ -680,184 +702,4 @@ class CTMenu(CBaseLog):
 
 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')
+    ml.openOption("picture_preset")

BIN
ssat_sdk/MenuTree3/接口清单.xlsx


+ 1 - 1
ssat_sdk/UATree/UAT_PathManage.py

@@ -83,7 +83,7 @@ class UATPathManage():
         if curParent is None:
             result,forwardPath = self.getOptionPath(option)
             return backPath,forwardPath
-        print "genOptionSmartPath:curParent:", curParent
+        # print "genOptionSmartPath:curParent:", curParent
         curOptionDict = curParent[UATTree.TAB_OPTION]
         if curOptionDict.__len__() > 0:
             curOption = curOptionDict[curOptionDict.keys()[0]]

+ 443 - 285
ssat_sdk/UATree/UAT_focusCommand.py

@@ -3,71 +3,22 @@ import time,sys,os
 from UAT_tree import UATTree
 from UAT_log import error,info,debug
 from UAT_PathManage import UATPathManage
-from UAT_runnerCommand import UATRunnerCommand
+from UIParamUtil import UIParamUtil
+# from UAT_runnerCommand import UATRunnerCommand
 from ssat_sdk.python_uiautomator import PyUIAutomator,DirectionManageAndroid,FocusManageAndroid
+import math
+import traceback
 
 ERROR = True
 INFO = True
-DEBUG = True
+DEBUG = True  # 上传SVN版本此参数关闭
 '''
 采用uiautomator技术,处理界面定位问题,用于移动焦点到目标组件上
 '''
 class FocusCommand():
     cls = "FocusCommand"
-    UIObjParam = {
-        "text": None,  # MASK_TEXT,
-        "textContains": None,  # MASK_TEXTCONTAINS,
-        "textMatches": None,  # MASK_TEXTMATCHES,
-        "textStartsWith": None,  # MASK_TEXTSTARTSWITH,
-        "className": None,  # MASK_CLASSNAME
-        "classNameMatches": None,  # MASK_CLASSNAMEMATCHES
-        "description": None,  # MASK_DESCRIPTION
-        "descriptionContains": None,  # MASK_DESCRIPTIONCONTAINS
-        "descriptionMatches": None,  # MASK_DESCRIPTIONMATCHES
-        "descriptionStartsWith": None,  # MASK_DESCRIPTIONSTARTSWITH
-        "checkable": None,  # MASK_CHECKABLE
-        "checked": None,  # MASK_CHECKED
-        "clickable": None,  # MASK_CLICKABLE
-        "longClickable": None,  # MASK_LONGCLICKABLE,
-        "scrollable": None,  # MASK_SCROLLABLE,
-        "enabled": None,  # MASK_ENABLED,
-        "focusable": None,  # MASK_FOCUSABLE,
-        "focused": None,  # MASK_FOCUSED,
-        "selected": None,  # MASK_SELECTED,
-        "packageName": None,  # MASK_PACKAGENAME,
-        "packageNameMatches": None,  # MASK_PACKAGENAMEMATCHES,
-        "resourceId": None,  # MASK_RESOURCEID,
-        "resourceIdMatches": None,  # MASK_RESOURCEIDMATCHES,
-        "index": None,  # MASK_INDEX,
-        "instance": None  # MASK_INSTANCE,
-    }
-    UATParamMap = {
-        "text":"text",  # MASK_TEXT,
-        "textContains":"textContains",  # MASK_TEXTCONTAINS,
-        "textMatch":"textMatches",  # MASK_TEXTMATCHES,
-        "textSWith":"textStartsWith",  # MASK_TEXTSTARTSWITH,
-        "class":"className",  # MASK_CLASSNAME
-        "classMatches":"classNameMatches",  # MASK_CLASSNAMEMATCHES
-        "desc":"description",  # MASK_DESCRIPTION
-        "descContain":"descriptionContains",  # MASK_DESCRIPTIONCONTAINS
-        "descMatch":"descriptionMatches",  # MASK_DESCRIPTIONMATCHES
-        "descSWith":"descriptionStartsWith",  # MASK_DESCRIPTIONSTARTSWITH
-        "checkable":"checkable",  # MASK_CHECKABLE
-        "checked":"checked",  # MASK_CHECKED
-        "clickable":"clickable",  # MASK_CLICKABLE
-        "lClickable":"longClickable",  # MASK_LONGCLICKABLE,
-        "scrollable":"scrollable",  # MASK_SCROLLABLE,
-        "enable":"enabled",  # MASK_ENABLED,
-        "focusable":"focusable",  # MASK_FOCUSABLE,
-        "focused":"focused",  # MASK_FOCUSED,
-        "selected":"selected",  # MASK_SELECTED,
-        "pkg":"packageName",  # MASK_PACKAGENAME,
-        "pkgMatch":"packageNameMatches",  # MASK_PACKAGENAMEMATCHES,
-        "resid":"resourceId",  # MASK_RESOURCEID,
-        "residMatch":"resourceIdMatches",  # MASK_RESOURCEIDMATCHES,
-        "index":"index",  # MASK_INDEX,
-        "instance":"instance"  # MASK_INSTANCE,
-    }
+    UIObjParam = UIParamUtil.UIObjParam
+    UATParamMap = UIParamUtil.UATParamMap
     def __init__(self, runnerCommand):
         self.runnerCommand = runnerCommand
         self.pyU = PyUIAutomator()
@@ -76,6 +27,68 @@ class FocusCommand():
         self.inLayout= False
 
     '''
+    解析option的焦点组件参数,返回focusView或者focus-select组件参数
+    :param option: option字典
+    :return 返回 focus焦点参数字典,采用setObjParam可以转换成UIObject识别需要的参数。
+    如果为None,则表示不需要焦点选中。
+    '''
+    def getFPFromOption(self, option):
+        focusParam = None
+        fsParam = option[UATTree.TAB_FOCUS_SELECT]
+        fvParam = option[UATTree.TAB_FOCUSE_VIEW]
+        if fsParam[UATTree.FS_Type].__len__() > 1:
+            if fsParam[UATTree.FS_Type].lower() == "focus":
+                fsParam["focus"] = True
+                focusParam = fsParam
+            elif fsParam[UATTree.FS_Type].lower() == "select":
+                fsParam["select"] = True
+                focusParam = fsParam
+            elif fsParam[UATTree.FS_Type].lower() == "no":
+                focusParam = None
+            else:
+                error(self.cls, "getFPFromOption", "Option %s focus-select参数异常。" % option[UATTree.TAB_NAME], ERROR)
+        else:
+            focusParam = fvParam
+        return focusParam
+
+    '''
+    解析option的焦点组件参数,返回focusView或者focus-select组件参数.
+    在getFPFromOption返回值上,采用setObjParam转换成UIObject识别需要的参数。
+    :param option: option字典
+    :return 返回 focus焦点UIObject参数字典.
+    '''
+    def getFPObjParam(self, option):
+        focusParam = self.getFPFromOption(option)
+        fpObjParam = UIParamUtil.setObjParam(focusParam)
+        return fpObjParam
+
+    '''
+    解析存储在UATree里的moveKey字典,得到按键类型、键值数组、最大尝试次数
+    :param moveKey, parent字典中moveKey字典
+    :param optionCount: moveKey对应的parent的option个数
+    :return keyType, keyCodeList, Max_Trye
+    '''
+    def parseMoveKey(self,moveKey, optionCount):
+        if moveKey[UATTree.Max_Try] != "":
+            Max_Try = int(moveKey[UATTree.Max_Try])
+        else:
+            Max_Try = optionCount
+        findDirection = ["down","up"]
+        keyType = UATTree.Key_Event
+        if moveKey[UATTree.Key_Event].__len__() > 1:
+            findDirection = moveKey[UATTree.Key_Event]
+            keyType = UATTree.Key_Event
+        elif moveKey[UATTree.Key_IR].__len__() > 1:
+            findDirection = moveKey[UATTree.Key_IR]
+            keyType = UATTree.Key_IR
+        elif moveKey[UATTree.Key_Input].__len__() > 1:
+            inputCmd = moveKey[UATTree.Key_Input]
+            #TODO input情况的处理
+            return None,None,None
+        else:
+            return None,None,None
+        return keyType, findDirection, Max_Try
+    '''
     在parent界面,将焦点移动到目标option上。
     焦点定位:根据layout是不是限制焦点范围,进行焦点组件寻找,焦点组件类型:focus、select、focusView、long-click
     目标定位:纯粹是根据optionView配置组建坐标定位
@@ -83,223 +96,398 @@ class FocusCommand():
     :param option:目标option字典
     :return True/False
     '''
-    def focusOptionView(self, parent, option):
+    def focusOptionView(self, parent, option,chooseType):
         #是否采用layout限制焦点判断范围
         layout = parent[UATTree.TAB_LAYOUT]
         print "focusOptionView,layout:",layout
         self.inLayout = layout[UATTree.Layout_Limit]
         # 找到目标optionView参数
         layoutUIObj = {}
-        optionUIObjParam = self.setObjParam(option[UATTree.TAB_OPTION_VIEW])
+        optionUIObjParam = UIParamUtil.setObjParam(option[UATTree.TAB_OPTION_VIEW])
         if self.inLayout == 1:
-            layoutUIObjParam = self.setObjParam(layout)
+            layoutUIObjParam = UIParamUtil.setObjParam(layout)
         else:
             layoutUIObjParam = {}
         # 获取move key按键
         moveKey = parent[UATTree.TAB_MOVE_KEY]
-        if moveKey[UATTree.Max_Try] != "":
-            Max_Try = int(moveKey[UATTree.Max_Try])
-        else:
-            Max_Try = parent[UATTree.TAB_OPTION].__len__()
-        findDirection = ["down","up"]
-        keyType = UATTree.Key_Event
-        if moveKey[UATTree.Key_Event].__len__() > 1:
-            findDirection = moveKey[UATTree.Key_Event]
-            keyType = UATTree.Key_Event
-        elif moveKey[UATTree.Key_IR].__len__() > 1:
-            findDirection = moveKey[UATTree.Key_IR]
-            keyType = UATTree.Key_IR
-        elif moveKey[UATTree.Key_Input].__len__() > 1:
-            inputCmd = moveKey[UATTree.Key_Input]
-            #TODO input情况的处理
-            return False
-        else:
+        keyType, findDirection,Max_Try = self.parseMoveKey(moveKey, parent[UATTree.TAB_OPTION].__len__())
+        if keyType is None:
             error(self.cls, "focusTargetOption", option[UATTree.TAB_NAME] + " option 读取 move_key失败", ERROR)
             return False
-        #获取焦点View和optionview的area参数
+        #获取optionview的View_sambounds参数
         osamBounds = option[UATTree.TAB_OPTION_VIEW][UATTree.View_sambounds]
-        fsamBounds = ""
         #获取焦点view参数
-        fsParam = option[UATTree.TAB_FOCUS_SELECT]
-        fvParam = option[UATTree.TAB_FOCUSE_VIEW]
-        fvUIParam = self.setObjParam(fvParam)
-        if fsParam[UATTree.FS_Type].__len__() > 1:
-            if fsParam[UATTree.FS_Type].lower() == "focus":
-                fsParam["focused"] = True
-            elif fsParam[UATTree.FS_Type].lower() == "select":
-                fsParam["select"] = True
-            else:
-                error(self.cls,"focusOptionView","Option %s focus-select参数异常。"%option[UATTree.TAB_NAME], ERROR)
-                return False
-            fsamBounds = fsParam[UATTree.View_sambounds]
-            fsUIParam = self.setObjParam(fsParam)
-            return self.toDestObj(optionUIObjParam, fsUIParam,Max_Try,findDirection,keyType,layoutUIObjParam,fsamBounds,osamBounds)
-        elif fvUIParam.__len__() > 0:
-            fsamBounds = fvParam[UATTree.View_sambounds]
-            return self.toDestObj(optionUIObjParam, fvUIParam,Max_Try,findDirection,keyType,layoutUIObjParam,fsamBounds,osamBounds)
+        focusParam = self.getFPFromOption(option)
+        focusUIParam = UIParamUtil.setObjParam(focusParam)
+        if focusParam is None:
+            fsamBounds = []
         else:
-            error(self.cls, "focusOptionView", "Option %s focusView参数异常。" % option[UATTree.TAB_NAME], ERROR)
-            return False
+            fsamBounds = focusParam[UATTree.View_sambounds]
+        if chooseType.lower()== 'no':
+            return self.getNoFocusedObj(option, Max_Try, findDirection, keyType, layoutUIObjParam)
+        else:
+            return self.toDestObj(parent, option, Max_Try, findDirection, keyType, layoutUIObjParam,fsamBounds,osamBounds)
 
-    def toDestObj(self, destUIParam, focusObjParam, Max_Try=10, findDirections=["down","up"], keyType=UATTree.Key_Event
-                  ,layoutUIParam={},fsamBounds="",osamBounds=""):
+    '''
+    传入uiautomator需要的目标组件参数,获取无聚焦属性的option对象  
+    '''
+    def getNoFocusedObj(self,option, Max_Try, findDirection, keyType, layoutUIObjParam):
+        count = 0
+        directIndex = 0
+        while(True):
+            if count >= Max_Try and directIndex >= findDirection.__len__()-1:
+                break
+            time.sleep(0.1)
+            destUIObject = self.getOptionUIObj(option, layoutUIObjParam)
+            if destUIObject and destUIObject.exists():
+                info(self.cls,"getNoFocusedObj","找到目标option %s"%option[UATTree.TAB_NAME],INFO)
+                return True
+            else:
+                if count < Max_Try:
+                    count = count + 1
+                elif count >= Max_Try and directIndex<findDirection.__len__() -1:
+                    count=0
+                    Max_Try *= 2
+                    directIndex = directIndex + 1
+                self.fm.pressKeyByType(findDirection[directIndex], keyType)
+        error(self.cls, "getNoFocusedObj", "执行%s 次查找,仍未找到目标焦点!!" % (str(Max_Try)), ERROR)
+        return False
+
+    '''
+    传入uiautomator需要的目标组件参数和焦点框参数,用于寻找目标。
+    '''
+    def toDestObj(self, parent, option, Max_Try=10, findDirections=["down","up"], keyType=UATTree.Key_Event,
+                  layoutUIParam={}, fsamBounds=[],osamBounds=[]):
         # 按照传入的方向寻找Max_Try次,如果仍未聚焦到选中area,则按照传入方向的反方向 反向寻找 2*Max_Try 次
-        print "toDestObj,enter:",Max_Try,findDirections,keyType
+        print "toDestObj,enter:",Max_Try,findDirections,keyType,fsamBounds,osamBounds
         count = 0
         Max_Try = Max_Try
         directIndex = 0
+        focusCount = 0  # 当目标和聚焦点在一个页面同时存在时,从当前聚焦点到目标的移动次数
+        focusedBoundsPre = {u'top': 0, u'left': 0, u'right': 0, u'bottom': 0}  # 上一轮聚焦点的坐标
+        destBoundsPre = {u'top': 0, u'left': 0, u'right': 0, u'bottom': 0}  # 上一轮目标的坐标
         while (True):
-            if count >= Max_Try and directIndex >= findDirections.__len__():
+            if count >= Max_Try and directIndex >= findDirections.__len__()-1:
                 break
             # 等待聚焦效果刷新完成,稳定之后再获取相关的属性
-            time.sleep(0.5)
-            print "toDestObj,focusObjParam:",focusObjParam
-            focusedUIObject = self.pyU.getUiObject2(focusObjParam)
-            focusedBounds = focusedUIObject.info['bounds']
-            # print "focusedBounds:",focusedBounds
-            if layoutUIParam.__len__() > 0:
-                layoutUIObj = self.pyU.getUiObject2(layoutUIParam)
-                destUIObject = layoutUIObj.child(**destUIParam)
-            else:
-                destUIObject = self.pyU.getUiObject2(destUIParam)
-            if destUIObject:
-                print "focusOptionView,focusedBounds,destUIBounds:",focusedBounds, destUIObject.info['bounds']
+            time.sleep(0.1)
+            destUIObject = self.getOptionUIObj(option,layoutUIParam)
+            focusParam = self.getFPFromOption(option)
+            #option是否需要聚焦
+            needFocus = True
+            if focusParam.has_key(UATTree.FS_Type) and \
+                focusParam[UATTree.FS_Type] == "no":
+                needFocus = False
+            if destUIObject and destUIObject.exists(): # 有聚焦属性的option存在不代表已聚焦,无聚焦属性的option存在即当作已聚焦
                 try:
+                    retCode, focusedUIObject = self.getChooseUIObjP(parent, layoutUIParam,needFocus=needFocus)
+                    debug(self.cls, "toDestObj", "Parent %s 当前聚焦框retCode:%d" % (parent[UATTree.TAB_NAME],retCode), DEBUG)
+                    #如果焦点不存在,只需要判断optionView存在即可算是聚焦成功
+                    if retCode == self.Focus_Need_Not:
+                        return True
+                    #根据已获取到destUIObject和focusedUIObject,进行是否选中目标option判断,
+                    #未选中,则继续利用moveKey查找
+                    focusedBounds = focusedUIObject.info['bounds']
+                    info(self.cls,"toDestObj","当前聚焦框所在坐标:%s"%focusedBounds,INFO)
                     destBounds = destUIObject.info['bounds']
-                    # print "destBounds:", destBounds
-                    if self.hasFocusDest(focusedBounds, destBounds, ):
-                        print '成功聚焦到目标焦点:',destUIParam
+                    info(self.cls, "toDestObj", "已寻找%s次" % focusCount, INFO)
+                    info(self.cls,"toDestObj","目标Option %s坐标:%s"%(option[UATTree.TAB_NAME], destBounds),INFO)
+                    if self.hasFocusDest(focusedBounds, destBounds, fsamBounds=fsamBounds, osamBounds=osamBounds):
+                        info(self.cls,"toDestObj","成功聚焦到目标焦点:%s"%option[UATTree.TAB_NAME],INFO)
                         return True
+                    # 如果往同一个方向跑了5次,聚焦坐标和目标坐标的位置都没变化,则说明目标可能为非聚焦,跑不动了
+                    if focusCount<5:
+                        direction = self.dm.getTargetDirection(focusedBounds, destBounds, findDirections)
+                        self.dm.goOneStep(self.pyU, direction, keyType)
+                        isSameFocused = self.dm.isSameBounds(focusedBoundsPre,focusedBounds) # 前一次聚焦点与当前聚焦点坐标对比
+                        isSameDest = self.dm.isSameBounds(destBoundsPre,destBounds)
+                        if isSameFocused == True and isSameDest == True:
+                            focusCount += 1
+                            focusedBoundsPre = focusedBounds
+                            destBoundsPre = destBounds
+                        if focusCount == 0:
+                            focusCount += 1 # 如果focusCount=0,则将当前聚焦点和目标坐标赋值给前一次
+                            focusedBoundsPre = focusedBounds
+                            destBoundsPre = destBounds
                     else:
-                        count = count + 1
-                    direction = self.dm.getTargetDirection(focusedBounds, destBounds)
-                    self.dm.goOneStep(self.pyU, direction, keyType)
+                        error(self.cls, "toDestObj", "未找到目标焦点!!!", ERROR)
+                        return False
+
                 except Exception,e:
-                    print "toDestObj,e:",e
+                    info(self.cls,"toDestObj","未获取到目标/焦点对象坐标:count:%d,Max_Try:%d,directIndex:%d"%(count,Max_Try,directIndex),INFO)
+                    traceback.print_exc()
                     # 出现控件出现一半的时候,获取控件信息会报错
                     if count < Max_Try:
                         count = count + 1
-                    else:
+                    elif count >= Max_Try and directIndex < findDirections.__len__() -1:
+                        count=0
+                        Max_Try *= 2
                         directIndex = directIndex + 1
                     self.fm.pressKeyByType(findDirections[directIndex], keyType)
             # 如果界面中没有目标文字的控件出现,则按照传入的方向寻找Max_Try次;仍然未找到 则反方向寻找2*Max_Try次
             else:
                 if count < Max_Try:
                     count = count + 1
-                    self.fm.pressKeyByType(findDirections[directIndex], keyType)
-                else:
+                elif count >= Max_Try and directIndex<findDirections.__len__() -1:
+                    count=0
+                    Max_Try *= 2
                     directIndex = directIndex + 1
-                    self.fm.pressKeyByType(findDirections[directIndex], keyType)
+                self.fm.pressKeyByType(findDirections[directIndex], keyType)
 
-        print "执行%s 次查找,仍未找到目标焦点!!" % (str(Max_Try))
+        error(self.cls, "toDestObj", "执行%s 次查找,仍未找到目标焦点!!" % (str(Max_Try)), ERROR)
         return False
 
     '''
     根据配置的样本焦点框和OptionView 区域坐标,计算是否聚焦
+    :param focusedBounds 当前电视焦点框坐标
+    :param destBounds 当前电视目标坐标
+    :param fsamBounds 取样时的焦点框坐标
+    :param osamBounds 取样时的目标坐标
+    :return True/False  True:焦点在目标上;False:焦点不在目标上
     '''
-    def hasFocusDest(self,focusedBounds, destBounds, fsamBounds="",osamBounds=""):
+    def hasFocusDest(self,focusedBounds, destBounds, fsamBounds=[],osamBounds=[]):
+        # print "hasFocusDest,focusedBounds,destBounds,fsamBounds,osamBounds:",focusedBounds, destBounds,fsamBounds,osamBounds
         if fsamBounds.__len__() < 1 or osamBounds.__len__()<1:
             return self.dm.isHasAnotherBounds(focusedBounds, destBounds)
         else:#在焦点框bounds特别大时,同时包含多个目标optionView时,用此方法判断是否选中。
-            fsamBounds = self.strToBounds(fsamBounds)
-            osamBounds = self.strToBounds(osamBounds)
-            sdx=fsamBounds[0][0] - osamBounds[0][0]
-            sdy=fsamBounds[0][1] - osamBounds[0][1]
-            sdrate = self.calPointAngle(fsamBounds)
-
-            focusBounds = self.strToBounds(focusedBounds)
-            destBounds = self.strToBounds(destBounds)
-            dx = focusBounds[0][0] - destBounds[0][0]
-            dy = focusBounds[0][1] - destBounds[0][1]
-            drate = dy / dx
-            if (drate - sdrate) < 5:
+            focusBounds = UIParamUtil.atxBounds2UATBounds(focusedBounds)
+            destBounds = UIParamUtil.atxBounds2UATBounds(destBounds)
+            print "fsamBounds,osamBounds"
+            sdrate = self.calPointAngle(fsamBounds[0], osamBounds[0])
+            sdLine = self.calPointLine(fsamBounds[0], osamBounds[0])
+            print "focusBounds,destBounds"
+            drate = self.calPointAngle(focusBounds[0],destBounds[0])
+            dLine = self.calPointLine(focusBounds[0],destBounds[0])
+            if abs(drate - sdrate) < 5 and abs(dLine - sdLine) < 30:
                 return True
             else:
                 return False
-
     '''
-    计算点p1相对点p2的角度
+    计算点p2相对点p1的角度
     '''
     def calPointAngle(self, p1, p2):
-        dx = p1[0]-p2[0]
-        dy = p1[1]-p2[1]
+        angle = 0.0
+        print "calPointAngle,p1,p2:", p1,p2
+        dx = float(p2[0])-float(p1[0])
+        dy = float(p2[1])-float(p1[1])
+        print "calPointAngle,dx,dy:",dx,dy
+        if dx == 0 and dy >= 0:
+            return 90
+        elif dx == 0 and dy < 0:
+            return 180
 
-    '''
-    将‘[801,116][1280,180]’转成数组[[801,116][1280,180]]
-    '''
-    def strToBounds(self,bstr):
-        char = "[]"
-        print "strToBounds,bstr:",bstr
-        keyIndex = 1
-        # key.__len__()-1 为去掉"["的处理
-        i1 = bstr.find(char[0])
-        i2 = bstr.find(char[1])
-        if i1 == -1 or i2 == -1:
-            return []
-        str1 = bstr[i1: i2+1]
-        str2 = bstr[i2+1 : bstr.__len__()]
-        return [eval(str1),eval(str2)]
+        radian = math.atan(dy/dx) #计算出弧度值
+        angle = 180 * radian/math.pi
+        print "calPointAngle,angle,radian:",angle,radian
+        return angle
+
+    def calPointLine(self, p1,p2):
+        print "calPointLine,p1,p2:", p1,p2
+        dx = float(p2[0])-float(p1[0])
+        dy = float(p2[1])-float(p1[1])
+        line = round(math.sqrt(dx*dx + dy*dy),1)
+        print "calPointLine,line:", line
+        return line
 
-    '''
-    根据传入的uat界面obj参数,转换成UIObject参数
-    参数均用字典存储。
-    '''
-    def setObjParam(self, uatObjParam):
-        uiObjParam = {}
-        for uatKey in uatObjParam:
-            uatParam = uatObjParam[uatKey]
-            if self.UATParamMap.has_key(uatKey) and uatParam is not None and str(uatParam).__len__() > 0:
-                uiObjParam[self.UATParamMap[uatKey]] = uatParam
-        return uiObjParam
     '''
     检测option组件,在电视上是否被选中了
     :param option 数据字典
+    :param layoutUIParam, parent的layout组件定位字典
+    :param focusUIObj, 焦点组件
     :return -1:未进入option的页面,找不到option UIObject;0:进入了option的页面,未选中option;1 已经选中option
         -2:代表未找到焦点
     '''
-    def checkOptionChoose(self, option):
-        optionUIObj = self.pyU.getUiObject(text=option["optionView"][UATTree.View_Text],
-                                           resourceId=option["optionView"][UATTree.View_ID],
-                                           description=option["optionView"][UATTree.View_Desc])
-        if optionUIObj is None or optionUIObj.count == 0:
+    def checkOptionChoose(self, option, layoutUIParam, focusUIObj):
+        optionUIObj = self.getOptionUIObj(option, layoutUIParam)
+        if optionUIObj is None or optionUIObj.exists() is False:
             return -1
-        curUIObj = self.getChooseUIObj(option)
-        if curUIObj is None or curUIObj.count == 0:
+        if focusUIObj is None or focusUIObj.exists() is False:
             return -2
-        if self.dm.isHasAnotherBounds(curUIObj.info["bounds"], optionUIObj.info['bounds']):
+        focusParam = self.getFPFromOption(option)
+        if focusParam is None:
+            return 1
+        try:
+            focusedBounds = focusUIObj.info()["bounds"]
+            destBounds = optionUIObj.info()['bounds']
+        except Exception,e:
+            return 0
+        fsamBounds = focusParam[UATTree.View_sambounds]
+        osamBounds = option[UATTree.TAB_OPTION_VIEW][UATTree.View_sambounds]
+        if self.hasFocusDest(focusedBounds, destBounds,fsamBounds, osamBounds):
             return 1
         else:
             return 0
 
+    def checkOptionExist(self, option, parent):
+        # print "checkOptionExist,option:",option
+        moveKey = parent[UATTree.TAB_MOVE_KEY]
+        optionName = option['name']
+        print "checkOptionExist,moveKey:", moveKey
+        Max_Try = moveKey[UATTree.Max_Try]
+        if Max_Try == "":
+            Max_Try = 1
+        else:
+            Max_Try = int(Max_Try)
+        Reverse_Max_Try = Max_Try * 2
+        print "Max_Try:", Max_Try
+        count = 0
+        Reversecount = 0
+        lastObj = ""
+
+        while (True):
+            if count >= Max_Try and Reversecount >= Reverse_Max_Try:
+                break
+            # 如果找到目标直接返回
+            debug(self.cls,"checkOptionExist","optionView:%s"%option,DEBUG)
+            opUIParam = UIParamUtil.setObjParam(option["optionView"])
+            ###
+            try:
+                opOBJ = self.pyU.getUiObject2(opUIParam)
+                opOBJInfo = opOBJ.info
+                debug(self.cls,"checkOptionExist","opOBJInfo: %s"%opOBJInfo,DEBUG)
+                break
+            except Exception,e:
+                opOBJ = None
+            if count < Max_Try:
+                flag = 1
+                count += 1
+                print "now count:", count
+            else:
+                flag = 0
+                Reversecount += 1
+                print "now Reversecount:", Reversecount
+            self.runnerCommand.executeMoveKey(moveKey, flag)
+        if opOBJ is not None and opOBJ.exists():
+            info(self.cls, "checkOptionExist", "当前页面已找到option: %s" % optionName, INFO)
+            return True
+        info(self.cls, "checkOptionExist", "注意!!!当前页面未找到option: %s" % optionName, INFO)
+        return False
+
     '''
     根据option配置的焦点方式,返回当前页面焦点组件
+    :param option: option字典
+    :param layoutUIObj : option对应组件的根组件
+    :return retCode,UIObject对象:reCode=0表示默认为不需要选中状态,retCode=1表示option默认为需要选中状态
+    '''
+    Focus_Need = 1
+    Focus_Need_Not = 0
+    def getChooseUIObj(self, option, layoutUIObj = None):
+        debug(self.cls, "getChooseUIObj", "option name:"+option[UATTree.TAB_NAME],DEBUG)
+        focusObjParam = self.getFPObjParam(option)
+        debug(self.cls, "getChooseUIObj", "focusObjParam:"+str(focusObjParam)+"; layoutUIObj:"+str(layoutUIObj),DEBUG)
+        if focusObjParam.__len__() <= 0:
+            return self.Focus_Need_Not,None
+        if layoutUIObj is None:
+            return self.Focus_Need,self.pyU.getUiObject2(focusObjParam)
+        else:
+            return self.Focus_Need,layoutUIObj.child(**focusObjParam)
+
+    '''
+    根据parent字典,检索parent是否有一个option被选中,任何一个备选中,返回option的UIObject
+    :param parent: parent 字典
+    :param needFocus: 目标option需要聚焦,则优先找焦点,再处理select type为no情况
+    :return retCode,UIObject对象:reCode=0表示默认为不需要选中状态,retCode=1表示option默认为需要选中状态
     '''
-    def getChooseUIObj(self, option):
-        print "getChooseUIObj option:", option["focus-select"]
-        if option["focus-select"]["type"].__len__() > 1:
-            chooseType = option["focus-select"][UATTree.FS_Type]
-            if chooseType.lower() == "focus":
-                return self.pyU.getFocusedUIObject()
-            elif chooseType.lower() == "select":
-                return self.pyU.getSelectedUIObject()
-            elif chooseType.lower() == "no":
-                resId = option[UATTree.TAB_OPTION_VIEW][UATTree.View_ID]
-                text = option[UATTree.TAB_OPTION_VIEW][UATTree.View_Text]
-                className = option[UATTree.TAB_OPTION_VIEW][UATTree.View_Class]
-                desc = option[UATTree.TAB_OPTION_VIEW][UATTree.View_Desc]
-                return self.pyU.getUiObject(resourceId=resId, text=text, className=className, description=desc)
+    def getChooseUIObjP(self, parent, layoutUIParam, needFocus = True):
+        if layoutUIParam.__len__() > 0:
+            layoutUIObj = self.pyU.getUiObject2(layoutUIParam)
+        else:
+            layoutUIObj = None
+        # 筛选焦点定位类型,减少重复判断
+        focusOptionDict = self.reMuFocusOption(parent[UATTree.TAB_OPTION])
+        debug(self.cls,"getChooseUIObjP","focusOptionDict:"+str(focusOptionDict.keys()),DEBUG)
+        for optionName in focusOptionDict:
+            option = focusOptionDict[optionName]
+            retCode, uiObj = self.getChooseUIObj(option, layoutUIObj)
+            if retCode == self.Focus_Need_Not and needFocus is False:
+                return self.Focus_Need_Not, None
+            elif retCode == self.Focus_Need and uiObj is not None and uiObj.exists():
+                return retCode, uiObj
+        return self.Focus_Need,None
+
+    def reMuFocusOption(self, optionDict):
+        retDict = {}
+        for optionName in optionDict:
+            option = optionDict[optionName]
+            focusObjParam = self.getFPObjParam(option)
+            if retDict.__len__() == 0:
+                retDict[optionName] = option
+                continue
+            isSame = False
+            for retName in retDict:
+                retOption = optionDict[retName]
+                retFocusObjParam = self.getFPObjParam(option)
+                if UIParamUtil.cmpObjParam(focusObjParam, retFocusObjParam):
+                    isSame = True
+                    break
+            if isSame is False:
+                retDict[optionName] = option
+        return retDict
+
+
+    '''
+    根据option和layoutUIParam参数,获取option UIObject对象
+    layoutUIParam参数有效,则在layout里查找子组件option,否则全界面查找子组件。
+    '''
+    def getOptionUIObj(self, option, layoutUIParam):
+        debug(self.cls, "getOptionUIObj","OptionView:"+str(option[UATTree.TAB_OPTION_VIEW]),DEBUG)
+        destUIParam = UIParamUtil.setObjParam(option[UATTree.TAB_OPTION_VIEW])
+        debug(self.cls, "getOptionUIObj", "destUIParam:" + str(destUIParam), DEBUG)
+        destUITextExist = False
+        try:
+            destUIText = destUIParam['text']
+            destUITextExist = True
+        except Exception, e:
+            info(self.cls, "getOptionUIObj", "目标对象%s的optionView无text属性"%option[UATTree.TAB_NAME], INFO)
+        if destUITextExist is True:
+            #####################
+            if type(destUIParam['text']) is type([]):
+                destUIObjectExist = False
+                tempTextList = destUIParam['text']
+                txtCount = 0
+                for txt in tempTextList:
+                    destUIParam['text'] = txt
+                    uix = self.pyU.dump_hierarchy()
+                    if txt not in uix:
+                        continue
+                    if layoutUIParam.__len__() > 0:
+                        layoutUIObj = self.pyU.getUiObject2(layoutUIParam)
+                        try:
+                            destUIObject = layoutUIObj.child(**destUIParam)
+                            destUIObjectInfo = destUIObject.info
+                            destUIObjectExist = True
+                            info(self.cls, "getOptionUIObj", "文本%s对应的option对象已找到" % txt, INFO)
+                        except Exception, e:
+                            nextText = tempTextList[txtCount + 1]
+                            error(self.cls, "getOptionUIObj", "文本%s对应的option对象未找到,匹配下一个文本%s" % (txt, nextText), ERROR)
+                        if destUIObjectExist is True:
+                            break
+                    else:
+                        try:
+                            destUIObject = self.pyU.getUiObject2(destUIParam)
+                            destUIObjectInfo = destUIObject.info
+                            destUIObjectExist = True
+                            info(self.cls, "getOptionUIObj", "文本%s对应的option对象已找到" % txt, INFO)
+                        except Exception, e:
+                            nextText = tempTextList[txtCount + 1]
+                            error(self.cls, "getOptionUIObj", "文本%s对应的option对象未找到,匹配下一个文本%s" % (txt, nextText), ERROR)
+                        if destUIObjectExist is True:
+                            break
+                    txtCount += 1
             else:
-                error(self.cls, "getChooseUIObj", option["name"] + " option focus-select属性配置异常", ERROR)
-                return None
-        elif option["focuseView"][UATTree.Focus_Text].__len__() > 0 \
-                or option["focuseView"][UATTree.Focus_ID].__len__() > 1 \
-                or option["focuseView"][UATTree.Focus_Desc].__len__() > 0:
-            return self.pyU.getUiObject(text=option["focuseView"][UATTree.Focus_Text],
-                                        resourceId=option["focuseView"][UATTree.Focus_ID],
-                                        description=option["focuseView"][UATTree.Focus_Desc])
+                if layoutUIParam.__len__() > 0:
+                    layoutUIObj = self.pyU.getUiObject2(layoutUIParam)
+                    destUIObject = layoutUIObj.child(**destUIParam)
+                else:
+                    destUIObject = self.pyU.getUiObject2(destUIParam)
+            #########################
         else:
-            error(self.cls, "getChooseUIObj", option["name"] + " option 需要配置 focus-select或者FocusView", ERROR)
-            return None
+            if layoutUIParam.__len__() > 0:
+                layoutUIObj = self.pyU.getUiObject2(layoutUIParam)
+                destUIObject = layoutUIObj.child(**destUIParam)
+            else:
+                destUIObject = self.pyU.getUiObject2(destUIParam)
+        return destUIObject
 
     '''
     检测parent,在电视上是否被选中了。一个option被选中,表示选中
@@ -312,97 +500,64 @@ class FocusCommand():
          1:已经选中parent
     '''
     def checkParentChoose(self, parent):
-        debug(self.cls, "checkParentChoose", "parent:" + parent["name"], DEBUG)
+        debug(self.cls, "checkParentChoose", "parent:" + parent[UATTree.TAB_NAME], DEBUG)
         chooseTypeDict = {}
-        layoutResId = parent["layout"][UATTree.Layout_ID]
+        layoutParam = parent[UATTree.TAB_LAYOUT]
+        layoutUIParam = UIParamUtil.setObjParam(layoutParam)
+        layoutResId = layoutParam[UATTree.Layout_ID]
         uiView = parent[UATTree.TAB_UI_VIEW]
         uiViewResId = uiView[UATTree.View_ID]
         uiViewText = uiView[UATTree.View_Text]
         uiViewDesc = uiView[UATTree.View_Desc]
+        # print "checkParentChoose,layoutUIParam:",layoutUIParam
         if layoutResId == "" and uiViewResId == "" and uiViewText == "" and uiViewDesc == "":
             debug(self.cls, "checkParentChoose",
-                  "Warning:Parent %s的Layout resId和UIView信息获取失败!!请注意检查UATree文件!!!" % parent['name'], DEBUG)
+                  "Warning:Parent %s的Layout resId和UIView信息获取失败!!请注意检查UATree文件!!!" % parent[UATTree.TAB_NAME], DEBUG)
             return -1
-        elif layoutResId != "":
+        elif layoutUIParam.__len__() > 0:
             # 如果存在UIView信息,则先判断UIView
-            if uiViewResId != "" or uiViewText != "" or uiViewDesc != "":
-                isExist = self.checkUIViewExist(uiView)
-                if isExist is False:
-                    info(self.cls, "checkParentChoose", "当前页面不存在Parent:%s的UIView组件,判断该界面非此parent" % parent['name'],
-                         INFO)
-                    return -3
-                else:
-                    info(self.cls, "checkParentChoose", "已识别出Parent:%s的UIView组件" % parent['name'], INFO)
-            description = parent["layout"][UATTree.Layout_Desc]
-            if description != "":
-                layoutUIObj = self.pyU.getUiObject(resourceId=parent["layout"][UATTree.Layout_ID],
-                                                   description=parent["layout"][UATTree.Layout_Desc])
+            isExist = self.checkUIViewExist(uiView)
+            if isExist is False:
+                info(self.cls, "checkParentChoose", "当前页面不存在Parent:%s的UIView组件,判断该界面非此parent" % parent[UATTree.TAB_NAME],
+                     INFO)
+                return -3
             else:
-                layoutUIObj = self.pyU.getUiObject(resourceId=parent["layout"][UATTree.Layout_ID])
-            if layoutUIObj is None or layoutUIObj.count == 0:
-                # debug(self.cls, "checkParentChoose", "parent isn't " + parent["name"], DEBUG)
+                debug(self.cls, "checkParentChoose", "已识别出Parent:%s的UIView组件" % parent[UATTree.TAB_NAME], DEBUG)
+            # 判断Layout是否存在
+            layoutUIObj = self.pyU.getUiObject2(layoutUIParam)
+            if layoutUIObj is None or layoutUIObj.exists() is False:
+                info(self.cls, "checkParentChoose", "parent %s layout 不存在 "%parent[UATTree.TAB_NAME], INFO)
                 return -1
-            # print "checkParentChoose, layoutUIObj:",layoutUIObj.info
-            debug(self.cls, "checkParentChoose", "layoutUIObj:" + str(layoutUIObj.info), DEBUG)
+            debug(self.cls, "checkParentChoose", "parent %s layout 存在"%parent[UATTree.TAB_NAME], DEBUG)
+
+            # 获取焦点组件,判断是否在layout中
+            retCode, focusObj = self.getChooseUIObjP(parent, layoutUIParam, needFocus=False)
+            if retCode == self.Focus_Need_Not:
+                debug(self.cls, "checkParentChoose",
+                     "已找到目标parent %s,不需要选中。" % (parent['name']), DEBUG)
+            elif focusObj is not None and focusObj.exists():
+                debug(self.cls, "checkParentChoose",
+                     "已找到目标parent %s,焦点在Layout中。" % (parent['name']), DEBUG)
+            else:
+                info(self.cls, "checkParentChoose",
+                     "已找到目标parent %s,但是焦点不在Layout中。" % (parent['name']), INFO)
+                return -2
+            # 判断parent中存在一个option,则标识parent被选中。注意:为了快速消失界面相应
+            if layoutParam[UATTree.Layout_Limit] <> 1:
+                layoutUIParam = {}
+            optionUIObj = None
+            optionExist = False
             for optionName in parent["option"]:
                 option = parent["option"][optionName]
-                if option["focus-select"]["type"].__len__() > 1:
-                    chooseTypeDict[option["focus-select"][UATTree.FS_Type]] = option
-                elif option["focuseView"][UATTree.Focus_Text].__len__() > 0 \
-                        or option["focuseView"][UATTree.Focus_ID].__len__() > 1 \
-                        or option["focuseView"][UATTree.Focus_Desc].__len__() > 0:
-                    chooseTypeDict[option["focuseView"][UATTree.Focus_ID]
-                                   + option["focuseView"][UATTree.Focus_Text]
-                                   + option["focuseView"][UATTree.Focus_Desc]] = option
-            info(self.cls, "checkParentChoose", str(chooseTypeDict), INFO)
-            for key in chooseTypeDict.keys():
-                option = chooseTypeDict[key]
-                chooseUIObj = self.getChooseUIObj(option)
-                # TODO 如果选中效果,是没有type[no],例如快捷键,如何处理?
-                if chooseUIObj is None or chooseUIObj.count == 0:
-                    return -2
-                debug(self.cls, "checkParentChoose", "chooseUIObj:" + str(chooseUIObj.info), DEBUG)
-
-                # 获取到chooseUIObj后,与layout对比坐标,确认焦点是否存在于layout之中
-                layoutBounds = layoutUIObj.info['bounds']
-                choosingBounds = chooseUIObj.info['bounds']
-                if not self.dm.isHasAnotherBounds(layoutBounds, choosingBounds):
+                ret = self.checkOptionExist(option,parent)
+                if ret is True:
                     info(self.cls, "checkParentChoose",
-                         "识别出parent %s的Layout,但焦点不在该Layout之中,判断界面未处于该parent" % (parent['name']), INFO)
-                    return -2
-
-                # 如果该页面text属性不为空,则确认该parent下所有option的text,是否存在于当前页面中;如果text属性为空,则判断index属性
-                for optionName in parent["option"]:
-                    option = parent["option"][optionName]
-                    # 如果有text属性则确认Text,是否能对比成功
-                    optionText = option[UATTree.TAB_OPTION_VIEW][UATTree.View_Text]
-                    optionId = option[UATTree.TAB_OPTION_VIEW][UATTree.View_ID]
-                    optionIndex = option[UATTree.TAB_OPTION_VIEW][UATTree.View_Index]
-                    if optionText.__len__() > 0:
-                        info(self.cls, "checkParentChoose",
-                             "checking parent %s text %s" % (parent['name'], optionText), INFO)
-                        textObj = self.pyU.getUiObject(text=optionText)
-                    elif optionId.__len__() > 0:
-                        info(self.cls, "checkParentChoose",
-                             "checking parent %s optionId %s" % (parent['name'], optionId),
-                             INFO)
-                        textObj = self.pyU.getUiObject(resourceId=optionId)
-                    elif optionIndex != "":
-                        # index属性无法用于判断option,如果在以index属性作为判断依据的页面,则直接返回在这个页面
-                        info(self.cls, "checkParentChoose",
-                             "已找到目标parent %s,该页面为Index item页面,无法判断具体option" % parent['name'], INFO)
-                        return 1
-                    else:
-                        error(self.cls, "checkParentChoose", "当前参数不足以判断option %s是否存在" % (option['name']), ERROR)
-                        continue
-                    if textObj.exists:
-                        info(self.cls, "checkParentChoose",
-                             "已找到目标parent %s,并识别出该parent下的option %s" % (parent['name'], option['name']), INFO)
-                        return 1
-                else:
-                    info(self.cls, "checkParentChoose",
-                         "return 0", INFO)
-                    return 0
+                         "已找到目标parent %s,识别出该parent下的option %s。" % (parent['name'], option['name']),
+                         INFO)
+                    return 1
+            info(self.cls, "checkParentChoose",
+                 "已找到目标parent %s,但是parent下的option未被发现。" % (parent['name']), INFO)
+            return 0
         else:
             isExist = self.checkUIViewExist(uiView)
             if not isExist:
@@ -418,6 +573,7 @@ class FocusCommand():
     当存在resId参数时,使用resId获取对象并获取文本,再与text做比较;
     当不存在resId参数时,使用text直接获取对象,判断对象是否存在。
     与checkParentChoose判断不一致,做特殊处理。
+    :return 整型:0代表不存在,1代表存在
     '''
     def checkDialogExist(self, dialog):
         debug(self.cls, "checkDialogExist", "dialog:" + dialog["name"], DEBUG)
@@ -444,13 +600,12 @@ class FocusCommand():
                 return 0
 
     def checkUIViewExist(self, uiView):
-        resid = uiView[UATTree.View_ID]
-        text = uiView[UATTree.View_Text]
-        description = uiView[UATTree.View_Desc]
-        uiViewObj = self.pyU.getUiObject(resourceId=resid,
-                                         text=text,
-                                         description=description)
-        if not uiViewObj.exists:
+        #uiview未配置默认为检测通过
+        if uiView.__len__() == 0:
+            return True
+        uiviewObjParam = UIParamUtil.setObjParam(uiView)
+        uiViewObj = self.pyU.getUiObject2(uiviewObjParam)
+        if uiViewObj is None or uiViewObj.exists is False:
             return False
         else:
             return True
@@ -463,7 +618,10 @@ if __name__ == "__main__":
     runnerCmd = UATRunnerCommand(uatPathManage, pyU, dm, fm)
     focusCmd = FocusCommand(runnerCmd)
 
-    focusCmd.strToBounds('[801,116][1280,180]')
+    # ret = focusCmd.calPointAngle([0,0],[244,151])
+    ret = focusCmd.calPointAngle([244,151],[107,641])
+
+    # focusCmd.strToBounds('[801,116][1280,180]')
 
     # option = focusCmd.runnerCommand.uatPathManage.uatData.getOption("av_devices_settings")
     # parent = focusCmd.runnerCommand.uatPathManage.uatData.getParentByOption(option)

+ 1 - 1
ssat_sdk/UATree/UAT_log.py

@@ -3,7 +3,7 @@ import os, sys, time
 from ssat_sdk.utils.LoggingUtil import printLog
 
 TAG = "UATree:"
-DEBUG = True
+DEBUG = True # 上传SVN时注意将此参数设置为False
 INFO = True
 ERROR = True
 

+ 39 - 1
ssat_sdk/UATree/UAT_menu.py

@@ -19,6 +19,9 @@ class UATMenu():
     '''
     def moveToOption(self, optionName):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "moveToOption","Option %s not found"%optionName, ERROR)
+            return False
         return self.uatRunner.moveToOption(option)
 
 
@@ -28,6 +31,9 @@ class UATMenu():
     '''
     def openMoveOption(self, optionName):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "openMoveOption","Option %s not found"%optionName, ERROR)
+            return False
         result = self.uatRunner.moveToOption(option)
         if result == False:
             return result
@@ -40,6 +46,9 @@ class UATMenu():
     '''
     def focusOption(self, optionName, fromFirst = False):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "focusOption","Option %s not found"%optionName, ERROR)
+            return False
         return self.uatRunner.focusOption(option, fromFirst)
 
     '''
@@ -49,16 +58,25 @@ class UATMenu():
     '''
     def openOption(self, optionName, fromFirst = False, nextParentName=""):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "openOption","Option %s not found"%optionName, ERROR)
+            return False
         nextParent = self.uatRunner.uatPathManage.uatData.getParentDict(nextParentName)
+        if nextParent is None:
+            info(self.cls, "openOption","nextParentName %s not found"%nextParentName, INFO)
         return self.uatRunner.openOption(option, fromFirst, nextParent)
 
     '''
     进入目标parent,在openOption基础上,增加了后续路过弹窗的无option界面parent。例如:usb的视频播放界面,有弹出提示框,continue或者replay。
+    实际效果,和openOption一样。
     :param parentName 目标parent名字
     :param fromFirst 是否固定从firstparent到目标option
     '''
     def openParent(self, parentName, fromFirst=False):
         parent = self.uatRunner.uatPathManage.uatData.getParentDict(parentName)
+        if parent is None:
+            error(self.cls, "openParent","parent %s not found"%parentName, ERROR)
+            return False
         return self.uatRunner.openParent(parent, fromFirst)
 	
     '''
@@ -70,6 +88,9 @@ class UATMenu():
     '''
     def setOptionValue(self, optionName, value, exitMenu=True, fromFirst=True):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "setOptionValue","Option %s not found"%optionName, ERROR)
+            return False
         return self.uatRunner.setOptionValue(option, value, exitMenu, fromFirst)
 
     '''
@@ -87,6 +108,9 @@ class UATMenu():
     '''
     def checkOptionInfoView(self, optionName, cmpText = "", cmpPic = ""):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "checkOptionInfoView","Option %s not found"%optionName, ERROR)
+            return False
         return self.uatRunner.checkOptionInfoView(option, cmpText = cmpText, cmpPic = cmpPic)
 
     '''
@@ -94,6 +118,9 @@ class UATMenu():
     '''
     def getOptionInfoView(self, optionName):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "getOptionInfoView","Option %s not found"%optionName, ERROR)
+            return None
         return self.uatRunner.getOptionInfoView(option)
 
     '''
@@ -101,10 +128,16 @@ class UATMenu():
     '''
     def checkOptionExist(self, optionName):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "checkOptionExist","Option %s not found"%optionName, ERROR)
+            return False
         return self.uatRunner.checkOptionExist(option)
 
     def getOptionObject(self, optionName):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "getOptionObject","Option %s not found"%optionName, ERROR)
+            return None
         return self.uatRunner.getOptionObject(option)
 
     '''
@@ -112,6 +145,9 @@ class UATMenu():
     '''
     def getOptionValueRange(self, optionName):
         option = self.uatRunner.uatPathManage.uatData.getOption(optionName)
+        if option is None:
+            error(self.cls, "getOptionObject","Option %s not found"%optionName, ERROR)
+            return None
         try:
             vRange = (option[UATTree.TAB_TEXT_VALUE][UATTree.ValueView_Min],
                       option[UATTree.TAB_TEXT_VALUE][UATTree.ValueView_Max])
@@ -125,8 +161,10 @@ if __name__ == "__main__":
     uatMenu = UATMenu()
     # uatMenu.openOption("av_backlight_settings",nextParentName="av_backlight_settings")
     # uatMenu.openParent("av_backlight_settings")
+    uatMenu.focusOption("av_backlight_settings")
 
-    uatMenu.setOptionValue("av_backlight",50, fromFirst=True)
+    # uatMenu.setOptionValue("av_backlight",50, fromFirst=True)
+    # uatMenu.setOptionValue("av_backlight",100, fromFirst=True)
     # uatMenu.setOptionValue("av_picture_mode","av_movie")
     # print uatMenu.getOptionValueRange("usb_Brightness")
     # obj1 = uatMenu.uatRunner.pyU.getChoosedUIObject("select")

+ 4 - 3
ssat_sdk/UATree/UAT_runner.py

@@ -82,7 +82,7 @@ class UATRunner():
             info(self.cls, "locateParentUI", "没有选中Parent,电视界面不知道在哪里", INFO)
         else:
             info(self.cls, "locateParentUI", "选中Parent:" + cparent[UATTree.TAB_NAME], INFO)
-        print "ParentParam:", cparent
+        # print "ParentParam:", cparent
         return cparent
 
     '''
@@ -246,6 +246,7 @@ class UATRunner():
         return True
 
     '''
+    for exitMenuByPath
     回退路径,仅仅只是使用forwardPath的话,最后一个元素是option,但是实际上已经进入了这个界面,应该判断的是parent。
     所以要把forwardPath里面的option替换为parent;如果没有配这个option对应的parent,则去掉该option,以便exitMenuByPath函数使用。
     '''
@@ -283,8 +284,8 @@ class UATRunner():
         ret = self.focusOption(option, fromFirst)
         if ret is False:
             return False
-        ret = self.runnerCmd.executeEnterKey(option[UATTree.TAB_ENTER_KEY],nextParent)
-        info(self.cls, "openOption", "executeKey,ret:%s"%ret, INFO)
+        ret = self.runnerCmd.executeEnterKey(option[UATTree.TAB_ENTER_KEY], nextParent)
+        info(self.cls, "openOption", "executeKey,ret:%s" % ret, INFO)
         parent = self.uatPathManage.uatData.getParentDict(option[UATTree.TAB_NAME])
         if parent is None:
             return ret

+ 42 - 71
ssat_sdk/UATree/UAT_runnerCommand.py

@@ -11,7 +11,7 @@ from ssat_sdk.pic_tool import ImageCMP
 from ssat_sdk.ocr_convert import OCRConvert
 from ssat_sdk.utils.string_util import strcmp, getDigitFromString
 from UAT_valueCommand import ValueCommand
-# from UAT_focusCommand import FocusCommand
+from UAT_focusCommand import FocusCommand
 
 import cv2 as cv
 
@@ -35,52 +35,11 @@ class UATRunnerCommand():
         self.imgCMP = ImageCMP()
         self.ocrConvert = OCRConvert()
         self.valueCmd = ValueCommand( self)
-        # self.focusCmd = FocusCommand(self)
+        self.focusCmd = FocusCommand(self)
 
     #检测option组件是不是在电视上显示了
     def checkOptionExist(self, option, parent):
-        #print "checkOptionExist,option:",option
-        moveKey = parent[UATTree.TAB_MOVE_KEY]
-        print "moveKey:", moveKey
-        Max_Try = moveKey[UATTree.Max_Try]
-        if Max_Try == "":
-            Max_Try = 1
-        else:
-            Max_Try = int(Max_Try)
-        Reverse_Max_Try = Max_Try * 2
-        print "Max_Try:",Max_Try
-        count = 0
-        Reversecount = 0
-        lastObj = ""
-
-        while(True):
-            if count >= Max_Try and Reversecount >= Reverse_Max_Try:
-                break
-            # 如果找到目标直接返回
-            if self.pyU.isElementExist(resourceId=option["optionView"][UATTree.UIView_ID],
-                                       text=option["optionView"][UATTree.UIView_Text],
-                                       description=option["optionView"][UATTree.UIView_Desc]):
-                return True
-            # choosingObj = self.focusCmd.getChooseUIObj(option)
-            # if lastObj != "":
-            #     if choosingObj.info == lastObj.info:
-            #         print "choosingObj.info == lastObj.info!!!!"
-            #         if count < Max_Try:
-            #             Max_Try = count
-            #         else:
-            #             break
-            # lastObj = choosingObj
-            if count < Max_Try:
-                flag = 1
-                count += 1
-                print "now count:", count
-            else:
-                flag = 0
-                Reversecount += 1
-                print "now Reversecount:", Reversecount
-            self.executeMoveKey(moveKey, flag)
-
-        return False
+        return self.focusCmd.checkOptionExist(option,parent)
 
     '''
     检测parent,在电视上是否被选中了。一个option被选中,表示选中
@@ -134,8 +93,8 @@ class UATRunnerCommand():
         print "%s chooseType: %s"%(option["name"], chooseType)
 
         if chooseType.__len__() > 1:
-            if chooseType.lower() == "focus" or chooseType.lower() == "select":
-                return self.focusCmd.focusOptionView(parent,option)
+            if chooseType.lower() == "focus" or chooseType.lower() == "select" or chooseType.lower() == "no":
+                return self.focusCmd.focusOptionView(parent,option,chooseType)
             elif chooseType.lower() == "long_click":
                 info(self.cls, "focusTargetOption", option[UATTree.TAB_NAME] + " going to long click type!!!", info)
                 targetObj = self.pyU.getUiObject(resourceId=viewId)
@@ -146,14 +105,14 @@ class UATRunnerCommand():
                 else:
                     info(self.cls, "focusTargetOption", option[UATTree.TAB_NAME] + " 目标Object当前界面未找到!!!", info)
                     return False
-            elif chooseType.lower() == "no":
-                info(self.cls, "focusTargetOption", option[UATTree.TAB_NAME] + " 目标Object的chooseType为no,默认为不需要选中。", info)
-                retValue = self.checkOptionExist(option,parent)
-                if retValue is True:
-                    return True
-                else:
-                    error(self.cls, "focusTargetOption", option[UATTree.TAB_NAME] + " option在当前界面不存在", ERROR)
-                    return False
+            # elif chooseType.lower() == "no":
+            #     info(self.cls, "focusTargetOption", option[UATTree.TAB_NAME] + " 目标Object的chooseType为no,默认为不需要选中。", info)
+            #     retValue = self.checkOptionExist(option,parent)
+            #     if retValue is True:
+            #         return True
+            #     else:
+            #         error(self.cls, "focusTargetOption", option[UATTree.TAB_NAME] + " option在当前界面不存在", ERROR)
+            #         return False
             else:
                 error(self.cls, "focusTargetOption", option[UATTree.TAB_NAME] + " option focus-select属性配置异常", ERROR)
                 return False
@@ -239,7 +198,12 @@ class UATRunnerCommand():
         enterKey = None
         if nextParent is None:
             # 不指定nextParent,默认用第一个
-            enterKey = mkeys[0][UATTree.Key_Param]
+            try:
+                enterKey = mkeys[0][UATTree.Key_Param]
+            except Exception,e:
+                enterKey = None
+                error(self.cls, "executeEnterKey", "This option has no enterKey",ERROR)
+                return True
         else:
             # 指定nextParent,根据parentName查找匹配的enterKey
             enterKey = self.uatPathManage.uatData.UATree.getEKByParent(mkeys, nextParent[UATTree.TAB_NAME])
@@ -363,7 +327,7 @@ class UATRunnerCommand():
                 dialog = dialog_A[dialogName]
                 ret = self.focusCmd.checkDialogExist(dialog)
                 if ret < 1:
-                    info(self.cls, "executeDialog", "parent %s dialog %s doesnt popup."%(parent[UATTree.TAB_NAME], dialogName), INFO)
+                    info(self.cls, "executeDialog", "parent %s dialog %s doesn't popup."%(parent[UATTree.TAB_NAME], dialogName), INFO)
                     continue
                 tarOption = dialog["choose"]
                 if not self.focusTargetOption(dialog, tarOption):
@@ -408,8 +372,8 @@ class UATRunnerCommand():
     执行设值动作,区分数字设值以及非数字设值
     '''
     def setValue(self, option, value):
-        textValue = option[UATTree.TAB_TEXT_VALUE][0]
-        sign = option[UATTree.TAB_TEXT_VALUE][1]
+        textValue = option[UATTree.TAB_TEXT_VALUE]
+        sign = textValue['sign']
         info(self.cls, "setValue", "textValue:%s"%textValue, INFO)
         if textValue[UATTree.ValueView_Value] != "":
             #EnterKey作为textValue配置中的值设置时的按键,enterKey只能配置一个
@@ -435,26 +399,32 @@ class UATRunnerCommand():
                 while(count < 3):
                     # 获取数值时存在两种情况,一种是数值文本在聚焦组件里,一种在聚焦组件之外,分别处理。
                     # 先在聚焦组件内找数值
-                    choosedObj = self.focusCmd.getChooseUIObj(option)
-                    print "choosedObj.info:", choosedObj.info
+                    retCode, choosedObj = self.focusCmd.getChooseUIObj(option)
+                    # print "choosedObj.info:", choosedObj.info
                     text = self.getValueInObjectByTextResourceId(choosedObj, valueViewResId)
                     if text is None:
                         # 组件之中找不到时,则直接用resourceId在整个页面找
                         textObj = self.pyU.getUiObject(resourceId=valueViewResId)
+                        if textObj is None or textObj.info is None:
+                            error(self.cls, "setValue", "查不到组件,请检查%s 的textValue resid是否正确" % option[UATTree.TAB_NAME], ERROR)
+                            return False
                         text = textObj.info['text']
-                        if text[0] not in sign:
-                            text = self.getValueByTextResourceId(valueViewResId)
-                            text_num = float(text)
-                        else:
-                            if text[0]==sign[0]:
-                                text_num = -float(text.strip(text[0]))
-                            elif text[0]==sign[1]:
-                                text_num = float(text.strip(text[0]))
-                            else:
-                                text_num = float(text)
                         if text is None:
                             error(self.cls, "setValue", "获取数值失败,请检查%s 的textValue resid是否正确"%option[UATTree.TAB_NAME], ERROR)
                             return False
+                    if text[0] not in sign:
+                        dList = getDigitFromString(text)
+                        if dList.__len__() > 0:
+                            text_num = float(dList[-1])
+                        else:
+                            text_num = None
+                    else:
+                        if text[0] == sign[0]:
+                            text_num = -float(text.strip(text[0]))
+                        elif text[0] == sign[1]:
+                            text_num = float(text.strip(text[0]))
+                        else:
+                            text_num = float(text)
                     info(self.cls, "setValue", "当前设值为%s"%text, INFO)
                     value = str(value)
                     if len(sign)!=0:
@@ -513,7 +483,7 @@ class UATRunnerCommand():
                 while (True):
                     # 获取当前值时存在两种情况,一种是数值文本在聚焦组件里,一种在聚焦组件之外,分别处理。
                     # 先在聚焦组件内找当前值
-                    choosedObj = self.focusCmd.getChooseUIObj(option)
+                    retCode, choosedObj = self.focusCmd.getChooseUIObj(option)
                     print "choosedObj.info:", choosedObj.info
                     for i in range(choosedObj.info["childCount"]):
                         childUIObject = choosedObj.child_by_instance(i, resourceId=valueViewResId)
@@ -565,6 +535,7 @@ class UATRunnerCommand():
             if toParentKey[UATTree.Key_Event] == [] and toParentKey[UATTree.Key_IR] == []:
                 info(self.cls, "exitMenuByPath", "parent:%s has no toparent_key, using default back" % parent[UATTree.TAB_NAME], INFO)
                 self.executeKeyByType("back", UATTree.Key_Event)
+                time.sleep(1)
             else:
                 self.executeKey(toParentKey)
             time.sleep(1)

+ 70 - 30
ssat_sdk/UATree/UAT_tree.py

@@ -84,14 +84,17 @@ class UATTree():
     #将数据从excel表格里取出来,只返回括号里面的内容
     def parseParam(self, key, params, char = "[]"):
         # 为防止key在其他地方存在相同关键字,添加一个"["做区别
-        key = key + char[0]
-        keyIndex = params.find(key)
+        key1 = key + char[0]
+        keyIndex = params.find(key1)
         if keyIndex == -1:
             return ""
         # key.__len__()-1 为去掉"["的处理
-        str1 = params[keyIndex + key.__len__()-1:params.__len__()]
+        str1 = params[keyIndex + key1.__len__()-1:params.__len__()]
         i1 = str1.find(char[0])
-        i2 = str1.find(char[1])
+        if key == self.View_Bounds or key == self.View_sambounds:
+            i2 = str1.find("]]")+1
+        else:
+            i2 = str1.find(char[1])
         if i1 == -1 or i2 == -1:
             return ""
         str2 = str1[i1 + 1: i2]
@@ -116,7 +119,10 @@ class UATTree():
     def parseMulParam(self, keyList, params):
         paramDict = {}
         for key in keyList:
-            value = self.parseParam(key, params)
+            if key == 'text':
+                value = self.parseMulText(params)
+            else:
+                value = self.parseParam(key, params)
             paramDict[key] = value
         return paramDict
 
@@ -211,25 +217,48 @@ class UATTree():
     '''
     def parseView(self, viewStr):
         keyList = [self.View_ID, self.View_Text, self.View_Desc, self.View_Bounds, self.View_Index, self.View_Class]
-        positionList = [u"left",u"top",u"right",u"bottom"]
         paramDict = self.parseMulParam(keyList, viewStr)
-        boundsStr = paramDict[self.View_Bounds]
-        if boundsStr != "":
-            i = boundsStr.find(",")
-            if i != -1:
-                boundsList = strToList(boundsStr,",")
-                boundsListToInt = []
-                for bl in boundsList:
-                    bl = int(bl)
-                    boundsListToInt.append(bl)
-        # 将读取出来的列表与positionList合并成为字典,为了与uiautomator viewer相匹配
-            boundsDic = dict(map(lambda x, y: [x, y], positionList, boundsListToInt))
-            paramDict['bounds'] = boundsDic
-
-            ret = paramDict
-            return ret
-        ret = self.parseMulParam(keyList, viewStr)
-        return ret
+        paramDict[self.View_Bounds] = self.strToBounds(paramDict[self.View_Bounds])
+        return paramDict
+
+    '''
+    将‘[801,116][1280,180]’转成数组[[801,116][1280,180]]
+    '''
+    def strToBounds(self, bstr):
+        char = "[]"
+        # print "strToBounds,bstr:", bstr,bstr.__len__()
+        if bstr.__len__() < 10:
+            return []
+        keyIndex = 1
+        # key.__len__()-1 为去掉"["的处理
+        i1 = bstr.find(char[0])
+        i2 = bstr.find(char[1])
+        if i1 == -1 or i2 == -1:
+            return []
+        str1 = bstr[i1: i2 + 1]
+        str2 = bstr[i2 + 1: bstr.__len__()]
+        return [eval(str1), eval(str2)]
+
+    '''
+    解析多文本option,更改key亦可作为其他参数
+    '''
+    def parseMulText(self,params, char="[]",key = 'text'):
+        key1 = key + char[0] + char[0]
+        keyIndex = params.find(key1)
+        if keyIndex == -1:
+            text = self.parseParam(key, params)  # 只有一层中括号
+        else:
+            text = []
+            str1 = params[keyIndex + key1.__len__() - 1:len(params)]
+            i1 = str1.find(char[0])
+            i2 = str1.find((char[1] + char[1]))
+            if i1 == -1 or i2 == -1:
+                return ""
+            str2 = str1[i1:i2 + 1]  # 只去掉外括号
+            strList = strToList(str2, char[1] + "," + char[0])
+            for strs in strList:
+                text.append(strs.strip(char[0]).strip(char[1]))
+        return text
 		
     View_sambounds = "sambounds" #采样时的样本坐标,用于和select-focus、focusView的坐标做相对比较,计算相对位置,用于确定是否被选中。
     '''
@@ -238,7 +267,10 @@ class UATTree():
     def parseOptionView(self, viewStr):
         keyList = [self.View_ID, self.View_Text, self.View_Desc, self.View_Bounds, self.View_Index, self.View_Class
                    ,self.View_sambounds]
-        return self.parseMulParam(keyList, viewStr)
+        paramDict = self.parseMulParam(keyList, viewStr)
+        paramDict[self.View_Bounds] = self.strToBounds(paramDict[self.View_Bounds])
+        paramDict[self.View_sambounds] = self.strToBounds(paramDict[self.View_sambounds])
+        return paramDict
 
     # UI 界面数据
     UIView_Activity = "activity"  # android activity
@@ -298,6 +330,7 @@ class UATTree():
         paramDict = self.parseMulParam(keyList, viewStr)
         if paramDict.has_key(self.Layout_Limit) and paramDict[self.Layout_Limit].__len__()>0:
             paramDict[self.Layout_Limit] = int(paramDict[self.Layout_Limit]) #字符'0'转数字0
+        paramDict[self.View_Bounds] = self.strToBounds(paramDict[self.View_Bounds])
         return paramDict
 
     #Android FocusView数据
@@ -309,7 +342,10 @@ class UATTree():
     def parseFocus(self, viewStr):
         keyList = [self.Focus_ID, self.Focus_Text, self.Focus_Desc, self.View_Bounds, self.View_Class,
                    self.View_sambounds]
-        return self.parseMulParam(keyList, viewStr)
+        paramDict = self.parseMulParam(keyList, viewStr)
+        paramDict[self.View_Bounds] = self.strToBounds(paramDict[self.View_Bounds])
+        paramDict[self.View_sambounds] = self.strToBounds(paramDict[self.View_sambounds])
+        return paramDict
 
     #Value数据
     ValueView_ID = "resid" #android view resource-id
@@ -322,6 +358,7 @@ class UATTree():
     ValueView_Max = "maxvalue" # 如果是数值范围形式的值,返回最小值
     ValueView_StepSize = "stepsize" # 如果是数值范围形式的值,每次调整数值的步长。读不到此参数时,默认为1。
     ValueView_Duration = "duration" # 如果是数值范围形式的值,每次调整数值的间隔时间。读不到此参数时,默认为0.2。
+    ValueView_Sign = "sign" # 针对前缀为字母,后缀为数字形式的值,用来保存前缀
     def parseValueView(self, viewStr):
         keyList = [self.ValueView_ID, self.ValueView_Text, self.ValueView_Desc, self.ValueView_Value,
                    self.ValueView_StepSize, self.ValueView_Duration, self.ValueView_Bounds, self.ValueView_Class,
@@ -373,7 +410,8 @@ class UATTree():
                         # 读出来的数值未带大括号,需加上大括号,并且json格式化
                         valueView = eval("{%s}" % valueViewStr)
                         paramDict[value] = valueView
-        return paramDict,sign
+        paramDict[self.ValueView_Sign] = sign
+        return paramDict
 
     #focus-select数据,不仅有select或者focus类型,还有指定组件属性情况,用于处理界面多个聚焦方式出现的场景
     FS_Type = "type"
@@ -384,8 +422,10 @@ class UATTree():
     FS_Class = "class"
     def parseFocusSelectType(self, fsType):
         keyList = [self.FS_Type,self.FS_ID,self.FS_Text,self.FS_Desc,self.FS_Bounds,
-                   self.View_sambounds]
-        return self.parseMulParam(keyList, fsType)
+                   self.View_sambounds,self.FS_Class]
+        paramDict = self.parseMulParam(keyList, fsType)
+        paramDict[self.View_sambounds] = self.strToBounds(paramDict[self.View_sambounds])
+        return paramDict
 
     '''
     扫描parent数据行时,要把建立当前parent的prevParent和nextParent关系。
@@ -450,10 +490,10 @@ class UATTree():
         if self.dialogDict.has_key(parentName):
             optionDict = {}
             optionDict[UATTree.TAB_NAME] = optionName
-            optionDict[UATTree.TAB_OPTION_VIEW] = self.parseView(optionView)
+            optionDict[UATTree.TAB_OPTION_VIEW] = self.parseOptionView(optionView)
             optionDict[UATTree.TAB_FOCUS_SELECT] = self.parseFocusSelectType(focusSelect)
             optionDict[UATTree.TAB_FOCUSE_VIEW] = self.parseFocus(focusView)
-            optionDict[UATTree.TAB_ENTER_KEY] = self.parseKey(enterKey)
+            optionDict[UATTree.TAB_ENTER_KEY] = self.parseEnterKey(enterKey,optionName)
             optionDict[UATTree.TAB_TEXT_VALUE] = self.parseValueView(textValue)
             optionDict[UATTree.TAB_INFO_VIEW] = self.parseView(infoView)
             optionDict[UATTree.TAB_PARENT_NAME] = parentName

+ 180 - 0
ssat_sdk/UATree/UIParamUtil.py

@@ -0,0 +1,180 @@
+# -*- coding:utf-8 -*-
+from UAT_log import error,info,debug
+
+ERROR = True
+INFO = True
+DEBUG = True
+
+'''
+UIObject Info:
+{
+	u 'contentDescription': None,
+	u 'checked': False,
+	u 'clickable': True,
+	u 'scrollable': False,
+	u 'text': None,
+	u 'packageName': u 'com.android.tv',
+	u 'selected': False,
+	u 'enabled': True,
+	u 'bounds': {
+		u 'top': 491,
+		u 'left': 75,
+		u 'right': 264,
+		u 'bottom': 619
+	},
+	u 'className': u 'android.widget.RelativeLayout',
+	u 'focusable': True,
+	u 'focused': True,
+	u 'checkable': False,
+	u 'resourceName': None,
+	u 'longClickable': False,
+	u 'visibleBounds': {
+		u 'top': 491,
+		u 'left': 75,
+		u 'right': 264,
+		u 'bottom': 619
+	},
+	u 'childCount': 3
+}
+UATree中的UI param:
+
+
+'''
+'''
+UI界面组件参数的工具类
+'''
+class UIParamUtil:
+    UIObjParam = {
+        "text": None,  # MASK_TEXT,
+        "textContains": None,  # MASK_TEXTCONTAINS,
+        "textMatches": None,  # MASK_TEXTMATCHES,
+        "textStartsWith": None,  # MASK_TEXTSTARTSWITH,
+        "className": None,  # MASK_CLASSNAME
+        "classNameMatches": None,  # MASK_CLASSNAMEMATCHES
+        "description": None,  # MASK_DESCRIPTION
+        "descriptionContains": None,  # MASK_DESCRIPTIONCONTAINS
+        "descriptionMatches": None,  # MASK_DESCRIPTIONMATCHES
+        "descriptionStartsWith": None,  # MASK_DESCRIPTIONSTARTSWITH
+        "checkable": None,  # MASK_CHECKABLE
+        "checked": None,  # MASK_CHECKED
+        "clickable": None,  # MASK_CLICKABLE
+        "longClickable": None,  # MASK_LONGCLICKABLE,
+        "scrollable": None,  # MASK_SCROLLABLE,
+        "enabled": None,  # MASK_ENABLED,
+        "focusable": None,  # MASK_FOCUSABLE,
+        "focused": None,  # MASK_FOCUSED,
+        "selected": None,  # MASK_SELECTED,
+        "packageName": None,  # MASK_PACKAGENAME,
+        "packageNameMatches": None,  # MASK_PACKAGENAMEMATCHES,
+        "resourceId": None,  # MASK_RESOURCEID,
+        "resourceIdMatches": None,  # MASK_RESOURCEIDMATCHES,
+        "index": None,  # MASK_INDEX,
+        "instance": None  # MASK_INSTANCE,
+    }
+    UATParamMap = {
+        "text": "text",  # MASK_TEXT,
+        "textContains": "textContains",  # MASK_TEXTCONTAINS,
+        "textMatch": "textMatches",  # MASK_TEXTMATCHES,
+        "textSWith": "textStartsWith",  # MASK_TEXTSTARTSWITH,
+        "class": "className",  # MASK_CLASSNAME
+        "classMatches": "classNameMatches",  # MASK_CLASSNAMEMATCHES
+        "desc": "description",  # MASK_DESCRIPTION
+        "descContain": "descriptionContains",  # MASK_DESCRIPTIONCONTAINS
+        "descMatch": "descriptionMatches",  # MASK_DESCRIPTIONMATCHES
+        "descSWith": "descriptionStartsWith",  # MASK_DESCRIPTIONSTARTSWITH
+        "checkable": "checkable",  # MASK_CHECKABLE
+        "checked": "checked",  # MASK_CHECKED
+        "clickable": "clickable",  # MASK_CLICKABLE
+        "lClickable": "longClickable",  # MASK_LONGCLICKABLE,
+        "scrollable": "scrollable",  # MASK_SCROLLABLE,
+        "enable": "enabled",  # MASK_ENABLED,
+        "focusable": "focusable",  # MASK_FOCUSABLE,
+        "focus": "focused",  # MASK_FOCUSED,
+        "select": "selected",  # MASK_SELECTED,
+        "pkg": "packageName",  # MASK_PACKAGENAME,
+        "pkgMatch": "packageNameMatches",  # MASK_PACKAGENAMEMATCHES,
+        "resid": "resourceId",  # MASK_RESOURCEID,
+        "residMatch": "resourceIdMatches",  # MASK_RESOURCEIDMATCHES,
+        "index": "index",  # MASK_INDEX,
+        "instance": "instance"  # MASK_INSTANCE,
+    }
+    cls = "UIObjParam"
+    def __init__(self):
+        pass
+
+    '''
+    将atx抓取到的bounds字典:'visibleBounds': {
+		u 'top': 491,
+		u 'left': 75,
+		u 'right': 264,
+		u 'bottom': 619
+	}
+	转换成UATree格式的:
+	[[75,491][264,619]]
+    '''
+    @staticmethod
+    def atxBounds2UATBounds(atxBounds):
+        try:
+            bounds = [[atxBounds['left'], atxBounds['top']], [atxBounds['right'], atxBounds['bottom']]]
+        except Exception,e:
+            error(self.cls, "atxBounds2UATBounds", "atxBounds has error format:"+str(atxBounds),ERROR)
+            bounds=[]
+        return bounds
+
+    ''''
+    UATree格式的:[[75,491][264,619]]    
+    转换成:
+    atx抓取到的bounds字典:'visibleBounds': {
+        u 'top': 491,
+        u 'left': 75,
+        u 'right': 264,
+        u 'bottom': 619
+    }
+    '''
+    @staticmethod
+    def uatBounds2ATXBounds(uatBounds):
+        bounds = {}
+        try:
+            bounds['left'] = uatBounds[0][0]
+            bounds["top"] = uatBounds[0][1]
+            bounds['right'] = uatBounds[1][0]
+            bounds['bottom'] = uatBounds[1][1]
+        except Exception,e:
+            error(self.cls, "uatBounds2ATXBounds", "uatBounds has error format:"+str(uatBounds),ERROR)
+            bounds={}
+        return bounds
+
+    '''
+    根据传入的uat界面界面参数(例如:optionView、UIView),转换成UIObject参数
+    参数均用字典存储。
+    '''
+    @staticmethod
+    def setObjParam(uatObjParam):
+        uiObjParam = {}
+        if uatObjParam is None:
+            return uiObjParam
+        for uatKey in uatObjParam:
+            uatParam = uatObjParam[uatKey]
+            if UIParamUtil.UATParamMap.has_key(uatKey) and uatParam is not None and str(uatParam).__len__() > 0:
+                uiObjParam[UIParamUtil.UATParamMap[uatKey]] = uatParam
+        return uiObjParam
+
+    '''
+    比对两个UIObject参数,是否一样,采用字典逐个比对方式。
+    objParam1, objParam2必须是同一中字典格式
+    return :True/False. True表示相同,False标识不同
+    '''
+    @staticmethod
+    def cmpObjParam(objParam1, objParam2):
+        for key in objParam1:
+            if "sambounds" == key: #sambounds只用于相对位置计算,不做重复考虑
+                break
+            try:
+                value1 = objParam1[key]
+                value2 = objParam2[key]
+                if value1 <> value2:
+                    return False
+            except Exception,e:
+                error(self.cls, "cmpObjParam", "objParam1 and objParam2 are different", ERROR)
+                return False
+        return True

+ 70 - 10
ssat_sdk/client/ota_client.py

@@ -5,8 +5,14 @@ from ssat_sdk.device_manage import ScbcCopyKey
 from ssat_sdk.sat_environment import getOtaParamUrl_dict
 import urllib
 import urllib2
+import urllib3
 import json
 import requests
+import os
+import httplib
+httplib.HTTPConnection._http_vsn = 10
+httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0'
+
 
 
 
@@ -21,15 +27,15 @@ class OtaClient():
         # 各服务器getParam接口
         self.getParamDict = getOtaParamUrl_dict()
 
-        # self.getParamDict = {
-        #     "CN": "https://cn.ota.qhmoka.com/ota-services/strategy/getParam",
-        #     "NA": "https://na.ota.qhmoka.com/ota-services/strategy/getParam",
-        #     "LA": "https://la.ota.qhmoka.com/ota-services/strategy/getParam",
-        #     "ME": "https://me.ota.qhmoka.com/ota-services/strategy/getParam",
-        #     "EU": "https://eu.ota.qhmoka.com/ota-services/strategy/getParam",
-        #     "AP": "https://ap.ota.qhmoka.com/ota-services/strategy/getParam",
-        #     "TEST": "https://test.uc.qhmoka.com/ota-services/strategy/getParam"
-        #     }
+        self.getParamDict = {
+            "CN": "https://cn.ota.qhmoka.com/ota-services/strategy/getParam",
+            "NA": "https://na.ota.qhmoka.com/ota-services/strategy/getParam",
+            "LA": "https://la.ota.qhmoka.com/ota-services/strategy/getParam",
+            "ME": "https://me.ota.qhmoka.com/ota-services/strategy/getParam",
+            "EU": "https://eu.ota.qhmoka.com/ota-services/strategy/getParam",
+            "AP": "https://ap.ota.qhmoka.com/ota-services/strategy/getParam",
+            "TEST": "https://test.uc.qhmoka.com/ota-services/strategy/getParam"
+            }
 
         # http request中统一使用的headers
         self.headers = {
@@ -148,6 +154,60 @@ class OtaClient():
     def getMAC(self):
         return ScbcCopyKey.GetMAC()
 
+    '''
+    从云测试系统随机取一个deviceId,返回
+    :param forFormal:是否为正式服务器,若为正式,则为True
+    :return deviceId
+    '''
+    def httpGetDeviceId(self,forFormal=True):
+        # urlTest = "http://10.201.40.65:8088/otaad/deviceId/getDeviceId.do"  # 云测试系统地址
+        # urlFormal = "http://10.201.40.65:8088/otaad/deviceId/getDeviceId.do"
+        urlTest = "http://10.126.16.60:8680/otaad/deviceId/getDeviceId.do"
+        urlFormal = "http://10.126.16.60:8380/otaad/deviceId/getDeviceId.do"
+        post_data = None
+        if forFormal is True:
+            result, response = self.postRequest(urlFormal,post_data,self.headers)
+        else:
+            result, response = self.postRequest(urlTest, post_data,self.headers)
+        # print "result",result
+        # print "response",response
+        resDict = json.loads(response.text)
+        if str(resDict['code']) == "00":
+            return resDict['deviceId']  # 获取成功则返回deviceId
+        else:
+            return None # 获取失败则返回None
+
+    '''
+    返回clientType,deviceId,userId 给云测试平台,将这三个数据绑定在一起
+    :param clientType,deviceId,userId
+    :return 若删除成功,返回True,否则返回False
+    '''
+    def httpGetDIdRespond(self,clientType,deviceId,userId=None,forFormal=True):
+        urlTest = "http://10.126.16.60:8680/otaad/deviceId/deleteDeviceId.do"  # 云测试系统地址
+        urlFormal = "http://10.126.16.60:8380/otaad/deviceId/deleteDeviceId.do"
+        supportData = {
+            "deviceId": deviceId,
+            "clientType": clientType,
+            "userId": userId
+        }
+        post_data = json.dumps(supportData)
+        if forFormal is True:
+            result, response = self.postRequest(urlFormal, post_data, self.headers)
+        else:
+            result, response = self.postRequest(urlTest, post_data, self.headers)
+        resDict = json.loads(response.text)
+        if str(resDict['code']) == "00":
+            return True  # 删除成功则返回True
+        else:
+            return False  # 删除失败则返回False
+
+
 
 if __name__ == '__main__':
-    pass
+    otaClient = OtaClient()
+    clientType = "123456"
+    deviceId = "ed5011314ad738e3abaf05868ef168dcae00e094"
+    userId = "987654"
+    forFormal = True
+    # print otaClient.httpGetDeviceId(False)
+    print otaClient.httpGetDIdRespond(clientType,deviceId,userId,False)

+ 3 - 6
ssat_sdk/config/resource.cfg

@@ -29,16 +29,13 @@ serialport_list = ['Mstar']
 serialport_select = Mstar
 voicecards = {"default":"pickup","devices":[{"name":"pickup","type":0},{"name":"USB","type":1}]}
 
-[baidu_ocr]
-app_id = 11704335
-api_key = Kb5sasEAbWGXc9CcvsIdmnIA
-secret_key = vIdDB83xAh0q4zp5DmNWM9yLQhPo9ykZ
-
 [MenuTree]
 menutreeselectedchip = MS3663
 menutreeselectedstyle = colorfuB_UI
 menutreeselectedchannel = 
 
 [Sound]
-sound_list = [1000, 1000]
+sound_list = [2000, 2000]
+input_front_mic = Mic in at front panel
+input_back_mic = Mic in at rear panel
 

+ 59 - 0
ssat_sdk/device_manage/TestWizardBLK.py

@@ -0,0 +1,59 @@
+# -*- coding:utf-8 -*-
+from ssat_sdk import sat_environment
+import sys,os
+from ConfigParser import *
+try:
+    SAT_HOME = sat_environment.getSATRCUDIR()
+    BlueToothDir = "bluetooth"
+    TW_BLEK_Path = os.path.join(SAT_HOME,BlueToothDir,"TW_BLEK.ini")
+    Home_Init = True
+except KeyError, e:
+    print "KeyError:", e.message
+
+class BLKConfig():
+    def __init__(self):
+        if not os.path.exists(TW_BLEK_Path):
+            print 'TW_BLEK.ini文件不存在,采用默认按键配置'
+        self.CFGParser = ConfigParser()
+        self.CFGParser.read(TW_BLEK_Path)
+
+    '''
+    将TW_BLEK.ini配置中的所有案件加载到字典中。
+    '''
+    def loadBLEKey(self):
+        section = "BLE_KEY"
+        try:
+            optionList = self.CFGParser.options(section)
+            blkDic = {}
+            for option in optionList:
+                value = self.CFGParser.get(section, option)
+                blkDic[option.lower()] = value
+            return blkDic
+        except Exception,e:
+            print "TW_BLEK.ini配置异常"
+            return None
+
+blkCFG = BLKConfig()
+BLK_DIC = blkCFG.loadBLEKey()
+if BLK_DIC is None:
+    BLK_DIC = {
+        #0C
+        "mute":"0C00E2",
+        "vol+":"0C00E9",
+        "vol-":"0C00EA",
+        "power":"0C0079",
+        "ch+":"0C009C",
+        "ch-":"0C009D",
+        "home":"0C0223",
+        "back":"0C0224",
+        #07
+        "rightarrow":"07004F",
+        "leftarrow":"070050",
+        "downarrow":"070051",
+        "uparrow":"070052",
+        "enter":"070028",
+        "menu":"070065",
+        "settings":"0700A8",
+        "epg":"07008E"
+    }
+# print "BLK_DIC:",BLK_DIC

+ 219 - 0
ssat_sdk/device_manage/testWizardClient.py

@@ -0,0 +1,219 @@
+# -*- coding:utf-8 -*-
+import os, sys, time
+import abc
+import socket
+import json
+import numpy as np
+import struct
+import TestWizardBLK
+import binascii
+
+# 创建ProHeader数据类型
+ProHead = np.dtype({'names': ['version', 'len'], 'formats': ['u1', 'u4']})
+
+
+class TestWizardClient():
+    def __init__(self):
+        '''设备通讯超时值:毫秒'''
+        self.device_timeout = 300
+
+        '''通信sock'''
+        self.sock = None
+        '''通信端口号'''
+        self.port = 5566
+        '''通信状态'''
+        self.constate = False
+        '''连接服务器'''
+        self.connect()
+
+    def __def__(self):
+        self.disconnect()
+
+    '''连接服务器'''
+
+    def connect(self):
+        try:
+            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+            self.sock.connect(('127.0.0.1', self.port))  # 没有返回值;
+            self.sock.sendall('111')
+            self.constate = True
+        except Exception, e:
+            print "TestWizardClient=> socket connect error:", e, self.port
+            self.constate = False
+
+        return self.constate
+
+    # 发送消息;
+    def __sendkey(self, keyname):
+
+        '''是否连接'''
+        if self.constate is False:
+            if self.connect() is False:
+                return None
+        '''发送请求数据'''
+        try:
+            self.sock.settimeout(3)
+            keyname = "ir>"+keyname
+            phead = np.array([(0xAC, keyname.__len__() + np.array([(0xAA, 0)], dtype=ProHead).itemsize)], dtype=ProHead)
+            '''一次性发送完整包'''
+            self.sock.sendall(bytearray(phead.tobytes() + bytes(keyname)))
+            '''分包发送:头、体'''
+            # self.sock.sendall(phead.tobytes())
+            # self.sock.sendall(bytes(keyname))
+            self.sock.settimeout(None)
+            # return False
+        except Exception, e:
+            print "__sendkey=> send Error:", e
+            import traceback
+            traceback.print_exc()
+            self.disconnect()
+            self.connect()
+            return None
+
+        try:
+            self.sock.settimeout(2)
+            recvBytes = bytearray(self.sock.recv(1024))
+            data = self.parseRetData(recvBytes, phead.itemsize)
+            if int(data) == 1:
+                print "Info:__sendkey %s success!"%keyname
+                return True
+            print "Error:__sendkey %s fail!"%keyname
+        except Exception, e:
+            print "__sendkey=> recv Error:", e
+            return None
+
+    def parseRetData(self, recvByteArray, headSize):
+        version = recvByteArray[0]&0xff
+        recvLen = self.byte4ToInt(recvByteArray, 1)
+        if version == 0xAC and recvLen == recvByteArray.__len__():
+            byteArr = bytes(recvByteArray)
+            data = byteArr[headSize: recvByteArray.__len__()]
+            return data
+        return "0"
+
+    def byte4ToInt(self,bytearray, start):
+        i = (bytearray[start] & 0xff) \
+            | (bytearray[start+1] & 0xff) << 8 \
+            | (bytearray[start+2] & 0xff) << 16\
+            | (bytearray[start+3] & 0xff) << 24
+        return i
+
+    # 发送消息;
+    def sendKey(self, keyname, times=1, duration=1):
+        while times > 0:
+            times -= 1
+            self.__sendkey(keyname)
+            time.sleep(duration)
+    
+    # 发送多个按键;
+    def sendKeys(self, keyNames, duration):
+        for key in keyNames:
+            self.__sendkey(key)
+            time.sleep(duration)
+
+    '''
+    用于发送测试精灵指令,不区分子模块,也不获取发送结果,默认指令发送成功。
+    因为各子模块,有不返回结果,而返回结果的,结果格式也不一样。
+    '''
+    def __sendCmd(self, cmdStr):
+        '''是否连接'''
+        if self.constate is False:
+            if self.connect() is False:
+                return False
+
+        '''发送请求数据'''
+        try:
+            self.sock.settimeout(3)
+            cmdStr = "cmd>"+cmdStr
+            phead = np.array([(0xAC, cmdStr.__len__() + np.array([(0xAA, 0)], dtype=ProHead).itemsize)], dtype=ProHead)
+            '''一次性发送完整包'''
+            ecmd = bytearray(phead.tobytes() + bytes(cmdStr))
+            self.sock.sendall(ecmd)
+        except Exception, e:
+            print "__sendCmd=> send Error:", e
+            import traceback
+            traceback.print_exc()
+            self.disconnect()
+            self.connect()
+            return False
+
+        try:
+            self.sock.settimeout(10)
+            recvBytes = bytearray(self.sock.recv(1024))
+            data = self.parseRetData(recvBytes, phead.itemsize)
+            if int(data) == 1:
+                print "Info:__sendkey success!"
+                return True
+            print "Error:__sendkey fail!"
+        except Exception, e:
+            print "__sendCmd=> recv Error:", e
+            return False
+
+    def sendBLEKey(self, bleKeyName):
+        if TestWizardBLK.BLK_DIC.has_key(bleKeyName.lower()):
+            cmdStr = "*INPUT BLEKEY " + TestWizardBLK.BLK_DIC[bleKeyName.lower()]
+            ret = self.__sendCmd(cmdStr)
+            time.sleep(1)
+            return ret
+        else:
+            return False
+    '''
+    电源开关,所有电源同时控制,不区分LAN口
+    :param onOff: ON代表开,OFF代表关,区分大小写。
+    :return True代表成功,False达标失败
+    '''
+    def setAllPower(self, onOff):
+        if onOff <> "ON" and onOff <> "OFF":
+            return False
+        cmdStr = "*SET " + onOff
+        ret = self.__sendCmd(cmdStr)
+        time.sleep(1)
+        return ret
+
+    def loadSignalDataSet(self, file):
+        pass
+
+    def addSignalDataSet(self, file):
+        pass
+
+    def getDeviceName(self):
+        pass
+
+    def getScriptName(self):
+        pass
+
+    def getKeyName(self):
+        pass
+
+    def close(self):
+        self.disconnect()
+
+    def getCurrentDeviceName(self):
+        pass
+
+
+    '''断开连接'''
+
+    def disconnect(self):
+        try:
+            self.sock.shutdown(2)
+            self.sock.close()
+        except Exception, e:
+            print "TestWizardClient=> socket disconnect error:", e
+
+
+if __name__ == "__main__":
+    tw = TestWizardClient()
+    # tw.sendKey('POWER')
+    tw.sendKey('down')
+    # print 'sleep'
+    # time.sleep(3)
+    tw.sendKey('up')
+    # print 111111
+    # print tw.sendBLEKey("uparrow")
+    # time.sleep(3)
+    # print tw.sendBLEKey("downarrow")
+    # time.sleep(3)
+    # print 2222222
+    # print tw.setAllPower("OFF")
+    # print tw.setAllPower("ON")

+ 18 - 4
ssat_sdk/pic_tool.py

@@ -209,7 +209,8 @@ class ImageCMP():
         return img[point[0], point[1]]
 
     '''
-    检测图像是否有马赛克
+    检测图像是否有马赛克,准确度不够
+    ::接口废弃
     '''
     def hasMosaic(self,std_pic,test_pic):
         return PreImageJudge.hasMosaic(std_pic,test_pic)
@@ -259,7 +260,7 @@ class ImageCMP():
             prevImg = imgList[index-1]
             currentImg = imgList[index]
             prevSingle = self.isSingleColor(prevImg)
-            currentSingle = self.isSingleColor(prevImg)
+            currentSingle = self.isSingleColor(currentImg)
             isSimilar = self.rgbColor.isColorSimilar(prevSingle[1],currentSingle[1])
             # print "checkAroundSSColor:",prevSingle,currentSingle,isSimilar
             self.rgbColor = None
@@ -301,6 +302,18 @@ class ImageCMP():
 
 if __name__ == "__main__":
     imgCmp = ImageCMP()
+    picStandard = r"E:\0year2020\pic\standard.jpg"
+    picDynamic = r"E:\0year2020\pic\dynamic.jpg"
+    picMovie = r"E:\0year2020\pic\movie.jpg"
+
+    cctM = imgCmp.calculateCCT(picMovie)
+    cctS = imgCmp.calculateCCT(picStandard)
+    cctD = imgCmp.calculateCCT(picDynamic)
+     
+    print "cctM", cctM
+    print "cctS",cctS
+    print "cctD", cctD
+
     #bled = r"D:\TST\test1\blend.jpg"
     #diffPath = r"D:\TST\test1\diff.jpg"
     #pic1 = r"D:\TST\test1\contrast-10.jpg"
@@ -312,8 +325,9 @@ if __name__ == "__main__":
     #pic4= r"D:\TST\image\65-603.jpg"
     #imgCmp.cmpPicTotal(pic3,pic4)
 
-    picPath = r"D:\1.png"
-    imgCmp.calulateCCT(picPath)
+    # picPath = r"D:\1.png"
+
+
     # mat = np.array(cv.imread(picPath))
     # print mat
     # PreImageJudge.getMaxRGB(picPath,(0,0,100,200))

+ 2 - 1
ssat_sdk/picture/CrossColor.py

@@ -60,7 +60,8 @@ def  getPartImage(img_addr):
                 images.append(dest_img[0:dest_img.shape[0],lines[i]+5:dest_img.shape[1]-5,:])
             images.append(dest_img[0:dest_img.shape[0],lines[i-1]+5:lines[i]-5,:])
     return images
-# 对外提供方法
+# 对外提供方法.函数准确度不够。
+# 废弃
 def cmpPicTotal(picpath1):
     images = getPartImage(picpath1)
     if len(images) != 8:

+ 23 - 10
ssat_sdk/picture/image_util.py

@@ -31,7 +31,9 @@ def cutMat(srcimg, area):
         mat_area = coordinateRXDY2DXRY(area)
         # print "cutMat:mat_area:", mat_area
         imgArr = np.array(srcimg)
-        return imgArr[int(mat_area[0]):int(mat_area[2]),int(mat_area[1]):int(mat_area[3])]
+        retMat = imgArr[int(mat_area[0]):int(mat_area[2]),int(mat_area[1]):int(mat_area[3])]
+        del imgArr
+        return retMat
     except Exception, e:
         LoggingUtil.getDebugLogger().info(pyFileName, "image_util", get_current_function_name(), 'ERROR:截图异常:'+str(e))
         return None
@@ -66,13 +68,14 @@ def cutImage(srcimg, area):
 def cutImageByPath(imgPath, area):
     srcimg = cv.imread(imgPath)
     try:
-        return np.array(srcimg[area[1]:area[3], area[0]:area[2]])
+        dstImg = cutImage(srcimg, area)
+        del srcimg
+        return dstImg
     except Exception,e:
         LoggingUtil.getDebugLogger().info(pyFileName, "image_util", get_current_function_name(), 'ERROR:裁剪图片异常:'+str(e))
         return None
 
-def saveCropPic(srcPic, destPic, area):
-    srcImg = cv.imread(srcPic)
+def saveCropImg(srcImg, destPic, area):
     if srcImg is None:
         LoggingUtil.getDebugLogger().info(pyFileName, "image_util", get_current_function_name(), 'SDK:打开图像返回空对象,文件可能不存在或无权限打开')
         return False
@@ -84,12 +87,15 @@ def saveCropPic(srcPic, destPic, area):
         LoggingUtil.getDebugLogger().info(pyFileName, "image_util", get_current_function_name(), 'SDK:截图失败:')
         return False
     cv.imwrite(destPic,destImg)
+    del destImg
     return True
 
-def saveCropImg(srcImg, destPic, area):
-    destImg = cutMat(srcImg, area)
-    cv.imwrite(destPic,destImg)
-    return True
+def saveCropPic(srcPic, destPic, area):
+    srcImg = cv.imread(srcPic)
+    ret = saveCropImg(srcImg, destPic, area)
+    del srcImg
+    return ret
+
 
 def cutImgWithRate(srcImg, destPic, rateArea):
     height, width, dim = srcImg.shape
@@ -101,17 +107,23 @@ def cutImgWithRate(srcImg, destPic, rateArea):
     print "dest: dest_x1,dest_y1, dest_x2, dest_y2:", dest_x1,dest_y1, dest_x2, dest_y2
     destImg = cutMat(srcImg, (dest_x1,dest_y1, dest_x2, dest_y2))
     cv.imwrite(destPic, destImg)
-
+    del destImg
+'''
+根据比例裁剪图片,并保存裁剪结果到指定文件中
+'''
 def cutPicWithRate(srcPic, destPic, rateArea):
     srcImg = cv.imread(srcPic)
     cutImgWithRate(srcImg, destPic, rateArea)
+    del srcImg
 
 '''
 截取选中区域以外的图片
 '''
 def saveRetCropPic(srcPic, area):
     srcImg = cv.imread(srcPic)
-    return saveRetCropImg(srcImg, area)
+    imgList = saveRetCropImg(srcImg, area)
+    del srcImg
+    return imgList
 
 def saveRetCropImg(srcImg, area):
     # print "saveRetCropImg,area:",area
@@ -143,6 +155,7 @@ def saveThresholdPicImg(srcImg,threshLow,threshTop, thresholdType):
     img_gray = cv.cvtColor(srcImg, cv.COLOR_BGR2GRAY)
     # print u"转成二值化;"
     ret, img_threshold = cv.threshold(img_gray, threshLow, threshTop, thresholdType)
+    del img_gray
     return img_threshold
 
 '''

+ 7 - 2
ssat_sdk/picture/pq_detect.py

@@ -6,6 +6,7 @@ import os, sys, time
 import cv2 as cv
 import numpy as np
 import random
+import math
 
 class PQDetect():
     def __init__(self):
@@ -91,7 +92,9 @@ class PQDetect():
     '''
     def calculateCCT(self, pic_path):
         img = cv.imread(pic_path)
-        xyImg = cv.cvtColor(img,cv.COLOR_BGR2XYZ)
+        # xyImg = cv.cvtColor(img,cv.COLOR_BGR2XYZ)
+        xyImg = cv.cvtColor(img, cv.COLOR_RGB2XYZ)
+        # print "xyImg",xyImg
         #色温计算
         X = np.average(xyImg[:,:,0])
         Y = np.average(xyImg[:,:,1])
@@ -101,7 +104,9 @@ class PQDetect():
         print "calculateCCT,x,y:",x,y
         n = (x-0.3320)/(0.1858-y)
         print "calculateCCT,n:",n
-        CCT = 437*n**3 + 3601*n**2 + 6831*n + 5517
+        # CCT = 437*n**3 + 3601*n**2 + 6831*n + 5517
+        # CCT = 437*math.pow(n,3) + 3601*math.pow(n,2) + 6831*n + 5517
+        CCT=-449*math.pow(n,3) + 3525*math.pow(n,2) - 6823.3*n + 5520.33
         print "calculateCCT,CCT:",CCT
         return CCT
 

+ 177 - 105
ssat_sdk/python_uiautomator.py

@@ -62,7 +62,9 @@ class PyUIAutomator():
         #     print e
         #     pass
         # self.u.app_stop_all()
-
+    '''
+    废弃,改用sat_environment.getAdbDeviceSatatus()
+    '''
     def checkAdbConnected(self):
         return u2.check_adb_connect()
 
@@ -74,6 +76,9 @@ class PyUIAutomator():
             # self.reConnect_usb()
             time.sleep(1)
 
+    def killAdb(self):
+        return u2.killAdb()
+
     def getAdbType(self):
         return u2.check_adb_connect_type()
 
@@ -106,7 +111,11 @@ class PyUIAutomator():
         # print "getUiObject2,paramDict:",paramDict
         uiObject = None
         if paramDict.__len__>0:
-            uiObject = self.u(**paramDict)
+            try:
+                uiObject = self.u(**paramDict)
+            except Exception,e:
+                print "UIAutomator未启动,请确认是否为开机状态"
+                uiObject = None
             # print uiObject,uiObject.info
         return uiObject
 
@@ -132,7 +141,36 @@ class PyUIAutomator():
 
     '''
 
-    def getUiObject(self, className='', resourceId='', text='', description='', bounds='', instance=-1):
+    def getUiObject(self, className='', resourceId='', text='', description='', bounds = '',instance=-1):
+
+        if text != "":
+            if type(text) is not type([]):
+                obj = self.getUiObjectTest(className,resourceId,text,description,bounds,instance)
+                return obj
+            if type[text] is type([]):
+                count = 0
+                for t in text:
+                    try:
+                        obj = self.getUiObjectTest(className,resourceId,t,description,bounds,instance)
+                        objInfo = obj.info
+                        print "文本%s对应的option已找到"%t
+                        return obj
+                    except Exception,e:
+                        nextText = text[count+1]
+                        print"文本%s对应的option未找到,匹配下一个文本%s"%(t,nextText)
+                    count += 1
+                print "所有文本均已匹配完毕,未找到对应option"
+                return None
+
+        else:
+            obj = self.getUiObjectTest(className,resourceId,text,description,bounds,instance)
+        return obj
+
+
+
+
+
+    def getUiObjectTest(self, className='', resourceId='', text='', description='', bounds = '',instance=-1):
         self.listenAdbConnected()
         # print 'className:', className
         # print 'resourceId:', resourceId
@@ -143,7 +181,7 @@ class PyUIAutomator():
         uiObject = None
         try:
 
-            if className == "" and resourceId == "" and text == "" and description == "" and \
+            if className == "" and resourceId == "" and text == "" and description == "" and\
                     bounds == "" and instance == -1:
                 print "没有参数带入,找不到对象"
                 return uiObject
@@ -151,54 +189,54 @@ class PyUIAutomator():
             if className != "" and resourceId != "" and text != "" and description != "" and \
                     bounds != "" and instance != -1:
                 uiObject = self.u(className=className, resourceId=resourceId, text=text, description=description,
-                                  bounds=bounds, instance=instance)
+                                  bounds=bounds,instance=instance)
             # 缺少一个元素的
-            # 缺instance
+            #缺instance
             if className != "" and resourceId != "" and text != "" and description != "" and \
                     bounds != "" and instance == -1:
-                uiObject = self.u(className=className, resourceId=resourceId, text=text, bounds=bounds,
+                uiObject = self.u(className=className, resourceId=resourceId, text=text, bounds = bounds,
                                   description=description)
-            # 缺bounds
+            #缺bounds
             if className != "" and resourceId != "" and text != "" and description != "" and \
                     bounds == "" and instance != -1:
                 uiObject = self.u(className=className, resourceId=resourceId, text=text,
-                                  description=description, instance=instance)
-            # 缺description
-            if className != "" and resourceId != "" and text != "" and description == "" and \
+                                  description=description,instance = instance)
+            #缺description
+            if className != "" and resourceId != "" and text != "" and description == "" and\
                     bounds != "" and instance != -1:
                 uiObject = self.u(className=className, resourceId=resourceId, text=text,
-                                  bounds=bounds, instance=instance)
-            # 缺text
-            if className != "" and resourceId != "" and text == "" and description != "" and \
+                                  bounds=bounds,instance=instance)
+            #缺text
+            if className != "" and resourceId != "" and text == "" and description != "" and\
                     bounds != "" and instance != -1:
                 uiObject = self.u(className=className, resourceId=resourceId, description=description,
-                                  bounds=bounds, instance=instance)
-            # 缺resouceId
+                                  bounds=bounds,instance=instance)
+            #缺resouceId
             if className != "" and resourceId == "" and text != "" and description != "" and \
-                    bounds != "" and instance != -1:
+                    bounds!="" and instance != -1:
                 uiObject = self.u(className=className, text=text, description=description,
-                                  bounds=bounds, instance=instance)
+                                  bounds=bounds,instance=instance)
             # lack of className
             if className == "" and resourceId != "" and text != "" and description != "" and \
-                    bounds != "" and instance != -1:
+                    bounds!="" and instance != -1:
                 uiObject = self.u(resourceId=resourceId, text=text, description=description,
-                                  bounds=bounds, instance=instance)
+                                  bounds=bounds,instance=instance)
 
             # 缺少两个元素的
             # lack of className and resourceId
             if className == "" and resourceId == "" and text != "" and description != "" and \
-                    bounds != "" and instance != -1:
-                uiObject = self.u(text=text, description=description, bounds=bounds,
+                    bounds!="" and instance != -1:
+                uiObject = self.u(text=text, description=description,bounds=bounds,
                                   instance=instance)
             # lack of className and text
             if className == "" and resourceId != "" and text == "" and description != "" and \
                     bounds != "" and instance != -1:
                 uiObject = self.u(resourceId=resourceId, description=description, bounds=bounds,
                                   instance=instance)
-            # lack of className and description
+            #lack of className and description
             if className == "" and resourceId != "" and text != "" and description == "" and \
-                    bounds != "" and instance != -1:
-                uiObject = self.u(resourceId=resourceId, text=text, bounds=bounds,
+                    bounds!="" and instance != -1:
+                uiObject = self.u(resourceId=resourceId, text=text,bounds=bounds,
                                   instance=instance)
             # lack of className and bounds
             if className == "" and resourceId != "" and text != "" and description != "" and \
@@ -212,13 +250,13 @@ class PyUIAutomator():
                                   bounds=bounds)
             # lack of resourceId and text
             if className != "" and resourceId == "" and text == "" and description != "" and \
-                    bounds != "" and instance != -1:
-                uiObject = self.u(className=className, description=description, bounds=bounds,
+                    bounds!="" and instance != -1:
+                uiObject = self.u(className=className, description=description,bounds=bounds,
                                   instance=instance)
             # lack of resourceId and description
             if className != "" and resourceId == "" and text != "" and description == "" and \
-                    bounds != "" and instance != -1:
-                uiObject = self.u(className=className, text=text, bounds=bounds,
+                    bounds!="" and instance != -1:
+                uiObject = self.u(className=className, text=text,bounds = bounds,
                                   instance=instance)
             # lack of resourceId and bounds
             if className != "" and resourceId != "" and text == "" and description != "" and \
@@ -232,167 +270,167 @@ class PyUIAutomator():
                                   bounds=bounds)
             # lack of text and description
             if className != "" and resourceId != "" and text == "" and description == "" and \
-                    bounds != "" and instance != -1:
+                    bounds!="" and instance != -1:
                 uiObject = self.u(className=className, resourceId=resourceId, bounds=bounds,
                                   instance=instance)
             # lack of text and bounds
             if className != "" and resourceId != "" and text == "" and description != "" and \
-                    bounds == "" and instance != -1:
+                    bounds=="" and instance != -1:
                 uiObject = self.u(className=className, resourceId=resourceId,
-                                  description=description, instance=instance)
+                                  description=description,instance=instance)
             # lack of text and instance
             if className != "" and resourceId != "" and text == "" and description != "" and \
-                    bounds != "" and instance == -1:
+                    bounds!="" and instance == -1:
                 uiObject = self.u(className=className, resourceId=resourceId,
-                                  description=description, bounds=bounds)
+                                  description=description,bounds=bounds)
             # lack of description and bounds
             if className != "" and resourceId != "" and text != "" and description == "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(className=className, resourceId=resourceId, text=text,
+                    bounds=="" and instance != -1:
+                uiObject = self.u(className=className, resourceId=resourceId, text = text,
                                   instance=instance)
             # lack of description and instance
             if className != "" and resourceId != "" and text != "" and description == "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(className=className, resourceId=resourceId, text=text,
+                    bounds!="" and instance == -1:
+                uiObject = self.u(className=className, resourceId=resourceId, text = text,
                                   bounds=bounds)
             # lack of bounds and instance
             if className != "" and resourceId != "" and text != "" and description != "" and \
-                    bounds == "" and instance == -1:
-                uiObject = self.u(className=className, resourceId=resourceId, text=text,
+                    bounds=="" and instance == -1:
+                uiObject = self.u(className=className, resourceId=resourceId, text = text,
                                   description=description)
 
             # 缺少3个元素
             # lack of className and resourceId and text
             if className == "" and resourceId == "" and text == "" and description != "" and \
-                    bounds != "" and instance != -1:
-                uiObject = self.u(description=description, bounds=bounds, instance=instance)
+                    bounds!="" and instance != -1:
+                uiObject = self.u(description=description, bounds=bounds,instance=instance)
             # lack of className and resourceId and description
             if className == "" and resourceId == "" and text != "" and description == "" and \
-                    bounds != "" and instance != -1:
-                uiObject = self.u(text=text, bounds=bounds, instance=instance)
+                    bounds!="" and instance != -1:
+                uiObject = self.u(text = text,bounds=bounds,instance=instance)
             # lack of className and resourceId and bounds
             if className == "" and resourceId == "" and text != "" and description != "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(text=text, description=description, instance=instance)
+                    bounds =="" and instance != -1:
+                uiObject = self.u(text = text,description=description,instance=instance)
             # lack of className and resourceId and instance
             if className == "" and resourceId == "" and text != "" and description != "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(text=text, description=description, bounds=bounds)
+                    bounds!="" and instance == -1:
+                uiObject = self.u(text = text,description=description,bounds=bounds)
             # lack of className and text and description
             if className == "" and resourceId != "" and text == "" and description == "" and \
-                    bounds != "" and instance != -1:
-                uiObject = self.u(resourceId=resourceId, bounds=bounds, instance=instance)
+                    bounds!="" and instance != -1:
+                uiObject = self.u(resourceId=resourceId,bounds=bounds,instance=instance)
             # lack of className and text and bounds
             if className == "" and resourceId != "" and text == "" and description != "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(resourceId=resourceId, description=description, instance=instance)
-            # lack of className and text and instance
+                    bounds=="" and instance != -1:
+                uiObject = self.u(resourceId=resourceId,description=description,instance=instance)
+             # lack of className and text and instance
             if className == "" and resourceId != "" and text == "" and description != "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(resourceId=resourceId, description=description, bounds=bounds)
+                    bounds!="" and instance == -1:
+                uiObject = self.u(resourceId=resourceId,description=description,bounds=bounds)
             # lack of className and description and bounds
             if className == "" and resourceId != "" and text != "" and description == "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(resourceId=resourceId, text=text, instance=instance)
+                    bounds=="" and instance != -1:
+                uiObject = self.u(resourceId=resourceId, text = text,instance=instance)
             # lack of className and description and instance
             if className == "" and resourceId != "" and text != "" and description == "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(resourceId=resourceId, text=text, bounds=bounds)
+                    bounds!="" and instance == -1:
+                uiObject = self.u(resourceId=resourceId, text = text,bounds=bounds)
             # lack of resourceId and text and description
             if className != "" and resourceId == "" and text == "" and description == "" and \
-                    bounds != "" and instance != -1:
-                uiObject = self.u(className=className, bounds=bounds, instance=instance)
+                    bounds!="" and instance != -1:
+                uiObject = self.u(className=className,bounds=bounds,instance=instance)
             # lack of resourceId and text and bounds
             if className != "" and resourceId == "" and text == "" and description != "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(className=className, description=description, instance=instance)
+                    bounds=="" and instance != -1:
+                uiObject = self.u(className=className,description=description,instance=instance)
             # lack of resourceId and text and instance
             if className != "" and resourceId == "" and text == "" and description != "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(className=className, description=description, bounds=bounds)
+                    bounds!="" and instance == -1:
+                uiObject = self.u(className=className,description=description,bounds=bounds)
             # lack of resourceId and description and bounds
             if className != "" and resourceId == "" and text != "" and description == "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(className=className, text=text, instance=instance)
+                    bounds =="" and instance != -1:
+                uiObject = self.u(className=className,text = text,instance=instance)
             # lack of resourceId and description and instance
             if className != "" and resourceId == "" and text != "" and description == "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(className=className, text=text, bounds=bounds)
+                    bounds!="" and instance == -1:
+                uiObject = self.u(className=className, text = text,bounds=bounds)
             # lack of resourceId and bounds and instance
             if className != "" and resourceId == "" and text != "" and description != "" and \
-                    bounds == "" and instance == -1:
-                uiObject = self.u(className=className, text=text, description=description)
+                    bounds =="" and instance == -1:
+                uiObject = self.u(className=className, text = text,description=description)
             # lack of text and description and bounds
             if className != "" and resourceId != "" and text == "" and description == "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(className=className, resourceId=resourceId, instance=instance)
+                    bounds=="" and instance != -1:
+                uiObject = self.u(className=className, resourceId=resourceId,instance=instance)
             # lack of text and description and instance
             if className != "" and resourceId != "" and text == "" and description == "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(className=className, resourceId=resourceId, bounds=bounds)
+                    bounds!="" and instance == -1:
+                uiObject = self.u(className=className, resourceId=resourceId,bounds=bounds)
             # lack of description and bounds and instance
             if className != "" and resourceId != "" and text != "" and description == "" and \
-                    bounds == "" and instance == -1:
-                uiObject = self.u(className=className, resourceId=resourceId, text=text)
+                    bounds =="" and instance == -1:
+                uiObject = self.u(className=className, resourceId=resourceId, text = text)
 
             # 缺少4个元素
             # lack of className and resourceId and text and description
             if className == "" and resourceId == "" and text == "" and description == "" and \
-                    bounds != "" and instance != -1:
-                uiObject = self.u(bounds=bounds, instance=instance)
+                    bounds !="" and instance != -1:
+                uiObject = self.u(bounds=bounds,instance=instance)
             # lack of className and resourceId and text and bounds
             if className == "" and resourceId == "" and text == "" and description != "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(description=description, instance=instance)
+                    bounds =="" and instance != -1:
+                uiObject = self.u(description=description,instance=instance)
             # lack of className and resourceId and text and instance
             if className == "" and resourceId == "" and text == "" and description != "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(description=description, bounds=bounds)
+                    bounds !="" and instance == -1:
+                uiObject = self.u(description=description,bounds=bounds)
             # lack of className and resourceId and description and bounds
             if className == "" and resourceId == "" and text != "" and description == "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(text=text, instance=instance)
+                    bounds =="" and instance != -1:
+                uiObject = self.u(text = text,instance=instance)
             # lack of className and resourceId and description and instance
             if className == "" and resourceId == "" and text != "" and description == "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(text=text, bounds=bounds)
+                    bounds !="" and instance == -1:
+                uiObject = self.u(text = text,bounds=bounds)
             # lack of resourceid and text and description and bounds
             if className != "" and resourceId == "" and text == "" and description == "" and \
-                    bounds == "" and instance != -1:
-                uiObject = self.u(className=className, instance=instance)
+                    bounds =="" and instance != -1:
+                uiObject = self.u(className=className,instance=instance)
             # lack of resourceid and text and description and instance
             if className != "" and resourceId == "" and text == "" and description == "" and \
-                    bounds != "" and instance == -1:
-                uiObject = self.u(className=className, bounds=bounds)
+                    bounds !="" and instance == -1:
+                uiObject = self.u(className=className,bounds=bounds)
             # lack of text and description and bounds and instance
             if className != "" and resourceId != "" and text == "" and description == "" and \
-                    bounds == "" and instance == -1:
+                    bounds =="" and instance == -1:
                 uiObject = self.u(className=className, resourceId=resourceId)
 
             # 缺少5个元素的
             # only className
             if className != "" and resourceId == "" and text == "" and description == "" and \
-                    bounds == "" and instance == -1:
+                    bounds =="" and instance == -1:
                 uiObject = self.u(className=className)
             # only resourceid
             if className == "" and resourceId != "" and text == "" and description == "" and \
-                    bounds == "" and instance == -1:
+                    bounds =="" and instance == -1:
                 uiObject = self.u(resourceId=resourceId)
             # only text
             if className == "" and resourceId == "" and text != "" and description == "" and \
-                    bounds == "" and instance == -1:
-                uiObject = self.u(text=text)
+                    bounds =="" and instance == -1:
+                uiObject = self.u(text = text)
             # only description
             if className == "" and resourceId == "" and text == "" and description != "" and \
-                    bounds == "" and instance == -1:
+                    bounds =="" and instance == -1:
                 uiObject = self.u(description=description)
             # only bounds
             if className == "" and resourceId == "" and text == "" and description == "" and \
-                    bounds != "" and instance == -1:
+                    bounds !="" and instance == -1:
                 uiObject = self.u(bounds=bounds)
             # only instance
             if className == "" and resourceId == "" and text == "" and description == "" and \
-                    bounds == "" and instance == -1:
+                    bounds =="" and instance == -1:
                 uiObject = self.u(instance=instance)
             return uiObject
         except Exception, e:
@@ -756,13 +794,17 @@ class DirectionManageAndroid():
     '''
     ImgRectLimit = 10000
 
-    def getTargetDirection(self, focusedBounds, destBounds):
+    def getTargetDirection(self, focusedBounds, destBounds, moveKey=["down","up"]):
         areaL = (focusedBounds['left'], focusedBounds['top'], focusedBounds['right'], focusedBounds['bottom'])
         areaT = (destBounds['left'], destBounds['top'], destBounds['right'], destBounds['bottom'])
         print "getTargetDirection:", areaL, areaT
         LoggingUtil.getDebugLogger().info(pyFileName, self.className,
                                           get_current_function_name(),
                                           u'script:focusMatch 当前聚焦区域坐标:%s  目标聚焦区域坐标:%s' % (str(areaL), str(areaT)))
+        if self.hasSameArea(areaL,areaT):
+            focusCP = [(areaL[2] + areaL[0])/2,(areaL[3]+areaL[1])/2]
+            destCP = [(areaT[2] + areaT[0])/2,(areaT[3]+areaT[1])/2]
+            return self.getPointDirection(focusCP,destCP)
         # 确定4个面位方位的区域的x,y轴范围
         up = [areaL[0], 0, areaL[2], areaL[1]]
         down = [areaL[0], areaL[3], areaL[2], DirectionManageAndroid.ImgRectLimit]
@@ -786,13 +828,43 @@ class DirectionManageAndroid():
         dL_Ux = areaT[0] - areaL[0]
         dL_Uy = areaT[1] - areaL[1]
         if (dL_Ux > 0) and (dL_Uy > 0):
-            return "down-right"
+            if "down" in moveKey:
+                return "down-right"
+            else:
+                return "right-down"
         if (dL_Ux < 0) and (dL_Uy > 0):
-            return "down-left"
+            if "down" in moveKey:
+                return "down-left"
+            else:
+                return "left-down"
         if (dL_Ux > 0) and (dL_Uy < 0):
-            return "up-right"
+            if "down" in moveKey:
+                return "up-right"
+            else:
+                return "right-up"
         if (dL_Ux < 0) and (dL_Uy < 0):
-            return "up-left"
+            if "down" in moveKey:
+                return "up-left"
+            else:
+                return "left-up"
+
+    '''
+    计算点p2相对p1的方位。按p1 45°斜线划分上下左右。
+    '''
+    def getPointDirection(self,p1,p2):
+        print "getPointDirection,p1,p2:",p1,p2
+        dx = p2[0] - p1[0]
+        dy = p2[1] - p1[1]
+        if dx >= 0 and abs(dy) < abs(dx):
+            return "right"
+        if dx <= 0 and abs(dy) < abs(dx):
+            return "left"
+        if dy >= 0 and abs(dy) >= abs(dx):
+            return "down"
+        if dy <= 0 and abs(dy) >= abs(dx):
+            return "up"
+        return "center"
+
 
     '''
     检测两个区域是否有重叠部分
@@ -1249,7 +1321,7 @@ class FocusManageAndroid():
         print "执行%s 次查找,仍未找到目标焦点!!" % (str(Max_Try))
         return False
 
-    # zhouyi
+
         # 在RecyclerView 中 focused控件包含bounds控件
     def toDestFocusByBounds_for_RecyclerView(self, bounds, boundsResourceId="", findDirection="down",
                                              Max_Try=10, keyType=UATTree.Key_Event, hb_keyDict={}):

+ 32 - 24
ssat_sdk/result_tool.py

@@ -90,9 +90,9 @@ class ResultTool(ProcessSync):
         print "updateBaseDir,dir=",dir
         ResultTool.Result_DIR = unicode(dir)
         ResultTool.Result_DIR_DETAIL = ResultTool.Result_DIR + "/" + ResultTool.Result_DETAIL
-        ResultTool.Result_DIR_LOG = ResultTool.Result_DIR + "/adb_log/" + ResultTool.File_Name
-        ResultTool.Result_DIR_SCREEN = ResultTool.Result_DIR + "/screen/" + ResultTool.File_Name
-        ResultTool.Result_DIR_SCRIPTS = ResultTool.Result_DIR + "/scripts/" + ResultTool.File_Name
+        ResultTool.Result_DIR_LOG = ResultTool.Result_DIR + "/adb_log/"
+        ResultTool.Result_DIR_SCREEN = ResultTool.Result_DIR + "/screen/"
+        ResultTool.Result_DIR_SCRIPTS = ResultTool.Result_DIR + "/scripts/"
         ResultTool.checkResultDIR()
         ResultTool.copyInitFile()
 
@@ -144,30 +144,32 @@ class ResultTool(ProcessSync):
             print e
         return casename + "/" + os.path.basename(picpath), os.path.basename(picpath)
 
-    def saveDetailResult2(self, destxml, itemname, result, picNameList, logName, data="",remark=""):
+    def saveDetailResult2(self, destxml, itemname, result, picNameList, logName, data="",remark="", caseFinish=0):
         print "saveDetailResult2,picNameList:", picNameList
         print "saveDetailResult:",destxml
         itemList = xml_util.parseDetailResult(destxml.decode('utf-8'))
-        item = self.getItemByName(itemList,itemname)
-        if (item == None):
-            item =  Item()
-            item.name = itemname
-            itemList.append(item)
-        item.result = result
-        del item.screenPathList[:]
-        for picName in picNameList:
-            item.screenPathList.append("../screen/" + picName)
-        if logName <> "":
-            item.logPath = "../log/" + logName
-        item.data =data
-        item.remark = remark
-        xml_util.saveDetailResult(destxml.decode('utf-8'), itemList)
+        if itemname is not None:
+            item = self.getItemByName(itemList,itemname)
+            if (item == None):
+                item =  Item()
+                item.name = itemname
+                itemList.append(item)
+            item.result = result
+            del item.screenPathList[:]
+            for picName in picNameList:
+                item.screenPathList.append("../screen/" + picName)
+            if logName <> "":
+                item.logPath = "../adb_log/" + logName
+            item.data =data
+            item.remark = remark
+        xml_util.saveDetailResult(destxml.decode('utf-8'), itemList, caseFinish)
+
 
 
     """
     picpathList:图片路径数组.最多存储6张图片
     """
-    def saveDetailResult(self, xmlName, itemname, result, picpathList, logpath="", data="", remark=""):
+    def saveDetailResult(self, xmlName, itemname, result, picpathList, logpath="", data="", remark="", caseFinish=0):
         xmlPath = ResultTool.Result_DIR_DETAIL + "/"+ xmlName
         # print "saveDetailResult,xmlPath=", xmlPath
         # print "saveDetailResult,picpathList:", picpathList
@@ -185,15 +187,18 @@ class ResultTool(ProcessSync):
         rePicNameList = []
         picNameList = []
         for picpath in picList:
-            if picpath.__len__() < 5:
+            if picpath is None or picpath.__len__() < 5:
                 break
             rePicName, picName = self.addItemPicture(self.tc.name, picpath)
             rePicNameList.append(rePicName)
             picNameList.append(picName)
         logname = os.path.basename(logpath)
-        self.saveDetailResult2(xmlPath, itemname, result,rePicNameList, logname, data, remark)
-        destPicDir = os.path.join(self.getScreenDir(), self.tc.name)
-        runner_sender.sendActionResult(self.tc.name, self.detailItemIndex, itemname, "", result, remark, destPicDir,"", picNameList,"",data)
+        self.saveDetailResult2(xmlPath, itemname, result,rePicNameList, logname, data, remark,caseFinish=caseFinish)
+        # destPicDir = os.path.join(self.getScreenDir(), self.tc.name)
+        # runner_sender.sendActionResult(self.tc.name, self.detailItemIndex, itemname, "", result, remark, destPicDir,"", picNameList,"",data)
+
+    def setCaseFinish(self, xmlName, caseFinish):
+        self.saveDetailResult(xmlName,None,"",[],caseFinish=caseFinish)
 
     def saveCaseResult2(self, result, detailFilePath):
         self.tc.result = result
@@ -204,12 +209,15 @@ class ResultTool(ProcessSync):
         #Packet case result
         #兼容制定测试用例名,和不指定测试用例的情况
 
+    '''
+    用于保存all-case中的测试结果
+    '''
     def saveCaseResult(self,result, detailFileName):
         #detailFilePath = ResultTool.Result_DIR + detailFileName
         detailFilePath = ResultTool.Result_DETAIL + "/"+detailFileName
         self.Case_Result = result
         self.saveCaseResult2(self.Case_Result, detailFilePath)
-        runner_sender.sendCaseResult(self.tc.name, self.detailItemIndex, result, "", "", "")
+        # runner_sender.sendCaseResult(self.tc.name, self.detailItemIndex, result, "", "", "")
 
 if __name__ == "__main__":
     # Result_XMLFile = "all-result.xml"

+ 32 - 26
ssat_sdk/sat_environment.py

@@ -7,7 +7,16 @@ sys.setdefaultencoding("utf-8")
 from utils.platform_Util import *
 from ConfigParser import *
 import json
+from ssat_sdk.service.service_config import ServiceConfig
 
+'''
+用于向外提供配置参数
+两份配置文件:ssat_sdk/config/resource_run.cfg、ssat_sdk/config/server_run.cfg
+resource_run.cfg:存储系统必须的初始化配置
+server_run.cfg:存储脚本和SATservice交互的状态参数,ServiceConfig单独处理
+'''
+
+serviceCFG = ServiceConfig()
 class TConfig:
     def __init__(self, ini_path):
         self.path = ini_path
@@ -148,7 +157,7 @@ try:
     Resource_CFG_Path = Python_Home + Environment.SDK_Relative_Path + "config/resource_run.cfg"
     Resource_CFG_Local_Path = Python_Home + Environment.SDK_Relative_Path + "config/resource.cfg"
     if not os.path.exists(Resource_CFG_Path):
-        print 'server_run.cfg文件不存在,复制server.cfg文件'
+        print 'resource_run.cfg文件不存在,复制resource.cfg文件'
         shutil.copy(Resource_CFG_Local_Path, Resource_CFG_Path)
     # Resource_CFG_Path = "config/resource.cfg"
     # 获取22293的配置路径
@@ -862,12 +871,12 @@ def getSoundList():
         else:
             sound_list = sound_list_str
         if sound_list == []:
-            sound_list = [1000, 1000]
+            sound_list = [2000, 2000]
             writeSoundList(sound_list)
         return sound_list
     except Exception, e:
         print e
-        sound_list = [1000, 1000]
+        sound_list = [2000, 2000]
         writeSoundList(sound_list)
         return sound_list
 
@@ -875,6 +884,13 @@ def getSoundList():
 def writeSoundList(sound_list):
     writeConfigFile('Sound', 'sound_list', str(sound_list))
 
+'''
+根据传入自定义设备名,查设备真实名字
+'''
+def getSoundDevice(devName):
+    return g_cfgParser.get_value("Sound",devName)
+
+
 def getOtaParamUrl_dict():
     return eval(g_cfgParser.get_value("URL", "getParam"))
 
@@ -883,38 +899,28 @@ adb设备可用或不可用状态;
 return ture:可用,false:不可用
 '''
 def getAdbDeviceSatatus():
-    Online = "Online"
-    options = g_cfgParser.get_options("ADBSTATUS")
-    # print "getAdbDeviceSatatus,options:",options
-    if options is None:
-        return False
-    for option in options:
-        status = g_cfgParser.get_value("ADBSTATUS",option)
-        # print "getAdbDeviceSatatus,%s:"%option, status
-        if status == Online:
-            return True
-    return False
+    return serviceCFG.getAdbDeviceSatatus()
 
 '''
 获取adb设备状态描述
 return: none表示没有这个设备,Unauthorized表示设备未授权,offline表示设备离线,online表示在线可用
 '''
 def getAdbDevSTDesc():
-    Online = "Online"
-    options = g_cfgParser.get_options("ADBSTATUS")
-    # print "getAdbDeviceSatatus,options:",options
-    if options is None:
-        return "none"
-    for option in options:
-        status = g_cfgParser.get_value("ADBSTATUS", option)
-        return status
-    return "none"
+    return serviceCFG.getAdbDevSTDesc()
 
 
 if __name__ == "__main__":
-    dict = getOtaParamUrl_dict()
-    print "CN:",dict["CN"]
-    print "ME:",dict["ME"]
+
+    # print "sections:", serviceCFG.CFGParser.sections()
+    for i in range(200):
+        ret = getAdbDevSTDesc()
+        ret2 = getAdbDeviceSatatus()
+        print "ret,ret2:", ret, ret2
+        time.sleep(1)
+
+    # dict = getOtaParamUrl_dict()
+    # print "CN:",dict["CN"]
+    # print "ME:",dict["ME"]
     # print "OCR IP addr:", getOCRIpAddr()
     # print "OCR port:", getOCRPort()
     # print "Serial port:", getSerialCOM()

+ 91 - 9
ssat_sdk/service/service_config.py

@@ -19,7 +19,6 @@ except KeyError, e:
 class ServiceConfig():
     ServiceModel = "service"
     CFGParser = ConfigParser()
-    CFGParser.read(Server_CFG_Path)
 
     def __init__(self):
         if not os.path.exists(Server_CFG_Path):
@@ -30,83 +29,166 @@ class ServiceConfig():
     def writeCFG(self):
         self.CFGParser.write(open(Server_CFG_Path, "w"))
 
+    def getOptions(self, section):
+        options = None
+        try:
+            options = self.CFGParser.options(section)
+        except Exception,e:
+            print "Error:service_config:section %s not found"%section
+        return options
+
+    '''废弃'''
     def setCCardListenerPort(self, listener_port):
+        self.CFGParser.read(Server_CFG_Path)
         self.CFGParser.set("service","ccard_port", listener_port)
         self.writeCFG()
 
+    '''废弃'''
     def getCCardListenerPort(self):
+        self.CFGParser.read(Server_CFG_Path)
         value = self.CFGParser.getint("service", "ccard_port")
         return value
 
+    '''废弃'''
     def setC22293ListenerPort(self, listener_port):
+        self.CFGParser.read(Server_CFG_Path)
         self.CFGParser.set("service","c22293_port", str(listener_port))
         self.writeCFG()
 
+    '''废弃'''
     def getC22293ListenerPort(self):
+        self.CFGParser.read(Server_CFG_Path)
         value = self.CFGParser.get("service", "c22293_port")
         value_int = int(value)
         return value_int
 
+    '''废弃'''
     def setSDKServer(self, onOff):
+        self.CFGParser.read(Server_CFG_Path)
         self.CFGParser.set("service","onOff", onOff)
         self.writeCFG()
 
+    '''废弃'''
     def getSDKServer(self):
+        self.CFGParser.read(Server_CFG_Path)
         return self.CFGParser.get("service", "onOff")
 
+    '''废弃'''
     def setSDKListenerPort(self, listener_port):
+        self.CFGParser.read(Server_CFG_Path)
         self.CFGParser.set("service", "sdk_port", listener_port)
         self.writeCFG()
 
+    '''废弃'''
     def getSDKListenerPort(self):
+        self.CFGParser.read(Server_CFG_Path)
         value = self.CFGParser.getint("service", "sdk_port")
         return value
 
+    '''废弃'''
     def setTG39ListenerPort(self, listener_port):
+        self.CFGParser.read(Server_CFG_Path)
         self.CFGParser.set("service", "tg39_port", str(listener_port))
         self.writeCFG()
 
+    '''废弃'''
     def getTG39ListenerPort(self):
+        self.CFGParser.read(Server_CFG_Path)
         value = self.CFGParser.get("service", "tg39_port")
         value_int = int(value)
         return value_int
 
+    '''废弃'''
     def setUB530ListenerPort(self, listener_port):
+        self.CFGParser.read(Server_CFG_Path)
         self.CFGParser.set("service", "ub530_port", str(listener_port))
         self.writeCFG()
 
+    '''废弃'''
     def getUB530ListenerPort(self):
+        self.CFGParser.read(Server_CFG_Path)
         value = self.CFGParser.get("service", "ub530_port")
         value_int = int(value)
         return value_int
 
+    '''废弃'''
     def setRedRatHubPort(self, listener_port):
+        self.CFGParser.read(Server_CFG_Path)
         self.CFGParser.set("service", "redrathub_port", listener_port)
         self.writeCFG()
 
+    '''废弃'''
     def getRedRatHubPort(self):
-		try:
-			return self.CFGParser.getint("service","redrathub_port")
-		except:
-			return 40000
+        self.CFGParser.read(Server_CFG_Path)
+        try:
+            return self.CFGParser.getint("service","redrathub_port")
+        except:
+            return 40000
 
+    '''废弃'''
     def getRedRatHubSignalXml(self):
+        self.CFGParser.read(Server_CFG_Path)
         try:
             return self.CFGParser.get("service","redrathub_signal")
         except:
             return None
-    
+
+    '''废弃'''
     def setRedRatHubSignalXml(self, signal_xml):
+        self.CFGParser.read(Server_CFG_Path)
         self.CFGParser.set("service", "redrathub_signal", signal_xml)
         self.writeCFG()
 
+    '''
+    adb设备可用或不可用状态;
+    return ture:可用,false:不可用
+    '''
+
+    def getAdbDeviceSatatus(self):
+        self.CFGParser.read(Server_CFG_Path)
+        Online = "Online"
+        options = self.getOptions("ADBSTATUS")
+        # print "getAdbDeviceSatatus,options:",options
+        if options is None:
+            return False
+        for option in options:
+            status = self.CFGParser.get("ADBSTATUS", option)
+            # print "getAdbDeviceSatatus,%s:"%option, status
+            if status == Online:
+                return True
+        return False
+
+    '''
+    获取adb设备状态描述
+    return: none表示没有这个设备,Unauthorized表示设备未授权,offline表示设备离线,online表示在线可用
+    '''
+
+    def getAdbDevSTDesc(self):
+        self.CFGParser.read(Server_CFG_Path)
+        options = self.getOptions("ADBSTATUS")
+        # print "getAdbDeviceSatatus,options:",options
+        if options is None:
+            return "none"
+        for option in options:
+            status = self.CFGParser.get("ADBSTATUS", option)
+            return status
+        return "none"
+
 def testCFG():
     print "testCFG:"
     serviceCFG = ServiceConfig()
 
 if __name__ == "__main__":
-    thread.start_new_thread(testCFG,())
-    thread.start_new_thread(testCFG,())
-    thread.start_new_thread(testCFG,())
+    # thread.start_new_thread(testCFG,())
+    # thread.start_new_thread(testCFG,())
+    # thread.start_new_thread(testCFG,())
+    serviceCFG = ServiceConfig()
+    # print "sections:", serviceCFG.CFGParser.sections()
+    for i in range(200):
+        ret = serviceCFG.getAdbDevSTDesc()
+        ret2 = serviceCFG.getAdbDeviceSatatus()
+        print "ret,ret2:",ret,ret2
+        time.sleep(1)
+
 
     time.sleep(3)

+ 64 - 17
ssat_sdk/sound/audio_analysis.py

@@ -32,6 +32,7 @@ class AAnalysis():
         self.soundPowerCHS = []
         self.allPowerCHS = []
         self.fftPowerCHS = []
+        self.fftFrqCHS = []
 
     def showLanguage(self, time=5, waveFile=None):
         if waveFile is None:
@@ -235,6 +236,28 @@ class AAnalysis():
             soundPowersL = self.list2NP(self.allPowerCHS[0])
             soundPowersR = self.list2NP(self.allPowerCHS[1])
             return np.average(soundPowersL),np.average(soundPowersR)
+
+    '''
+    计算整个录音的声音最大强度,实际意义不太大,除非是固定幅度音频检测。
+    '''
+    def getTotalMaxPower(self, LR=False):
+        if LR is False:
+            if self.allPowerCHS.__len__() < 1:
+                return 0
+            soundPowersL = self.list2NP(self.allPowerCHS[0])
+            if self.allPowerCHS.__len__() == 2:
+                soundPowersR = self.list2NP(self.allPowerCHS[1])
+                soundPowers = np.where(soundPowersL > soundPowersR, soundPowersL, soundPowersR)
+                return int(np.max(soundPowers))
+            else:
+                return int(np.max(soundPowersL))
+        else:
+            if self.allPowerCHS.__len__() < 1:
+                return 0, 0
+            soundPowersL = self.list2NP(self.allPowerCHS[0])
+            soundPowersR = self.list2NP(self.allPowerCHS[1])
+            return int(np.max(soundPowersL)), int(np.max(soundPowersR))
+
     '''
     计算帧的平均声音强度
     '''
@@ -244,9 +267,8 @@ class AAnalysis():
         return avgPower
 
     '''
-        计算帧的平均声音强度
-        '''
-
+    计算帧的最大声音强度
+    '''
     def getFrameMaxPower(self, frameL):
         maxPower = np.max(np.abs(frameL))
         # print "getFrameMaxPower:",maxPower
@@ -258,8 +280,10 @@ class AAnalysis():
     def getFrameFFTPower(self, frameL, timeL):
         fftFreq = self.time2Frequence(frameL, timeL)
         fftPower = np.max(np.abs(fftFreq))
-        # print "getFrameFFTPower:",fftPower
-        return fftPower
+
+        powerFreq = np.argmax(np.abs(fftFreq))
+        # print "getFrameFFTPower:",fftPower,powerFreq
+        return fftPower,powerFreq
 
     '''
     计算整个录音过程的fft转换后得到的声音强度
@@ -281,6 +305,21 @@ class AAnalysis():
             soundPowersR = self.list2NP(self.fftPowerCHS[1])
             return np.average(soundPowersL),np.average(soundPowersR)
     '''
+    获取整个录音文件每1秒的最大声音频率列表。
+    LR=False,表示左右声道一样,取一个声道的值
+    LR=True,表示左右声道不一样,取两个声道的值
+    :param LR:是否取左右两个声道值。
+    :return :LR=True,二维数组,左右两个声道值;LR=False,一位数组,一个声道值。None:表示数据异常
+    '''
+    def getFFTFreq(self, LR = False):
+        # print "getFFTFreq:",self.fftFrqCHS
+        if self.fftFrqCHS.__len__() < 1:
+            return None
+        if LR is True:
+            return self.fftFrqCHS
+        else:
+            return self.fftFrqCHS[0]
+    '''
     计算帧过零率
     '''
     def ZCR(self,curFrame):
@@ -293,12 +332,14 @@ class AAnalysis():
         return zcr
 
     '''
-    将声音时域数据,转换成频谱数据。
+    将声音时域数据,转换成频谱数据。并指取人耳能听到的部分100Hz ~ 12KHz
     注意:如果帧时长,不是1秒,无法建立频谱图。
     '''
     def time2Frequence(self, frameL, timeL=1.0):
         fftFreq = np.fft.fft(frameL) * 2 / len(frameL)
-        # freq = np.arange(0,len(frameL),1)
+        if np.size(fftFreq) > 12000:
+            fftFreq = fftFreq[0:12000]
+        # freq = np.arange(0,np.size(fftFreq),1)
         # plt.plot(freq[:], abs(fftFreq[:]), 'r')
         # plt.show()
         return fftFreq
@@ -351,11 +392,14 @@ class AAnalysis():
         self.soundPowerCHS = []
         self.allPowerCHS = []
         self.fftPowerCHS = []
+        self.fftFrqCHS = []
         for i in range(channels):
             self.soundPowerCHS.append([])
             self.allPowerCHS.append([])
             self.fftPowerCHS.append([])
+            self.fftFrqCHS.append([])
         while self.isAF is True or frameQue.qsize() > 0:
+            # print "anaysisFrames,frameQue.qsize:",frameQue.qsize()
             frames = frameQue.get()
             dtype = self.getNPDtype(width)
             # print "anaysisFrames,dtype size:", len(frames), channels, dtype
@@ -366,8 +410,9 @@ class AAnalysis():
             for i in range(channels):
                 avgPower = self.getFrameMaxPower(frameL[i])
                 self.allPowerCHS[i].append(avgPower)
-                fftPower = self.getFrameFFTPower(frameL[i], frameTime)
+                fftPower,powerFreq = self.getFrameFFTPower(frameL[i], frameTime)
                 self.fftPowerCHS[i].append(fftPower)
+                self.fftFrqCHS[i].append(powerFreq)
                 channelPowers.append(avgPower)
                 #每一帧各通道数值计算完毕,开始进行帧的有无声判断
                 if i == channels - 1:
@@ -421,7 +466,7 @@ class AAnalysis():
 
     '''
     分析wav音频文件后,可以使用get类型接口,获取结果,例如:getTotalAVGPower
-    分析fft声音频谱时,timeL=1秒才有意义,才能建立频谱图。
+    分析fft声音频谱时,buf_time=1秒才有意义,才能建立频谱图。
     '''
     def analyWav(self, waveFile, buf_time=READ_BUF_TIME):
         self.frameTime = buf_time
@@ -433,10 +478,11 @@ class AAnalysis():
         nChannels, width, frameRate, nframes = wfReader.getparams()[0:4]
         stepnFrames = int(frameRate*buf_time)
         times = int(nframes/stepnFrames)
-        print "analyWav:",buf_time,stepnFrames,times
+        print "analyWav:",buf_time,stepnFrames,times,frameQue.qsize()
         for i in range(times):
             frameL = wfReader.readframes(stepnFrames)
             frameQue.put_nowait(frameL)
+        print "analyWav:", buf_time, stepnFrames, times, frameQue.qsize()
         # self.startFramesAnalysis(frameQue, nChannels, width,timeL)
         # self.endFramesAnalysis()
         self.isAF = False
@@ -446,9 +492,9 @@ class AAnalysis():
 if __name__ == "__main__":
     analysis = AAnalysis()
     # waveFile = "test1.wav"
-    # waveFile = "eq_10khz_v0.wav"
+    waveFile = "eq_10khz_v0.wav"
     # waveFile = "tv/DVB_DTV_automatic_search.wav"
-    waveFile = "wav_balance_v15.wav"
+    # waveFile = "wav_balance_v15.wav"
     # waveFile = r"D:\sound\sound_preset\sound_preset_mode_music.wav"
     # waveFile = r"D:\sound\5k\eq_5khz_v100.wav"
     # waveFile = r"D:\sound\monitorSound_balance.wav"
@@ -457,11 +503,12 @@ if __name__ == "__main__":
     # analysis.showFregForm(waveFile)
     # analysis.showWaveForm(waveFile)
 
-    analysis.analyWav(waveFile)
-    print "sound,status hasSound,hasBlock,hasPlosive:",analysis.hasSound,analysis.hasBlock,analysis.hasPlosive
-    print "0,getSoundAVGPower:", analysis.getSoundAVGPower(LR=True)
-    print "0,getTotalAVGPower:", analysis.getTotalAVGPower(LR=True)
-    print "0,getFFTPower:",analysis.getFFTPower(LR=True)
+    analysis.analyWav(waveFile, buf_time=1)
+    analysis.getFFTFreq()
+    # print "sound,status hasSound,hasBlock,hasPlosive:",analysis.hasSound,analysis.hasBlock,analysis.hasPlosive
+    # print "0,getSoundAVGPower:", analysis.getSoundAVGPower(LR=True)
+    # print "0,getTotalAVGPower:", analysis.getTotalAVGPower(LR=True)
+    # print "0,getFFTPower:",analysis.getFFTPower(LR=True)
     #
     # waveFile = "eq_10khz_v100.wav"
     # analysis.analyWav(waveFile)

+ 82 - 23
ssat_sdk/sound/audio_recorder.py

@@ -21,7 +21,7 @@ class ARecorder():
         self.pyStream = None
         self.saveWav = False
         self.RecordStatus = 0#0停止录制,1正在录制
-        self.recordLock = threading.Condition()
+        self.recordLastTime = 0
         self.waveStatus = 0#0录制文件停止,1正在录制文件
         self.waveFile = ""
         self.frameQue = Queue.Queue()#存储录制的音频帧
@@ -37,6 +37,33 @@ class ARecorder():
         # RATE = 192000
         # FORMAT = pyaudio.paInt32
 
+        self.devName = ""
+
+    def setDeviceName(self,devName):
+        self.devName = devName
+
+    '''
+    查找设备index。
+    :return :None表示用默认设备,-1表示找不到指定设备,正整数表示找到的设备索引index。
+    '''
+    def getDeviceIndex(self):
+        if self.devName.__len__() < 1:
+            LoggingUtil.printLog("采用默认录音设备")
+            return None
+        else:
+            pa = pyaudio.PyAudio()
+            count = pa.get_device_count()
+            realName = getSoundDevice(self.devName)
+            print "getDeviceIndex,devName:%s, realName:%s"%(self.devName,realName)
+            for index in range(count):
+                soundDev = pa.get_device_info_by_index(index)
+                print "getDeviceIndex,soundDev:",soundDev
+                if soundDev.has_key("name") and soundDev["name"].find(realName) > -1:
+                    LoggingUtil.printLog("采用录音设备:"+realName)
+                    return index
+            LoggingUtil.printLog("未找到指定录音设备")
+            return -1
+
 
     '''
     查询录音设备状态
@@ -56,22 +83,30 @@ class ARecorder():
     录制wave文件,可以设定录制的时间,单位s。
     '''
     def recordWave(self, filePath, time, channels = 2):
-        print "Recording wave..."
+        print "recordWave,Start to record wave..."
 
         self.pyaudio = pyaudio.PyAudio()
+        # 获取设备数量;
+        # device_count = self.pyaudio.get_device_count()
+        # http://people.csail.mit.edu/hubert/pyaudio/docs/
+        devIndex = self.getDeviceIndex()
+        if devIndex == -1:
+            return
         self.pyStream = self.pyaudio.open(format=self.FORMAT,
                         channels=channels,
                         rate=self.RATE,
                         input=True,
                         output=False,
-                        frames_per_buffer=self.CHUNK*channels)
+                        frames_per_buffer=self.CHUNK*channels,
+                        input_device_index = devIndex,
+                        output_device_index = None)
 
-        print("* recording")
+        print("recordWave, recording")
         frames = []
         for i in range(0, int(self.RATE / self.CHUNK * time)):
             data = self.pyStream.read(self.CHUNK)
             frames.append(data)
-        print("* done")
+        print("recordWave, done")
 
         self.pyStream.stop_stream()
         self.pyStream.close()
@@ -86,7 +121,7 @@ class ARecorder():
 
 
     def monitor(self,timeL,saveWave = False, waveFile=None, buf_time=None):
-        self.recordLock.acquire()
+        print "audio_recorder,monitor start"
         if waveFile is not None:
             self.waveFile = waveFile
         else:
@@ -102,40 +137,53 @@ class ARecorder():
         audioEnable = False
         while tryMax > 0:#在音频设备启动失败时,尝试6次,每次间隔0.5秒
             tryMax -= 1
+            print "audio_recorder,tryMax:",tryMax
             try:
                 self.pyaudio = pyaudio.PyAudio()
-                LoggingUtil.printLog(TAG,self.pyaudio.get_default_input_device_info())
+                devIndex = self.getDeviceIndex()
+                if devIndex == -1:
+                    LoggingUtil.printLog(TAG, self.pyaudio.get_default_input_device_info())
+                    return
+                elif devIndex is None:
+                    LoggingUtil.printLog(TAG,self.pyaudio.get_default_input_device_info())
+                else:
+                    LoggingUtil.printLog(TAG, self.pyaudio.get_device_info_by_index(devIndex))
                 self.pyStream = self.pyaudio.open(rate=self.RATE,
                                       channels=self.CHANNELS,
                                       format=self.FORMAT,
                                       input=True,
                                       frames_per_buffer=self.CHUNK,
-                                      stream_callback=self.streamCallBack)
+                                      stream_callback=self.streamCallBack,
+                                      input_device_index = devIndex)
                 LoggingUtil.printLog("AudioRecorder monitor, device init success!")
                 audioEnable = True
                 break
             except Exception,e:
                 audioEnable = False
                 LoggingUtil.printLog("AudioRecorder monitor, Error:"+e.message)
-                if self.pyStream is not None:
-                    self.pyStream.close()
-                    self.pyStream = None
-                if self.pyaudio is not None:
-                    self.pyaudio.terminate()
-                    self.pyaudio = None
+                self.releaseRecord()
+                self.closeRecord()
                 time.sleep(0.5)
         if audioEnable is False:
             return
         self.initRecordStatus(saveWave)
         LoggingUtil.printLog("Record sound....")
+        self.recordLastTime = time.time()
         self.pyStream.start_stream()
-        self.recordLock.wait() #等待录音结束
-        self.recordLock.release()
+        while self.RecordStatus == 1:#等待录音结束
+            curTime = time.time()
+            dtime = curTime - self.recordLastTime
+            if buf_time is None and dtime > READ_BUF_TIME * 10:
+                self.releaseRecord()
+            elif  dtime > buf_time*10:
+                self.releaseRecord()
+        self.closeRecord()
         LoggingUtil.printLog("Record sound End")
 
     def streamCallBack(self, in_data,frame_count,time_info,status_flags):
         # print "streamCallBack.Thread:", threading.currentThread().getName()
         # print "Recording.streamCallBack:frame_count:",frame_count,";time_info:",time_info,";status_flags:",status_flags
+        self.recordLastTime = time.time()
         self.frameBufDel = self.frameBufDel - 1
         #舍弃前1秒钟的音频内容
         if self.frameBufDel > 0:
@@ -145,7 +193,7 @@ class ARecorder():
         if self.saveWav is True:
             self.saveWavFrame(in_data)
         if self.frameBufMax <= 0:
-            self.closeRecord()
+            self.releaseRecord()
             return (None, pyaudio.paComplete)
         else:
             return (None, pyaudio.paContinue)
@@ -156,14 +204,24 @@ class ARecorder():
         if self.saveWav is True:
             self.initWavSave()
 
-    def closeRecord(self):
+    def releaseRecord(self):
+        print "releaseRecord"
         self.RecordStatus = 0
-        self.recordLock.acquire()
-        self.recordLock.notify()
-        self.recordLock.release()
+
+    def closeRecord(self):
+        print "closeRecord"
         # self.pyStream.stop_stream()
-        self.pyStream.close()
-        self.pyaudio.terminate()
+        if self.pyStream is not None and self.pyStream.is_stopped():
+            # print "stop pyStream"
+            self.pyStream.stop_stream()
+        if self.pyStream is not None:
+            # print "close pyStream"
+            self.pyStream.close()
+            self.pyStream = None
+        if self.pyaudio is not None:
+            # print "close pyaudio"
+            self.pyaudio.terminate()
+            self.pyaudio = None
         if self.saveWav is True:
             self.closeWavSave()
 
@@ -190,6 +248,7 @@ class ARecorder():
 
     def closeWavSave(self):
         try:
+            print "close Wave file"
             self.wf.close()
             self.wf = None
             self.waveStatus = 0

+ 3 - 0
ssat_sdk/sound_init.py

@@ -1,6 +1,9 @@
 # -*- coding:utf-8 -*-
 from ssat_sdk.sound_tool import AudioIdentify
 
+'''
+用于SATHelper和SATService做声音相关初始化
+'''
 if __name__ == "__main__":
     audioIdentify = AudioIdentify()
     audioIdentify.setSoundLevel()

+ 128 - 15
ssat_sdk/sound_tool.py

@@ -13,9 +13,10 @@ import pyaudio
 # import matplotlib
 import math,random
 from ssat_sdk.sound.audio_recorder import ARecorder
+# from ssat_sdk.sound.audio_device import AudioDevice
 from ssat_sdk.sound.audio_recorder import READ_BUF_TIME
 from ssat_sdk.sound.audio_analysis import AAnalysis
-from ssat_sdk.sat_environment import getSATTmpDIR,getVoicecards
+from ssat_sdk.sat_environment import getSATTmpDIR,getVoicecards,writeSoundList
 
 import threading,thread
 import Queue
@@ -58,7 +59,7 @@ class AudioIdentify():
         self.deviceType = 0
         # self.audioChecker = Sound()
         self.audioChecker = None
-        self.audioRecorder = ""
+        self.audioFile = ""
         try:
             list = getVoicecards()
             print u'音频设备列表:',list
@@ -75,12 +76,21 @@ class AudioIdentify():
                     print u'初始化声音设备失败'
             else:
                 self.audioChecker = ARecorder()
-                print u'使用拾音器设备'
+                print u'使用拾音器/MIC设备'
         except Exception, e:
             self.audioChecker = ARecorder()
             print u"获取VoiceCards失败", e
 
-
+    '''
+    设置有声阈值.录制声音3秒钟,然后获取声音强度,写入resource_run.cfg文件:
+    [Sound]
+    sound_list = [2000, 2000]
+    '''
+    def setSoundLevel(self):
+        self.monitorSound(3)
+        soundPower = self.getSoundPower()
+        soundLevel = [int(soundPower),int(soundPower)]
+        writeSoundList(soundLevel)
 
     # 是否有声音,muteVoltage静音电压值
     # 废弃函数
@@ -152,8 +162,8 @@ class AudioIdentify():
     '''
     返回monitorSound过程中的声音平均强度,如果LR为True则返回左右声道的声音强度,Freq为频率范围,单位Hz。
     切割音频,进行最大强度累计,然后算平均值。
-    :param minFreq:频率范围中的最小频率值。
-    :param maxFreq:频率范围中的最大频率值。
+    :param minFreq:频率范围中的最小频率值。 作废
+    :param maxFreq:频率范围中的最大频率值。 作废
     :param LR:左右声道检测选项。True:左右声道分开检测;False:仅检测0声道,可能是左声道,有可能仅有的一个声道。
     :param audioFile: 传入新的音频文件,不用monitorSound的音频文件。
     :return: LR=True时,返回左右声道声音强度2个值,LR=False时,返回一个声道强度值。
@@ -162,6 +172,16 @@ class AudioIdentify():
         if audioFile.__len__() > 2:
             self.aAnalysis.analyWav(audioFile)
         return self.aAnalysis.getTotalAVGPower(LR=LR)
+    '''
+    返回monitorSound过程中的最大音量,如果LR为True,则返回左右声道的声音最大强度。
+    :param LR:左右声道检测选项。True:左右声道分开检测;False:仅检测0声道,可能是左声道,有可能仅有的一个声道。
+    :param audioFile: 传入新的音频文件,不用monitorSound的音频文件。
+    :return: LR=True时,返回左右声道声音强度2个值,LR=False时,返回一个声道强度值。
+    '''
+    def getMaxSoundPower(self, LR=False, audioFile=""):
+        if audioFile.__len__() > 2:
+            self.aAnalysis.analyWav(audioFile)
+        return self.aAnalysis.getTotalMaxPower(LR=LR)
 
     '''
     获取左右声道有无声的状态。
@@ -200,15 +220,16 @@ class AudioIdentify():
                                                self.audioChecker.WIDTH,
                                                buf_time)
             self.audioChecker.monitor(seconds,saveWave=True, waveFile=waveFile, buf_time=buf_time)
-            self.audioRecorder = self.audioChecker.waveFile
+            self.audioFile = self.audioChecker.waveFile
             time.sleep(1) #预留1秒钟,用于音频分析。
             self.aAnalysis.endFramesAnalysis()
 
             firstItem["audio"] = self.audioChecker.getWavFile()
-            ExpList.append(firstItem)
-            retExpList = self.changeMonSoundResult(ExpList)
+            firstItem["status"] = self.Sound_No
+            retExpList = self.changeMonSoundResult(firstItem)
             # print u'monitorSound end',retExpList
             MuTex.release()  # 解锁
+            ExpList.append(firstItem)
             print "monitorSound end:", seconds, retExpList
             return retExpList
         elif self.deviceType == 1:
@@ -232,7 +253,7 @@ class AudioIdentify():
     def monitorSoundTH(self,seconds,expList=None, waveFile = None, buf_time=READ_BUF_TIME):
         thread.start_new_thread(self.monitorSound, (seconds, expList, waveFile, buf_time,))
 
-    def changeMonSoundResult(self,ExpList):
+    def changeMonSoundResult(self,expFirstItem):
         self.isSound = self.aAnalysis.hasSound
         self.hasBlock = self.aAnalysis.hasBlock
         soundPower = self.getSoundPower()
@@ -240,14 +261,13 @@ class AudioIdentify():
         if soundPower - self.aAnalysis.TH_POWER >= 0:
             self.aAnalysis.hasSound = True
         if self.aAnalysis.hasSound is True and self.aAnalysis.hasBlock is True:
-            ExpList[0]["status"] = self.Sound_Break
+            expFirstItem["status"] = self.Sound_Break
         elif self.aAnalysis.hasSound is True and self.aAnalysis.hasBlock is False:
-            ExpList[0]["status"] = self.Sound_Has
+            expFirstItem["status"] = self.Sound_Has
         else:
-            ExpList[0]["status"] = self.Sound_No
+            expFirstItem["status"] = self.Sound_No
         retExpList = []
-        for item in ExpList:
-            retExpList.append(item)
+        retExpList.append(expFirstItem)
         # del ExpList #线程调用,回传参数,需要用到
         return retExpList
         # print "changeMonSoundResult:",ExpList
@@ -295,9 +315,102 @@ class AudioIdentify():
         wavfile.write(rightPath, sampleRate, np.array(right))
         return leftPath, rightPath
 
+    '''
+    对比两个声音模式的参数,判断是否为同一种声音模式。
+    传入的参数,mode1和mode2数组数据要对齐,按低频率到高频率幅度值排列。
+    建议采集幅度:200HZ、500HZ、1KHz、1.5KHz、2KHz、5KHz、10KHz
+    :param mode1,数组[200hz声音强度、500hz声音强度...]
+    :param mode2,数组[200hz声音强度、500hz声音强度...]
+    :return True/False, True代表一样,False代表不一样
+    '''
+    def cmpSoundMode(self, mode1, mode2, offset=2000):
+        freqD = 0
+        for index in range(mode1.__len__()):
+            if abs(mode1[index] - mode2[index]) > offset:
+                return False
+            elif abs(mode1[index] - mode2[index]) > 2*offset/3:
+                freqD += 2
+            elif abs(mode1[index] - mode2[index]) > offset/3:
+                freqD += 1
+        if freqD/mode1.__len__() > 1:
+            return False
+        else:
+            return True
+
+    '''
+    用于检测幅度不变单频音的稳定播放,支持左右两个声道不同单频音
+    在monitorSound后,根据记录下的音频文件,判断最大幅度的频率是否和目标频率吻合。
+    或者输入一个音频文件,分析音频文件,判断最大幅度的频率是否和目标频率吻合。
+    精确度:1秒为单位,进行频率检测。
+    :param stdFreq:目标频率,单位Hz
+    :param audioFile:录制的音频文件
+    :param offset:表示频率偏差允许范围
+    :return 声道1数值,声道2数值。1:频率完全吻合,0:频率部分吻合,有频率变化情况(可能是噪音);-1:频率完全不吻合
+    '''
+    def checkSingleFreq(self, stdFreq,audioFile="",offset=10):
+        #audioFile: D:\1.wav
+        ret1 = -1
+        ret2 = -1
+        if audioFile is not None and audioFile.find(".wav") > -1:
+            self.aAnalysis.analyWav(audioFile, buf_time=1)
+        else:
+            self.aAnalysis.analyWav(self.audioFile, buf_time=1)
+        # 记录每秒的最大幅度频率
+        freqList = self.aAnalysis.getFFTFreq(LR=True)
+        print "checkSingleFreq,freqList:",freqList.__len__(),freqList
+        if freqList is None:
+            return ret1, ret2
+        # 根据记录的频率列表,与stdFreq比对,返回结果。
+        if freqList.__len__() < 1:
+            return ret1.ret2
+        freqList1,freqList2 = [],[]
+        if freqList.__len__() >= 1:
+            freqList1 = freqList[0]
+        if freqList.__len__() >= 2:
+            freqList2 = freqList[0]
+        #计算声道1
+        meetNum = 0
+        for freq in freqList1:
+            if abs(freq - stdFreq) < offset:
+                meetNum += 1
+        if meetNum/freqList1.__len__() == 1:
+            ret1 = 1
+        elif meetNum/freqList1.__len__() > 0:
+            ret1 = 0
+        elif meetNum/freqList1.__len__() == 0:
+            ret1 = -1
+        #计算声道2
+        meetNum = 0
+        for freq in freqList2:
+            if abs(freq - stdFreq) < offset:
+                meetNum += 1
+        if meetNum/freqList2.__len__() == 1:
+            ret2 = 1
+        elif meetNum/freqList2.__len__() > 0:
+            ret2 = 0
+        elif meetNum/freqList2.__len__() == 0:
+            ret2 = -1
+        return ret1,ret2
+
+    '''
+    设置录音设备:
+    input_front_mic = Realtek High Definition
+    input_back_mic = HD Webcam C270
+    :param devName,传入自定义设备名字,例如:input_front_mic
+    '''
+    def setInputSoundDev(self, devName):
+        self.audioChecker.setDeviceName(devName)
+
 if __name__ == "__main__":
+    audioDevice = AudioDevice()
+    audioDevice.setDevice(2)
     aIden = AudioIdentify()
+    # aIden.setInputSoundDev("input_front_mic")
+    # aIden.setInputSoundDev("input_back_mic")
     aIden.monitorSound(5)
+    # print "cmpSoundMode:", aIden.cmpSoundMode([3000,6000,34000,40000],[3400,5000,30000,40000])
+
+    # aIden.monitorSound(5)
     time.sleep(1)
     # sound1 = "D:/1.wav"
     # sound2 = "D:/2.wav"

+ 22 - 1
ssat_sdk/tv_detect.py

@@ -125,6 +125,9 @@ class TvDetect():
         # print scale_left,scale_right,scale_up,scale_down
         return scale_left,scale_right,scale_up,scale_down
 
+    '''
+    寻找图片白色条相交中心点,例如:重现率图片
+    '''
     def findCenterPoint(self, img):
         h1,w1 = img.shape
         center_x1 = 4 * w1 / 10
@@ -159,6 +162,24 @@ class TvDetect():
                 break
         print "center_width:", center_width
         return center_height, center_width
+
+    '''
+    判断是否在重显率画面
+    :param picPath:图片路径
+    :return True/False:True:是重显率画面,False:不是重显率
+    '''
+    def isOverScan(self, picPath):
+        print "isOverScan,picPath:",picPath
+        srcImg = cv.imread(picPath)
+        height,widht,dim = srcImg.shape
+        grayImg = cv.cvtColor(srcImg,cv.COLOR_BGR2GRAY)
+        center_height, center_width = self.findCenterPoint(grayImg)
+        dHeight = center_height*2.0/height
+        dWidth = center_width*2.0/widht
+        if 0.98 < dHeight < 1.02 and 0.98 < dWidth < 1.02:
+            return True
+        else:
+            return False
     '''
     根据指定的直线,扫描刻度线条。去掉刻度线条宽度的影响,同时刻度线条和背景灰阶阈值,采用像素点平均值。
     :param img:待检测图片。22293 pattern
@@ -618,7 +639,7 @@ class TvDetect():
         #检测声音
         if checkAudio is True:
             self.audioChecker.startCHK(5)
-            soundList.append(self.audioChecker.audioRecorder)
+            soundList.append(self.audioChecker.audioFile)
             return not self.audioChecker.isOK(), picList, soundList
         else:
             return True, picList, soundList

+ 7 - 10
ssat_sdk/tv_operator.py

@@ -12,6 +12,8 @@ from ssat_sdk.device_manage import RedRatHub4
 from ssat_sdk.uiautomator2.adb_keyevent import AdbKeyevent
 from ssat_sdk.utils import string_util
 from ssat_sdk.sat_environment import getRCUDeviceSelected
+# 导入测试精灵;
+from ssat_sdk.device_manage.testWizardClient import TestWizardClient
 
 Operator_Device = "redrat2"
 
@@ -25,22 +27,15 @@ class TvOperator():
         if "redrat" == rcuDevice:
             self.operator = Remote()
             LoggingUtil.printLog(u"初始化TvOperator(深圳redrat3)")
-        elif "redrat3" == rcuDevice:
-            # self.operator = RedRatHub3.RedRat3()
-            # self.operator = RedRatHub3.redrat3_singleton
-            if isReStart:
-                print u"重启红老鼠连接"
-                self.operator = RedRatHub3.RedRat3()
-            else:
-                self.operator = RedRatHub3.redrat3_singleton
-            LoggingUtil.printLog(u"初始化TvOperator(redrat3)")
-        elif "redrat4" == rcuDevice:
+        elif "redrat3" == rcuDevice or "redrat4" == rcuDevice:
             if isReStart:
                 print u"重启红老鼠连接"
                 self.operator = RedRatHub4.RedRat4()
             else:
                 self.operator = RedRatHub4.redrat4_singleton
             LoggingUtil.printLog(u"初始化TvOperator(redrat3)")
+        elif "tw" == rcuDevice:
+            self.operator = TestWizardClient()
         self.adbEvent = AdbKeyevent()
         # 创建视频采集对象
         self.ccard = CCardManager()
@@ -59,6 +54,8 @@ class TvOperator():
             LoggingUtil.printLog(u"初始化TvOperator(redrat3)")
         elif string_util.strcmp("adb", deviceType):
             self.operator = self.adbEvent
+        elif string_util.strcmp("tw", deviceType):
+            self.operator = TestWizardClient()
 
     def sendKey(self, keyName, times=1, duration=1):
         try:

+ 5 - 1
ssat_sdk/uiautomator2/session.py

@@ -750,7 +750,11 @@ class UiObject(object):
         UiObjectNotFoundError, delay=.5, tries=3, jitter=0.1, logger=logging)
     def info(self):
         '''ui object info.'''
-        return self.jsonrpc.objInfo(self.selector)
+        try:
+            return self.jsonrpc.objInfo(self.selector)
+        except Exception,e:
+            print("error:info is none")
+            return None
 
     @_failprompt
     def click(self, timeout=None, offset=None):

+ 8 - 1
ssat_sdk/utils/xml_util.py

@@ -56,12 +56,19 @@ def saveAllResult(task, destxml):
     doc.writexml(xmlfile, addindent=' ' * 4, newl='\n', encoding="utf-8")
     xmlfile.close()
 
-def saveDetailResult(destxml, results):
+'''
+<?xml version="1.0" encoding="utf-8"?>
+<?xml-stylesheet type="text/xsl" href="case_result_detail.xsl"?>
+<results caseFinish="0">
+设置caseFinish状态:0 代表脚本未结束;1 代表脚本结束
+'''
+def saveDetailResult(destxml, results, caseFinish=0):
     print "saveDetailResult"
     resultDom = xml.dom.getDOMImplementation()
     doc = resultDom.createDocument(None, Item.NODE_RESULTS, None)
     xsl = doc.createProcessingInstruction('xml-stylesheet', 'type="text/xsl" href="case_result_detail.xsl"')
     root = doc.documentElement
+    root.setAttribute("caseFinish",str(caseFinish))
     doc.insertBefore(xsl, root)
 
     # 每条子项测试的结果