blob: 84dc55848cd5dee7e3046eba255b43fe50810a95 [file] [log] [blame]
Dennis Kempin1a8a5be2013-06-18 11:00:02 -07001# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
Dennis Kempin037675e2013-06-14 14:12:39 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4#
Dennis Kempin17766a62013-06-17 14:09:33 -07005""" This module manages the platform properties in mttools/platforms """
Dennis Kempin1a8a5be2013-06-18 11:00:02 -07006import json
Dennis Kempin037675e2013-06-14 14:12:39 -07007import os
8import re
Dennis Kempin1a8a5be2013-06-18 11:00:02 -07009import sys
Dennis Kempin037675e2013-06-14 14:12:39 -070010from xorg_conf import XorgInputClassParser
11
12# path to current script directory
13script_dir = os.path.dirname(os.path.realpath(__file__))
Dennis Kempin17766a62013-06-17 14:09:33 -070014platforms_dir = os.path.realpath(os.path.join(script_dir, '..', 'platforms'))
15xorg_conf_path = os.path.realpath(os.path.join(script_dir, '..',
16 '..', 'xorg-conf'))
Dennis Kempin037675e2013-06-14 14:12:39 -070017
18class PlatformProperties(object):
Dennis Kempin17766a62013-06-17 14:09:33 -070019 """ A class containing hardware and xorg properties for a platform.
20
21 The class can be created from an activity log or by providing
22 the name of the platform. Information will then be read from the
23 'platforms_dir' directory.
Dennis Kempin037675e2013-06-14 14:12:39 -070024 """
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070025 def __init__(self, platform=None, log=None):
26 self.required_axis = []
27 self.has_axis = []
28 self.device_class = "touchpad"
Dennis Kempin037675e2013-06-14 14:12:39 -070029 if platform:
30 basename = os.path.join(platforms_dir, platform)
31 self.name = platform
Dennis Kempin17766a62013-06-17 14:09:33 -070032 self.hwprops_file = basename + '.hwprops'
33 self.props_file = basename + '.props'
Dennis Kempin037675e2013-06-14 14:12:39 -070034 self.xorg_parser = XorgInputClassParser()
35 self._ParseHWProperties(open(self.hwprops_file).read())
36 self._ParseProperties(open(self.props_file).read())
37 self._UpdateDimensions()
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070038 elif log:
Dennis Kempin17766a62013-06-17 14:09:33 -070039 self.name = ''
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070040 self._ParseActivityLog(log.activity)
41 if log.evdev:
42 self._ParseEvdevLog(log.evdev)
Dennis Kempin037675e2013-06-14 14:12:39 -070043
44 def _ParseActivityLog(self, activity_data):
Dennis Kempin17766a62013-06-17 14:09:33 -070045 """ Parse property information from an activity log """
Dennis Kempin037675e2013-06-14 14:12:39 -070046 activity = json.loads(activity_data)
Dennis Kempin17766a62013-06-17 14:09:33 -070047 self.properties = activity['properties']
Dennis Kempin037675e2013-06-14 14:12:39 -070048
Dennis Kempin17766a62013-06-17 14:09:33 -070049 hwprops = activity['hardwareProperties']
50 self.x_min = int(hwprops['left'])
51 self.x_max = int(hwprops['right'])
52 self.x_res = int(hwprops['xResolution'])
53 self.y_min = int(hwprops['top'])
54 self.y_max = int(hwprops['bottom'])
55 self.y_res = int(hwprops['yResolution'])
Dennis Kempin037675e2013-06-14 14:12:39 -070056
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070057 def _ParseEvdevLog(self, evdev_data):
58 # The format of ABS (0003) reports is:
59 # timestamp 0003 axis value
60 report_regex = re.compile(' 0003 ([0-9a-f]{4}) ([0-9a-f]+)')
61 for match in report_regex.finditer(evdev_data):
62 axis = int(match.group(1), 16)
63 if axis not in self.required_axis:
64 self.required_axis.append(axis)
65
Dennis Kempin037675e2013-06-14 14:12:39 -070066 def _ParseHWProperties(self, data):
Dennis Kempin17766a62013-06-17 14:09:33 -070067 """ Parse x and y dimensions and resolution from hwprops file """
68 xregex = re.compile('A: 35 ([0-9\-]+) ([0-9\-]+) ([0-9\-]+) ' +
69 '([0-9\-]+) ([0-9\-]+)')
Dennis Kempin037675e2013-06-14 14:12:39 -070070 xmatch = xregex.search(data);
71 self.x_min = int(xmatch.group(1))
72 self.x_max = int(xmatch.group(2))
73 self.x_res = int(xmatch.group(5))
74
Dennis Kempin17766a62013-06-17 14:09:33 -070075 yregex = re.compile('A: 36 ([0-9\-]+) ([0-9\-]+) ([0-9\-]+) ' +
76 '([0-9\-]+) ([0-9\-]+)')
Dennis Kempin037675e2013-06-14 14:12:39 -070077 ymatch = yregex.search(data);
78 self.y_min = int(ymatch.group(1))
79 self.y_max = int(ymatch.group(2))
80 self.y_res = int(ymatch.group(5))
81
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070082 axis_regex = re.compile('A: ([0-9a-f]+)')
83 for match in axis_regex.finditer(data):
84 self.has_axis.append(int(match.group(1), 16))
85
Dennis Kempin037675e2013-06-14 14:12:39 -070086 def _ParseProperties(self, data):
Dennis Kempin17766a62013-06-17 14:09:33 -070087 """ Parse properties from file and inject xorg properties. """
Dennis Kempin037675e2013-06-14 14:12:39 -070088 self.properties = {}
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070089 self.ignore_properties = []
Dennis Kempin037675e2013-06-14 14:12:39 -070090 data = json.loads(data)
91
Dennis Kempin17766a62013-06-17 14:09:33 -070092 if 'gestures' in data:
93 self.properties.update(data['gestures'])
Dennis Kempin037675e2013-06-14 14:12:39 -070094
Dennis Kempin17766a62013-06-17 14:09:33 -070095 if 'device_class' in data:
96 self.device_class = data['device_class']
Dennis Kempin037675e2013-06-14 14:12:39 -070097
Dennis Kempin1a8a5be2013-06-18 11:00:02 -070098 if 'ignore' in data:
99 self.ignore_properties.extend(data['ignore'])
100
Dennis Kempin17766a62013-06-17 14:09:33 -0700101 if xorg_conf_path and 'xorg' in data and 'file' in data['xorg']:
102 filename = os.path.join(xorg_conf_path, data['xorg']['file'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700103 input_classes = self.xorg_parser.Parse(file=filename)
Dennis Kempin17766a62013-06-17 14:09:33 -0700104 if 'identifier' in data['xorg']:
105 properties = input_classes[data['xorg']['identifier']]
Dennis Kempin037675e2013-06-14 14:12:39 -0700106 self.properties.update(properties)
Dennis Kempin17766a62013-06-17 14:09:33 -0700107 if 'identifiers' in data['xorg']:
108 for identifier in data['xorg']['identifiers']:
Dennis Kempin037675e2013-06-14 14:12:39 -0700109 properties = input_classes[identifier]
110 self.properties.update(properties)
111
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700112 for prop in self.ignore_properties:
113 if prop in self.properties:
114 del self.properties[prop]
115
Dennis Kempin037675e2013-06-14 14:12:39 -0700116 def _UpdateDimensions(self):
Dennis Kempin17766a62013-06-17 14:09:33 -0700117 """ Update x/y min/max with xorg properties.
118
119 CMT allows hardware properties to be overwritten by xorg properties.
120 Do the same in this class.
Dennis Kempin037675e2013-06-14 14:12:39 -0700121 """
Dennis Kempin17766a62013-06-17 14:09:33 -0700122 if 'Active Area Left' in self.properties:
123 self.x_min = int(self.properties['Active Area Left'])
124 if 'Active Area Right' in self.properties:
125 self.x_max = int(self.properties['Active Area Right'])
Dennis Kempin17766a62013-06-17 14:09:33 -0700126 if 'Active Area Top' in self.properties:
127 self.y_min = int(self.properties['Active Area Top'])
128 if 'Active Area Bottom' in self.properties:
129 self.y_max = int(self.properties['Active Area Bottom'])
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700130
131 if 'Horizontal Resolution' in self.properties:
132 self.x_res = int(self.properties['Horizontal Resolution'])
Dennis Kempin17766a62013-06-17 14:09:33 -0700133 if 'Vertical Resolution' in self.properties:
134 self.y_res = int(self.properties['Vertical Resolution'])
Dennis Kempin037675e2013-06-14 14:12:39 -0700135
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700136 if 'SemiMT Non Linear Area Left' in self.properties:
137 self.x_min = int(self.properties['SemiMT Non Linear Area Left'])
138 if 'SemiMT Non Linear Area Right' in self.properties:
139 self.x_max = int(self.properties['SemiMT Non Linear Area Right'])
140 if 'SemiMT Non Linear Area Top' in self.properties:
141 self.y_min = int(self.properties['SemiMT Non Linear Area Top'])
142 if 'SemiMT Non Linear Area Bottom' in self.properties:
143 self.y_max = int(self.properties['SemiMT Non Linear Area Bottom'])
144
145
146 def Match(self, other, loose, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700147 """ Compare properties and return similarity.
148
149 Compare these properties to another PlatformProperties instance.
150 The return value is a score between 1. 0 meaning there is a big mismatch
151 and 1 meaning the properties match completely.
152 Only a selected range of properties are compared in order to
153 prevent property adjustments to cause platforms to be mismatched.
Dennis Kempin037675e2013-06-14 14:12:39 -0700154 """
155 scores = []
156 def compare(a, b, what):
157 value = abs(float(a) - float(b))
158 if value > 0:
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700159 value = min(1, value / max(abs(float(a)), abs(float(b))))
Dennis Kempin037675e2013-06-14 14:12:39 -0700160 scores.append(1-value)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700161 if debug:
162 print "%s: %s == %s" % (what, str(a), str(b))
Dennis Kempin037675e2013-06-14 14:12:39 -0700163 def compare_attr(what):
164 compare(getattr(self, what), getattr(other, what), what)
165 def compare_prop(what):
166 if what not in self.properties or what not in other.properties:
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700167 scores.append(0)
168 else:
169 compare(self.properties[what], other.properties[what], what)
170 def check_axis(required, available):
171 for axis in required:
172 if axis not in available:
173 scores.append(0)
174 return
175
Dennis Kempin17766a62013-06-17 14:09:33 -0700176 compare_attr('x_min')
177 compare_attr('x_max')
178 compare_attr('x_res')
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700179 compare_attr('y_min')
180 compare_attr('y_max')
181 compare_attr('y_res')
182 if not loose:
183 compare_prop('Pressure Calibration Offset')
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700184
185 if self.required_axis:
186 if debug:
187 print "axis:", self.required_axis, "in", other.has_axis
188 check_axis(self.required_axis, other.has_axis)
189
190 if other.required_axis:
191 if debug:
192 print "axis:", other.required_axis, "in", self.has_axis
193 check_axis(other.required_axis, self.has_axis)
194
Dennis Kempin037675e2013-06-14 14:12:39 -0700195 return reduce(lambda x,y: (x * y), scores)
196
197class PlatformDatabase(object):
Dennis Kempin17766a62013-06-17 14:09:33 -0700198 """ Class for managing platforms.
199
200 This class reads all available platforms from the platforms_dir and allows
201 to search for matching platforms to an activity log file.
Dennis Kempin037675e2013-06-14 14:12:39 -0700202 """
203 def __init__(self):
204 platform_files = [ f for f in os.listdir(platforms_dir)
Dennis Kempin17766a62013-06-17 14:09:33 -0700205 if f.endswith('.hwprops') ]
Dennis Kempin037675e2013-06-14 14:12:39 -0700206 self.platforms = {}
207 for file in platform_files:
Dennis Kempin17766a62013-06-17 14:09:33 -0700208 name = file.replace('.hwprops', '')
Dennis Kempin037675e2013-06-14 14:12:39 -0700209 self.platforms[name] = PlatformProperties(platform=name)
210
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700211 def FindMatching(self, log, loose=True, debug=False):
Dennis Kempin17766a62013-06-17 14:09:33 -0700212 """ Find platform matching activity_data.
213
214 Returns the PlatformProperties instance of the platform matching
215 the activity log data. This method might terminate the program in
216 case no match can be made, or the match is ambiguous.
Dennis Kempin037675e2013-06-14 14:12:39 -0700217 """
218 result = None
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700219 properties = PlatformProperties(log=log)
Dennis Kempin037675e2013-06-14 14:12:39 -0700220 for name, platform in self.platforms.items():
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700221 if debug:
222 print "#" * 10, name
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700223 score = platform.Match(properties, loose, debug)
Dennis Kempin1a8a5be2013-06-18 11:00:02 -0700224 if debug:
225 print name, "score =", score
Dennis Kempin037675e2013-06-14 14:12:39 -0700226 if score > 0.9:
227 if result:
Dennis Kempin55af9cc2013-06-20 15:07:21 -0700228 if loose:
229 if debug:
230 print "-" * 10, "Multiple matches. Try strict"
231 return self.FindMatching(log, False, debug)
Dennis Kempin17766a62013-06-17 14:09:33 -0700232 print ('multiple matching platforms:', result.name,
233 'and', platform.name)
Dennis Kempin19e972b2013-06-20 13:21:38 -0700234 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700235 result = platform
236 if not result:
Dennis Kempin17766a62013-06-17 14:09:33 -0700237 print 'cannot find matching platform'
Dennis Kempin19e972b2013-06-20 13:21:38 -0700238 return None
Dennis Kempin037675e2013-06-14 14:12:39 -0700239 return result