blob: e0883abec37142b369e2cf5cd5261c3aa205d72f [file] [log] [blame]
Tan Gao3768ffe2011-08-30 11:12:20 -07001# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""A Python library to interact with INA219 module for TPM testing.
6
7Background
8 - INA219 is one of two modules on TTCI board
9 - This library provides methods to interact with INA219 programmatically
10
11Dependency
12 - This library depends on a new C shared library called "libsmogcheck.so".
13 - In order to run test cases built using this API, one needs a TTCI board
14
15Notes:
16 - An exception is raised if it doesn't make logical sense to continue program
17 flow (e.g. I/O error prevents test case from executing)
18 - An exception is caught and then converted to an error code if the caller
19 expects to check for error code per API definition
20"""
21
22import logging, re
23from autotest_lib.client.common_lib import i2c_slave
24
25
26# INA219 registers
27INA_REG = {
28 'CONF': 0, # Configuration Register
29 'SHUNT_VOLT': 1, # Shunt Voltage
30 'BUS_VOLT': 2, # Bus Voltage
31 'POWER': 3, # Power
32 'CURRENT': 4, # Current
33 'CALIB': 5, # Calibration
34 }
35
36# Regex pattern for measurement value
37HEX_STR_PATTERN = re.compile('^0x([0-9a-f]{2})([0-9a-f]{2})$')
38
39# Constants used to initialize INA219 registers
40# TODO(tgao): add docstring for these values after stevenh replies
41INA_CONF_INIT_VAL = 0x9f31
42INA_CALIB_INIT_VAL = 0xc90e
43
44# Default values used to calculate/interpret voltage and current measurements.
45DEFAULT_MEAS_RANGE_VALUE = {
46 'current': {'max': 0.1, 'min': 0.0, 'denom': 10000.0,
47 'reg': INA_REG['CURRENT']},
48 'voltage': {'max': 3.35, 'min': 3.25, 'denom': 2000.0,
49 'reg': INA_REG['BUS_VOLT']},
50 }
51
52
53class InaError(Exception):
54 """Base class for all errors in this module."""
55
56
57class InaController(i2c_slave.I2cSlave):
58 """Object to control INA219 module on TTCI board."""
59
60 def __init__(self, slave_addr=None, range_dict=None):
61 """Constructor.
62
63 Mandatory params:
64 slave_addr: slave address to set. Default: None.
65
66 Optional param:
67 range_dict: desired max/min thresholds for measurement values.
68 Default: DEFAULT_MEAS_RANGE_VALUE.
69
70 Args:
71 slave_addr: an integer, address of main or backup power.
72 range_dict: desired max/min thresholds for measurement values.
73
74 Raises:
75 InaError: if error initializing INA219 module or invalid range_dict.
76 """
77 super(InaController, self).__init__()
78 if slave_addr is None:
79 raise InaError('Error slave_addr expected')
80
81 try:
82 if range_dict is None:
83 range_dict = DEFAULT_MEAS_RANGE_VALUE
84 else:
85 self._validateRangeDict(DEFAULT_MEAS_RANGE_VALUE, range_dict)
86 self.range_dict = range_dict
87
88 self.setSlaveAddress(slave_addr)
89 self.writeWord(INA_REG['CONF'], INA_CONF_INIT_VAL)
90 self.writeWord(INA_REG['CALIB'], INA_CALIB_INIT_VAL)
91 except InaError, e:
92 raise InaError('Error initializing INA219: %s' % e)
93
94 def _validateRangeDict(self, d_ref, d_in):
95 """Validates keys and types of value in range_dict.
96
97 Iterate over d_ref to make sure all keys exist in d_in and
98 values are of the correct type.
99
100 Args:
101 d_ref: a dictionary, used as reference.
102 d_in: a dictionary, to be validated against reference.
103
104 Raises:
105 InaError: if range_dict is invalid.
106 """
107 for k, v in d_ref.iteritems():
108 if k not in d_in:
109 raise InaError('Key %s not present in dict %r' % (k, d_in))
110 if type(v) != type(d_in[k]):
111 raise InaError(
112 'Value type mismatch for key %s. Expected: %s; actual = %s'
113 % (k, type(v), type(d_in[k])))
114 if type(v) is dict:
115 self._validateRangeDict(v, d_in[k])
116
117 def readMeasure(self, measure):
118 """Reads requested measurement.
119
120 Args:
121 measure: a string, 'current' or 'voltage'.
122
123 Returns:
124 a float, measurement in native units. Or None if error.
125
126 Raises:
127 InaError: if error reading requested measurement.
128 """
129 try:
130 hex_str = '0x%.4x' % self.readWord(self.range_dict[measure]['reg'])
131 logging.debug('Word read = %r', hex_str)
132 return self._checkMeasureRange(hex_str, measure)
133 except InaError, e:
134 logging.error('Error reading %s: %s', measure, e)
135
136 def getPowerMetrics(self):
137 """Get measurement metrics for Main Power.
138
139 Returns:
140 an integer, 0 for success and -1 for error.
141 a float, voltage value in Volts. Or None if error.
142 a float, current value in Amps. Or None if error.
143 """
144 logging.info('Attempt to get power metrics')
145 try:
146 return (0, self.readMeasure('voltage'),
147 self.readMeasure('current'))
148 except InaError, e:
149 logging.error('getPowerMetrics(): %s', e)
150 return (-1, None, None)
151
152 def _checkMeasureRange(self, hex_str, measure):
153 """Checks if measurement value falls within a pre-specified range.
154
155 Args:
156 hex_str: a string (hex value).
157 measure: a string, 'current' or 'voltage'.
158
159 Returns:
160 measure_float: a float, measurement value.
161
162 Raises:
163 InaError: if value doesn't fall in range.
164 """
165 measure_float = self._convertHexToFloat(
166 hex_str, self.range_dict[measure]['denom'])
167 measure_msg = '%s value %.2f' % (measure, measure_float)
168 range_msg = '[%(min).2f, %(max).2f]' % self.range_dict[measure]
169 if (measure_float < self.range_dict[measure]['min'] or
170 measure_float > self.range_dict[measure]['max']):
171 raise InaError('%s is out of range %s' % measure_msg, range_msg)
172 logging.info('%s is in range %s', measure_msg, range_msg)
173 return measure_float
174
175 def _convertHexToFloat(self, hex_str, denom):
176 """Performs measurement calculation.
177
178 The measurement reading from INA219 module is a 2-byte hex string.
179 To convert this hex string to a float, we need to swap these two bytes
180 and perform a division. An example:
181 response = 0xca19
182 swap bytes to get '0x19ca'
183 convert to decimal value = 6602
184 divide decimal by 2000.0 = 3.301 (volts)
185
186 Args:
187 hex_str: a string (raw hex value).
188 denom: a float, denominator used for hex-to-float conversion.
189
190 Returns:
191 a float, measurement value.
192
193 Raises:
194 InaError: if error converting measurement to float.
195 """
196 match = HEX_STR_PATTERN.match(hex_str)
197 if not match:
198 raise InaError('Error: hex string %s does not match '
199 'expected pattern' % hex_str)
200
201 decimal = int('0x%s%s' % (match.group(2), match.group(1)), 16)
202 return decimal/denom