Add Swipe Test
Adds the Swipe test case, including configuration controls,
gesture grid generation, issuing robot commands, and saving results to
sqlite database.
BUG=b:148627899
TEST=Run test using TnT UI, selecting different configuration options.
Test grid visualization is correct, robot performs gestures correctly,
and results are saved properly in sqlite database.
Change-Id: I49f7561dcb4e96fd145228a7c07bdd3e6c321f62
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/third_party/optofidelity_TPPT/+/2102951
Tested-by: Sean O'Brien <seobrien@chromium.org>
Reviewed-by: Harry Cutts <hcutts@chromium.org>
diff --git a/MeasurementDB.py b/MeasurementDB.py
index 451b830..2d33b4a 100644
--- a/MeasurementDB.py
+++ b/MeasurementDB.py
@@ -142,6 +142,7 @@
session.add( TestType( 17, 'One Finger Tapping Repeatability Test'))
session.add( TestType( 18, 'Finger Tracking Test'))
session.add( TestType( 19, 'Finger To Edge Test'))
+ session.add( TestType( 20, 'Swipe Test'))
session.commit()
session.close()
except:
diff --git a/TPPTcommon/ConfigurationDatabase.py b/TPPTcommon/ConfigurationDatabase.py
index dd1349c..2f1b4e2 100644
--- a/TPPTcommon/ConfigurationDatabase.py
+++ b/TPPTcommon/ConfigurationDatabase.py
@@ -315,6 +315,9 @@
_finger_to_edge_configurations = \
relationship('FingerToEdgeTestParameters', back_populates='_test_configuration_orm')
+ _swipe_configurations = \
+ relationship('SwipeTestParameters', back_populates='_test_configuration_orm')
+
__table_args__ = (UniqueConstraint('name', 'configuration_group', name='_name_group_uc'),)
diff --git a/testcases/Swipe.py b/testcases/Swipe.py
new file mode 100644
index 0000000..5fae394
--- /dev/null
+++ b/testcases/Swipe.py
@@ -0,0 +1,242 @@
+"""
+Copyright (c) 2020, OptoFidelity OY
+Copyright (c) 2020, The Chromium OS Authors
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by the OptoFidelity OY.
+ 4. Neither the name of the OptoFidelity OY nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+"""
+import logging
+
+from sqlalchemy import Column, Integer, Boolean, Float, ForeignKey, VARCHAR
+from sqlalchemy.orm import backref, relation, relationship
+
+import MeasurementDB as mDB
+from TPPTcommon.ConfigurationDatabase import ConfigurationDatabase
+from TPPTcommon.exceptions import GridCreationError, TipSelectionError
+from TPPTcommon.grid import (create_multi_swipe_horizontal, create_multi_swipe_vertical,
+ create_multi_swipe_diagonal, GridVisContainer)
+from TPPTcommon.TestStep import TestStep
+
+logger = logging.getLogger(__name__)
+
+# Database table name for the test case.
+DB_TEST_TABLE_NAME = 'swipe_test'
+
+# Database table name for the test results.
+DB_RESULTS_TABLE_NAME = 'swipe_results'
+
+# Database table indices associated with test case.
+DB_TABLE_INDICES = [(DB_TEST_TABLE_NAME, 'test_id'), (DB_RESULTS_TABLE_NAME, 'gesture_id')]
+
+
+class SwipeTest(mDB.Base):
+ __tablename__ = DB_TEST_TABLE_NAME
+
+ id = Column(Integer, primary_key=True)
+ test_id = Column(Integer, ForeignKey('test_item.id', ondelete='CASCADE'), nullable=False)
+ test = relation(mDB.TestItem, backref=backref(DB_TEST_TABLE_NAME, order_by=id))
+
+ # For multi-finger lines, these are the coordinates of the primary finger (left-most with
+ # zero azimuth)
+ start_x = Column(Float)
+ start_y = Column(Float)
+ end_x = Column(Float)
+ end_y = Column(Float)
+
+ num_fingers = Column(Integer)
+ finger_size = Column(Float)
+ swipe_speed = Column(Float)
+ separation = Column(Float)
+ azimuth = Column(Float)
+
+
+class SwipeResults(mDB.Base):
+ __tablename__ = DB_RESULTS_TABLE_NAME
+
+ id = Column(Integer, primary_key=True)
+ gesture_id = Column(Integer, ForeignKey(DB_TEST_TABLE_NAME + '.id', ondelete='CASCADE'), nullable=False)
+ line = relation(SwipeTest, backref=backref(DB_RESULTS_TABLE_NAME, order_by=id))
+
+ panel_x = Column(Float)
+ panel_y = Column(Float)
+ pressure = Column(Float)
+ finger_id = Column(Integer)
+ time = Column(Float)
+ event = Column(Integer)
+
+
+class Swipe(TestStep):
+ """
+ In this test case we perform fast swipes across the DUT using one or two fingers, which approach the touch
+ surface while already in motion.
+ """
+
+ def __init__(self, context):
+ super().__init__('Swipe')
+
+ self.context = context
+
+ # If database_configuration is defined, the controls parameters will be overridden from database if possible
+ self.database_configuration = SwipeTestParameters
+
+ sizes = sorted(list(context.tips_node.single_tips_by_size.keys()), key=float)
+
+ self.controls.finger_size = ""
+ self.controls.info['finger_size'] = {'label': 'Finger size (mm)', 'items': sizes}
+
+ self.controls.num_fingers = 1
+ self.controls.info['num_fingers'] = {'label': 'Number of Fingers', 'min': 1, 'max': 2}
+
+ self.speed = 300.0
+
+ def execute(self):
+ dut = self.context.get_active_dut()
+
+ base_distance = dut.base_distance
+ separation = self.context.get_min_separation(float(self.controls.finger_size))
+
+ self.context.html("Running Swipe test for dut:%s" % dut)
+
+ test_item = self.context.create_db_test_item("Swipe Test")
+
+ measurement_lines = self._create_grid(dut)
+
+ for index, line in enumerate(measurement_lines):
+ self.context.indicators.set_test_detail('Line', str(index + 1) + ' / ' + str(len(measurement_lines)))
+
+ self.context.set_robot_default_speed()
+ dut.jump(line.start_x, line.start_y, base_distance, base_distance)
+
+ self.context.set_robot_speed(self.speed)
+
+ continuous_measurement = self.context.create_continuous_measurement(line)
+ continuous_measurement.start()
+
+ self.context.robot.set_finger_separation(separation)
+ dut.swipe(line.start_x, line.start_y, line.end_x, line.end_y, clearance=-2, radius=20,
+ azimuth1=line.angle, azimuth2=line.angle)
+
+ continuous_measurement.end()
+
+ touch_list = continuous_measurement.parse_data()
+
+ gesture_id = self._create_gesture(line, test_item, separation)
+
+ self._save_measurement_data(gesture_id, touch_list)
+
+ self.context.breakpoint()
+
+ self.context.close_db_test_item(test_item)
+
+ def _create_grid(self, dut):
+ """
+ Create grid of lines that define the geometry of the test case.
+ :param dut: DUT where the grid is evaluated on.
+ """
+ separation = self.context.get_min_separation(float(self.controls.finger_size))
+ border_width = 10.0
+
+ grid = create_multi_swipe_horizontal(dut, self.controls.num_fingers, separation, border_width)
+ grid.extend(create_multi_swipe_vertical(dut, self.controls.num_fingers, separation, border_width))
+ grid.extend(create_multi_swipe_diagonal(dut, self.controls.num_fingers, separation, border_width))
+
+ return grid
+
+ def visualize_grid(self, dut):
+ test_pattern = self._create_grid(dut)
+ return GridVisContainer(self.__class__.__name__, (dut.width, dut.height), test_pattern, dut.name)
+
+ def tip_is_valid(self):
+ # TODO: allow multiple fingers when support is added in robot client
+ if self.controls.num_fingers > 1:
+ logger.error("Swipes with more than one finger are not yet supported.")
+ return False
+
+ try:
+ self.context.tips_node.select_tips_by_size(self.controls.finger_size, self.controls.num_fingers,
+ check_only=True)
+ return True
+ except TipSelectionError as e:
+ logger.error(str(e))
+ return False
+
+ def pattern_is_valid(self):
+ dut = self.context.get_active_dut()
+ try:
+ _ = self._create_grid(dut)
+ return True
+ except GridCreationError as e:
+ logger.error(str(e))
+ return False
+
+ def _create_gesture(self, line, test_item, separation):
+ """
+ Create gesture and add to database.
+ :return: id of created gesture.
+ """
+ test = SwipeTest()
+
+ test.start_x = line.start_x
+ test.start_y = line.start_y
+ test.end_x = line.end_x
+ test.end_y = line.end_y
+ test.separation = separation
+ test.azimuth = line.angle
+ test.test_id = test_item.id
+ test.num_fingers = line.fingers
+ test.finger_size = float(self.controls.finger_size)
+ test.swipe_speed = self.speed
+
+ self.context.db.add(test)
+
+ return test.id
+
+ def _save_measurement_data(self, gesture_id, touch_list):
+ """
+ Save swipe measurement to database.
+ """
+ db_list = []
+
+ for test_result in touch_list:
+ results = SwipeResults()
+
+ results.panel_x = float(test_result.panel_x)
+ results.panel_y = float(test_result.panel_y)
+ results.pressure = float(test_result.pressure)
+ results.finger_id = int(test_result.finger_id)
+ results.time = test_result.time
+ results.event = test_result.event
+ results.gesture_id = gesture_id
+
+ db_list.append(results)
+
+ self.context.add_dut_point(float(test_result.panel_x), float(test_result.panel_y))
+
+ self.context.db.addAll(db_list)
+
+
+class SwipeTestParameters(ConfigurationDatabase.TestConfigBase):
+ __tablename__ = 'swipe_parameters'
+ _id = Column('id', Integer, primary_key=True, autoincrement=True)
+ _test_configuration = Column('test_configuration', Integer,
+ ForeignKey('test_configuration.id', ondelete='CASCADE'))
+ _test_configuration_orm = relationship('TestConfiguration', back_populates='_swipe_configurations')
+
+ enabled = Column(Boolean)
+ finger_size = Column(VARCHAR(40))
+ num_fingers = Column(Integer)