| # -*- coding: utf-8 -*- |
| # |
| # Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| |
| # DESCRIPTION : |
| # |
| # Intended for use during manufacturing to validate Developer mode |
| # switch and Recovery button function properly. This program will |
| # display an image of the d-housing with Developer switch and Recovery |
| # button. Operator will then be instructed via text and visually to |
| # switch and restore the Developer switch and press/release the |
| # Recovery button. Success at each step resets a 20 second countdown timer. |
| |
| |
| import cairo |
| import gobject |
| import gtk |
| import sys |
| import time |
| import os |
| import math |
| |
| from autotest_lib.client.bin import test |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib import utils |
| |
| from autotest_lib.client.cros import gpio |
| from autotest_lib.client.cros import factory_setup_modules |
| from cros.factory.test import factory |
| from cros.factory.test import ui as ful |
| |
| class DevRecTest(object): |
| gpio_info = { |
| # Note, names are NOT arbitrary. Will need to change gpio_setup before |
| # modifying |
| 'developer_switch' : {'type' : 'switch', |
| 'cx' : 475, |
| 'cy' : 375, |
| 'size' : 30, |
| 'arrow' : {'x' : 425, |
| 'y' : 375, |
| 'width' : 15, |
| 'length' : 100, |
| # in degrees starts as rt arrow |
| 'direction' : 0, |
| }, |
| }, |
| 'recovery_button' : {'type' : 'button', |
| 'cx' : 475, |
| 'cy' : 375, |
| 'size' : 30, |
| 'arrow' : {'x' : 420, |
| 'y' : 375, |
| 'width' : 15, |
| 'length' : 100, |
| 'direction' : 270, |
| } |
| }, |
| } |
| |
| # How long DevRecTest allows in seconds until failing |
| timeout = 90 |
| # How long to display the success message in seconds before exit. |
| pass_msg_timeout = 2 |
| |
| # Background color and alpha for various states |
| rgba_state = [(0.0, 1.0, 0.0, 0.9), |
| (0.9, 0.9, 0.0, 0.6), |
| (0.9, 0.0, 0.0, 0.6)] |
| |
| def __init__(self, devrec_image, gpio): |
| self._devrec_image = devrec_image |
| self._successful = set() |
| self._deadline = None |
| self._success = None |
| self._error_message = 'Test was unsuccessful' |
| self.gpios = DevRecGpio(gpio) |
| |
| def show_arrow(self, context, cx, cy, headx, heady, awidth, length, |
| degrees): |
| '''Draw a simple arrow in given context. |
| ''' |
| |
| context.save() |
| |
| # rotation transform |
| matrix = cairo.Matrix(1, 0, 0, 1, 0, 0) |
| context.set_source_rgba(0, 0, 0, 1) |
| cairo.Matrix.translate(matrix, cx, cy) |
| cairo.Matrix.rotate(matrix, math.radians(degrees)) |
| cairo.Matrix.translate(matrix, -cx, -cy) |
| context.transform(matrix) |
| |
| # right arrow default |
| context.set_line_width(5) |
| context.move_to(headx, heady) |
| context.rel_line_to(-awidth, -awidth/2.0) |
| context.rel_line_to(0, awidth) |
| context.rel_line_to(awidth, -awidth/2.0) |
| context.fill_preserve() |
| context.rel_line_to(-length, 0) |
| context.stroke_preserve() |
| context.set_source_rgba(0, 0, 0, 0.5) |
| context.stroke_preserve() |
| context.restore() |
| |
| def start_countdown(self, duration): |
| self._deadline = int(time.time()) + duration |
| |
| def request_action(self, widget, context, name): |
| '''Determine action required by gpio state and show |
| ''' |
| |
| gpio_default = self.gpios.gpio_default(name) |
| gpio_state = self.gpios.gpio_state(name) |
| gpio_val = self.gpios.gpio_read(name) |
| |
| # state transitions based on current value |
| if (gpio_state == 2) and (gpio_val != gpio_default): |
| gpio_state-=1 |
| # refresh countdown |
| self.start_countdown(self.timeout) |
| elif (gpio_state == 1) and (gpio_val == gpio_default): |
| gpio_state-=1 |
| self._successful.add(name) |
| |
| # store state change |
| self.gpios.table[name][1] = gpio_state |
| |
| widget_width, widget_height = widget.get_size_request() |
| context.save() |
| ginfo = self.gpio_info[name] |
| |
| context.set_source_rgba(0, 0, 0, 1) |
| |
| if (ginfo['type'] == 'button'): |
| text = ['Done', 'Release', 'Press'] |
| context.arc(ginfo['cx'], ginfo['cy'], ginfo['size'], |
| 0, math.radians(360)) |
| context.stroke() |
| context.arc(ginfo['cx'], ginfo['cy'], ginfo['size'], |
| 0, math.radians(360)) |
| elif (ginfo['type'] == 'switch'): |
| text = ['Done', 'Restore', 'Move'] |
| # two rects one outline of switch body the other |
| # representing the position |
| rect_x = ginfo['cx'] - ginfo['size'] |
| rect_y = ginfo['cy'] - ginfo['size'] / 2.0 |
| context.rectangle(rect_x, rect_y, ginfo['size'] * 2, |
| ginfo['size']) |
| context.stroke() |
| |
| if gpio_state == 1: |
| rect_x = rect_x + ginfo['size'] |
| context.rectangle(rect_x, rect_y, ginfo['size'], |
| ginfo['size']) |
| else: |
| raise |
| |
| context.set_source_rgba(*self.rgba_state[gpio_state]) |
| context.fill() |
| |
| if ginfo['arrow'] is not None: |
| arrow_x = ginfo['arrow']['x'] |
| arrow_y = ginfo['arrow']['y'] |
| arrow_l = ginfo['arrow']['length'] |
| arrow_w = ginfo['arrow']['width'] |
| |
| arrow_dir = ginfo['arrow']['direction'] |
| if (gpio_state == 1) and (ginfo['type'] == 'switch'): |
| arrow_dir =+ 180 |
| |
| self.show_arrow(context, ginfo['cx'], ginfo['cy'], |
| arrow_x, arrow_y, arrow_w, arrow_l, arrow_dir) |
| |
| context.scale(widget_width / 1.0, widget_height / 1.0) |
| context.set_source_rgba(0.1, 0.1, 0.1, 0.95) |
| context.select_font_face( |
| 'Verdana', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL) |
| context.set_font_size(.05) |
| |
| if gpio_state > 0 and self._deadline: |
| dtext = "%s %s now [ %d ]" % \ |
| (text[gpio_state], name, (self._deadline - int(time.time()))) |
| else: |
| dtext = "%s with %s" % (text[gpio_state], name) |
| |
| x_bearing, y_bearing, width, height = context.text_extents(dtext)[:4] |
| context.move_to(0.5 - (width / 2) - x_bearing, |
| 0.5 - (height / 2) - y_bearing) |
| context.show_text(dtext) |
| |
| dtext = "Press Q to fail." |
| x_bearing, y_bearing, width, height = context.text_extents(dtext)[:4] |
| context.move_to(0.08 - x_bearing, |
| 0.9 - (height / 2) - y_bearing) |
| context.show_text(dtext) |
| |
| context.restore() |
| return True |
| |
| def time_expired(self): |
| if self._deadline is None: |
| return None |
| if self._deadline is not None and self._deadline <= time.time(): |
| return True |
| else: |
| return False |
| |
| def expose_event(self, widget, event): |
| context = widget.window.cairo_create() |
| |
| context.set_source_surface(self._devrec_image, 0, 0) |
| context.paint() |
| |
| if self.time_expired() or self.gpios.cur_gpio() in self._successful: |
| self.gpios.next_gpio() |
| self.start_countdown(self.timeout) |
| |
| if self.gpios.cur_gpio(): |
| self.request_action(widget, context, self.gpios.cur_gpio()) |
| elif self.gpios.num_gpios == len(self._successful): |
| self._success = True |
| |
| return True |
| |
| def timer_event(self, window): |
| if self._success: |
| gtk.main_quit() |
| if not self._deadline: |
| # Ignore timer events with no countdown in progress. |
| return True |
| if self.time_expired(): |
| if self._success is None: |
| self._success = False |
| self._error_message = ('Timeout occured before test pass ' + |
| '(%s)' % self.gpios.cur_gpio()) |
| gtk.main_quit() |
| |
| window.queue_draw() |
| return True |
| |
| def key_release_callback(self, widget, event): |
| if event.keyval == ord('Q') or event.keyval == ord('q'): |
| self._success = False |
| self._error_message = ('User exited before test pass ' + |
| '(%s)' % self.gpios.cur_gpio()) |
| gtk.main_quit() |
| |
| return True |
| |
| def register_callbacks(self, window): |
| window.connect('key-release-event', self.key_release_callback) |
| window.add_events(gtk.gdk.KEY_RELEASE_MASK) |
| |
| |
| class DevRecGpio: |
| ''' |
| Borrowed from site_tests/hardware_GPIOSwitches. |
| ''' |
| |
| def __init__(self, gpio): |
| self._gpio = gpio |
| self._cur_gpio = None |
| self._gpio_list = [] |
| self.table = None |
| self.cfg() |
| |
| def cfg(self): |
| # The GPIO values should be interpreted by kernel driver so gpio_read |
| # is always returning "logical" values. For example, developer_switch |
| # should always be "0" for "disabled" and "1" for "enabled", no matter |
| # whether the real PCB is made pull-high or pull-low. |
| self.table = { |
| # name : [<default>, <state>] |
| # <default> == 0 || 1 |
| # <state> == number counts down 0 |
| 'developer_switch': [1, 2], |
| 'recovery_button': [0, 2], |
| } |
| |
| self._gpio_list = self.table.keys() |
| self._gpio_list.reverse() |
| self.num_gpios = len(self._gpio_list) |
| |
| def gpio_read(self, name): |
| if name not in self.table: |
| raise error.TestNAError( |
| 'Unable to locate definition for gpio %s\n' |
| '测试程式找不到 gpio %s' % (name, name)) |
| try: |
| return self._gpio.read(name) |
| except: |
| raise error.TestError( |
| 'Unable to read gpio value "%s"\n' |
| '测试程式无法读取 gpio 数值 "%s"' % (name, name)) |
| |
| def cur_gpio(self): |
| if self._cur_gpio is None: |
| self._cur_gpio = self.next_gpio() |
| return self._cur_gpio |
| |
| def next_gpio(self): |
| if len(self._gpio_list): |
| self._cur_gpio = self._gpio_list.pop() |
| else: |
| self._cur_gpio = False |
| return self.cur_gpio() |
| |
| def gpio_default(self, name): |
| return self.table[name][0] |
| |
| def gpio_state(self, name): |
| return self.table[name][1] |
| |
| |
| class factory_DeveloperRecovery(test.test): |
| version = 2 |
| preserve_srcdir = True |
| |
| def initialize(self): |
| self._gpio = gpio.Gpio(error.TestError) |
| self._gpio.setup() |
| |
| def run_once(self, layout=None): |
| |
| factory.log('%s run_once' % self.__class__) |
| |
| os.chdir(self.srcdir) |
| dr_image = cairo.ImageSurface.create_from_png('%s.png' % layout) |
| image_size = (dr_image.get_width(), dr_image.get_height()) |
| |
| test = DevRecTest(dr_image, self._gpio) |
| |
| drawing_area = gtk.DrawingArea() |
| drawing_area.set_size_request(*image_size) |
| drawing_area.connect('expose_event', test.expose_event) |
| drawing_area.add_events(gtk.gdk.EXPOSURE_MASK) |
| gobject.timeout_add(200, test.timer_event, drawing_area) |
| |
| test.start_countdown(test.timeout) |
| |
| ful.run_test_widget(self.job, drawing_area, |
| window_registration_callback=test.register_callbacks) |
| |
| if not test._success: |
| raise error.TestFail(test._error_message) |
| |
| factory.log('%s run_once finished' % self.__class__) |