blob: d86b8e581287898e5f8273210392c1061c9c5c98 [file] [log] [blame]
# -*- coding: utf-8 -*-
# Copyright (c) 2013 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.
"""A factory test for ambient light sensor.
Tests that ambient light sensor reacts to both darkening by covering w/finger
as well as brightening.
Roughly speaking:
- indoor ambient lighting: 20-100
- sunlight direct: 30k-60k
- flashlight direct: 5k-10k
"""
from __future__ import print_function
import logging
import math
import os
import time
import unittest
import factory_common # pylint: disable=unused-import
from cros.factory.test import countdown_timer
from cros.factory.test import test_ui
from cros.factory.utils.arg_utils import Arg
from cros.factory.utils import process_utils
_DEFAULT_SUBTEST_LIST = ['Light sensor dark',
'Light sensor exact',
'Light sensor light']
_DEFAULT_SUBTEST_CFG = {'Light sensor dark': {'below': 4},
'Light sensor exact': {'between': (10, 15)},
'Light sensor light': {'above': 200}}
_DEFAULT_SUBTEST_INSTRUCTION = {
'Light sensor dark': 'Cover light sensor with finger',
'Light sensor exact': 'Remove finger from light sensor',
'Light sensor light': 'Shine light sensor with flashlight'}
_DEFAULT_DEVICE_PATH = '/sys/bus/iio/devices/iio:device0/'
_DEFAULT_DEVICE_INPUT = 'illuminance0_input'
_MSG_PROMPT_FMT = """
Use indicated light source to pass each subtest<br>
Hit "space" to begin...<br><br>
"""
_ID_CONTAINER = 'light-sensor-container'
_ID_COUNTDOWN_TIMER = 'light-sensor-timer'
_CSS_LIGHT_SENSOR_TEST = """
#light-sensor-timer { font-size: 1.5em; color: red; }
.light-sensor-info { font-size: 1.5em; }
.light-sensor-desc {font-size: 1.5em; color: green; }
"""
class iio_generic(object):
"""Object to interface to ambient light sensor over iio.
Properties:
self._rd : the device file path
self._init_cmd : command to initial device file
self._min : minimum value of device output
self._max : maximum value of device output
self._mindelay : delay between each read action
"""
def __init__(self, device_path, device_input, range_value, init_cmd):
"""Initial light sensor object.
Args:
device_path: light sensor device path
device_input: file exports light sensor value
range_value: reference device_path/range_available file to
set one of valid value (1000, 4000, 16000, 64000).
None means no value is set.
init_cmd: initial command to setup light sensor device
"""
# initial values
self._rd = device_path + device_input
self._range_setting = device_path + 'range'
self._init_cmd = init_cmd
self._min = 0
self._max = math.pow(2, 16)
self._mindelay = 0.178
if not os.path.isfile(self._rd):
self.Config()
if range_value is not None:
if range_value not in (1000, 4000, 16000, 64000):
raise ValueError('Range value is invalid: %d' % range_value)
with open(self._range_setting, 'w') as f:
f.write('%d\n' % range_value)
ambient = self.Read('mean', delay=0, samples=10)
logging.info('ambient light sensor = %d', ambient)
def Config(self):
"""Creates device node if device does not exist."""
if self._init_cmd:
process_utils.Spawn(self._init_cmd, check_call=True)
if not os.path.isfile(self._rd):
raise ValueError('Cannot create %s' % self._rd)
val = self.Read('first', samples=1)
if val <= self._min or val >= self._max:
raise ValueError('Failed initial read')
def Read(self, param, delay=None, samples=1):
"""Reads the light sensor and return value based on param
Args:
param: string describing type of value to return. Valid
strings are 'mean' | 'min' | 'max' | 'raw' | 'first'
delay: delay between samples in seconds. 0 means as fast as possible
samples: total samples to read. O means infinite
Returns:
The value of light sensor
Raises:
ValueError if param is invalid.
"""
count = 0
buffers = []
if delay is None:
delay = self._mindelay
while True:
try:
with open(self._rd, 'r') as f:
value = int(f.readline().rstrip())
except IOError:
continue
else:
f.close()
buffers.append(value)
count += 1
time.sleep(delay)
if count == samples:
break
if param is 'mean':
return sum(buffers) / len(buffers)
elif param is 'max':
return max(buffers)
elif param is 'min':
return min(buffers)
elif param is 'raw':
return buffers
elif param is 'first':
return buffers[0]
else:
raise ValueError('Illegal value %s for type' % type)
class LightSensorTest(unittest.TestCase):
"""Tests light sensor."""
ARGS = [
Arg('device_path', str, 'device path', _DEFAULT_DEVICE_PATH),
Arg('device_input', str, 'device input file', _DEFAULT_DEVICE_INPUT),
Arg('timeout_per_subtest', int, 'timeout for each subtest', 10),
Arg('subtest_list', list, 'subtest list', optional=True),
Arg('subtest_cfg', dict, 'subtest configuration', optional=True),
Arg('subtest_instruction', dict, 'subtest instruction', optional=True),
Arg('check_per_subtest', int, 'check times for each subtest', 3),
Arg('init_command', list, 'Setup device command', optional=True),
# Special parameter for ISL 29018 light sensor
Arg('range_value', int, 'one of value (1000, 4000, 16000, 64000)',
optional=True),
]
def setUp(self):
# Initialize frontend presentation
self.ui = test_ui.UI()
try:
self._als = iio_generic(self.args.device_path, self.args.device_input,
self.args.range_value, self.args.init_command)
except ValueError as e:
self.ui.Fail(e)
return
self.ui.AppendCSS(_CSS_LIGHT_SENSOR_TEST)
self.ui.BindKey(test_ui.SPACE_KEY, self.StartCountDown, once=True)
self.ui.SetHTML(_MSG_PROMPT_FMT, id=_ID_CONTAINER)
# Initialize variables
self._subtest_list = _DEFAULT_SUBTEST_LIST
self._subtest_cfg = _DEFAULT_SUBTEST_CFG
self._subtest_instruction = _DEFAULT_SUBTEST_INSTRUCTION
self.GetSubtest(self.args.subtest_list, self.args.subtest_cfg,
self.args.subtest_instruction)
self._timeout_per_subtest = self.args.timeout_per_subtest
self._iter_req_per_subtest = self.args.check_per_subtest
self._current_iter_remained = self._iter_req_per_subtest
self._cumulative_val = 0
self._tested = 0
self._started = False
self._active_subtest = self._subtest_list[0]
test = 0
for name in self._subtest_list:
self.ui.SetHTML(self._subtest_instruction[name], id='title%d' % test)
desc = '%s (%s)' % (
name, self.GetConfigDescription(self._subtest_cfg[name]))
self.ui.SetHTML(desc, id='desc%d' % test)
self.ui.SetHTML(' : UNTESTED', id='result%d' % test)
test += 1
process_utils.StartDaemonThread(target=self.MonitorSensor)
def StartCountDown(self, event):
del event # Unused.
self._started = True
self._active_subtest = self._subtest_list[0]
self.ui.SetHTML(' : ACTIVE', id='result%d' % self._tested)
countdown_timer.StartCountdownTimer(
self._timeout_per_subtest * len(self._subtest_list),
self.TimeoutHandler, self.ui, _ID_COUNTDOWN_TIMER)
def NextSubtest(self):
self._tested += 1
if self._tested >= len(self._subtest_list):
self.ui.Pass()
return False
self._active_subtest = self._subtest_list[self._tested]
self.ui.SetHTML(' : ACTIVE', id='result%d' % self._tested)
self._current_iter_remained = self._iter_req_per_subtest
self._cumulative_val = 0
return True
def TimeoutHandler(self):
self.ui.SetHTML(' : FAILED', id='result%d' % self._tested)
self.ui.Fail('Timeout on subtest "%s"' % self._active_subtest)
def PassOneIter(self, name):
self._current_iter_remained -= 1
if self._current_iter_remained is 0:
self.ui.SetHTML(' : PASSED', id='result%d' % self._tested)
self._current_iter_remained = self._iter_req_per_subtest
mean_val = self._cumulative_val / self._iter_req_per_subtest
logging.info('Passed subtest "%s" with mean value %d.', name, mean_val)
if not self.NextSubtest():
return
def CheckSensorEvent(self):
val = self._als.Read('mean', samples=5, delay=0)
if self._started:
name = self._active_subtest
cfg = self._subtest_cfg[name]
passed = False
if 'above' in cfg:
if val > cfg['above']:
logging.info('Passed checking "above" %d > %d',
val, cfg['above'])
passed = True
elif 'below' in cfg:
if val < cfg['below']:
logging.info('Passed checking "below" %d < %d',
val, cfg['below'])
passed = True
elif 'between' in cfg:
lb, ub = cfg['between']
if val > lb and val < ub:
logging.info('Passed checking "between" %d < %d < %d',
lb, val, ub)
passed = True
if passed:
self._cumulative_val += val
self.PassOneIter(name)
else:
if self._current_iter_remained != self._iter_req_per_subtest:
logging.info('Resetting iter count.')
self._cumulative_val = 0
self._current_iter_remained = self._iter_req_per_subtest
self.ui.SetHTML('Input: %d' % val, id='sensor_input')
return True
def GetConfigDescription(self, cfg):
if 'above' in cfg:
return 'Input > %d' % cfg['above']
elif 'below' in cfg:
return 'Input < %d' % cfg['below']
elif 'between' in cfg:
return '%d < Input < %d' % cfg['between']
else:
raise ValueError('Unknown type in subtest configuration')
def GetSubtest(self, subtest_list, subtest_cfg, subtest_instruction):
has_specified = (subtest_list or subtest_cfg or subtest_instruction)
all_specified = (subtest_list and subtest_cfg and subtest_instruction)
if has_specified and not all_specified:
raise ValueError('Missing parameters of subtests.')
if all_specified:
self._subtest_list = subtest_list
self._subtest_cfg = subtest_cfg
self._subtest_instruction = subtest_instruction
def MonitorSensor(self):
while True:
self.CheckSensorEvent()
time.sleep(0.6)
def runTest(self):
self.ui.Run()