You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
209 lines
7.1 KiB
209 lines
7.1 KiB
# Lint as: python2, python3
|
|
# Copyright (c) 2011 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 Python library to interact with INA219 module for TPM testing.
|
|
|
|
Background
|
|
- INA219 is one of two modules on TTCI board
|
|
- This library provides methods to interact with INA219 programmatically
|
|
|
|
Dependency
|
|
- This library depends on a new C shared library called "libsmogcheck.so".
|
|
- In order to run test cases built using this API, one needs a TTCI board
|
|
|
|
Notes:
|
|
- An exception is raised if it doesn't make logical sense to continue program
|
|
flow (e.g. I/O error prevents test case from executing)
|
|
- An exception is caught and then converted to an error code if the caller
|
|
expects to check for error code per API definition
|
|
"""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import logging, re
|
|
from autotest_lib.client.common_lib import i2c_node
|
|
import six
|
|
|
|
|
|
# INA219 registers
|
|
INA_REG = {
|
|
'CONF': 0, # Configuration Register
|
|
'SHUNT_VOLT': 1, # Shunt Voltage
|
|
'BUS_VOLT': 2, # Bus Voltage
|
|
'POWER': 3, # Power
|
|
'CURRENT': 4, # Current
|
|
'CALIB': 5, # Calibration
|
|
}
|
|
|
|
# Regex pattern for measurement value
|
|
HEX_STR_PATTERN = re.compile('^0x([0-9a-f]{2})([0-9a-f]{2})$')
|
|
|
|
# Constants used to initialize INA219 registers
|
|
# TODO(tgao): add docstring for these values after stevenh replies
|
|
INA_CONF_INIT_VAL = 0x9f31
|
|
INA_CALIB_INIT_VAL = 0xc90e
|
|
|
|
# Default values used to calculate/interpret voltage and current measurements.
|
|
DEFAULT_MEAS_RANGE_VALUE = {
|
|
'current': {'max': 0.1, 'min': 0.0, 'denom': 10000.0,
|
|
'reg': INA_REG['CURRENT']},
|
|
'voltage': {'max': 3.35, 'min': 3.25, 'denom': 2000.0,
|
|
'reg': INA_REG['BUS_VOLT']},
|
|
}
|
|
|
|
|
|
class InaError(Exception):
|
|
"""Base class for all errors in this module."""
|
|
|
|
|
|
class InaController(i2c_node.I2cNode):
|
|
"""Object to control INA219 module on TTCI board."""
|
|
|
|
def __init__(self, node_addr=None, range_dict=None):
|
|
"""Constructor.
|
|
|
|
Mandatory params:
|
|
node_addr: node address to set. Default: None.
|
|
|
|
Optional param:
|
|
range_dict: desired max/min thresholds for measurement values.
|
|
Default: DEFAULT_MEAS_RANGE_VALUE.
|
|
|
|
Args:
|
|
node_addr: an integer, address of main or backup power.
|
|
range_dict: desired max/min thresholds for measurement values.
|
|
|
|
Raises:
|
|
InaError: if error initializing INA219 module or invalid range_dict.
|
|
"""
|
|
super(InaController, self).__init__()
|
|
if node_addr is None:
|
|
raise InaError('Error node_addr expected')
|
|
|
|
try:
|
|
if range_dict is None:
|
|
range_dict = DEFAULT_MEAS_RANGE_VALUE
|
|
else:
|
|
self._validateRangeDict(DEFAULT_MEAS_RANGE_VALUE, range_dict)
|
|
self.range_dict = range_dict
|
|
|
|
self.setNodeAddress(node_addr)
|
|
self.writeWord(INA_REG['CONF'], INA_CONF_INIT_VAL)
|
|
self.writeWord(INA_REG['CALIB'], INA_CALIB_INIT_VAL)
|
|
except InaError as e:
|
|
raise InaError('Error initializing INA219: %s' % e)
|
|
|
|
def _validateRangeDict(self, d_ref, d_in):
|
|
"""Validates keys and types of value in range_dict.
|
|
|
|
Iterate over d_ref to make sure all keys exist in d_in and
|
|
values are of the correct type.
|
|
|
|
Args:
|
|
d_ref: a dictionary, used as reference.
|
|
d_in: a dictionary, to be validated against reference.
|
|
|
|
Raises:
|
|
InaError: if range_dict is invalid.
|
|
"""
|
|
for k, v in six.iteritems(d_ref):
|
|
if k not in d_in:
|
|
raise InaError('Key %s not present in dict %r' % (k, d_in))
|
|
if type(v) != type(d_in[k]):
|
|
raise InaError(
|
|
'Value type mismatch for key %s. Expected: %s; actual = %s'
|
|
% (k, type(v), type(d_in[k])))
|
|
if type(v) is dict:
|
|
self._validateRangeDict(v, d_in[k])
|
|
|
|
def readMeasure(self, measure):
|
|
"""Reads requested measurement.
|
|
|
|
Args:
|
|
measure: a string, 'current' or 'voltage'.
|
|
|
|
Returns:
|
|
a float, measurement in native units. Or None if error.
|
|
|
|
Raises:
|
|
InaError: if error reading requested measurement.
|
|
"""
|
|
try:
|
|
hex_str = '0x%.4x' % self.readWord(self.range_dict[measure]['reg'])
|
|
logging.debug('Word read = %r', hex_str)
|
|
return self._checkMeasureRange(hex_str, measure)
|
|
except InaError as e:
|
|
logging.error('Error reading %s: %s', measure, e)
|
|
|
|
def getPowerMetrics(self):
|
|
"""Get measurement metrics for Main Power.
|
|
|
|
Returns:
|
|
an integer, 0 for success and -1 for error.
|
|
a float, voltage value in Volts. Or None if error.
|
|
a float, current value in Amps. Or None if error.
|
|
"""
|
|
logging.info('Attempt to get power metrics')
|
|
try:
|
|
return (0, self.readMeasure('voltage'),
|
|
self.readMeasure('current'))
|
|
except InaError as e:
|
|
logging.error('getPowerMetrics(): %s', e)
|
|
return (-1, None, None)
|
|
|
|
def _checkMeasureRange(self, hex_str, measure):
|
|
"""Checks if measurement value falls within a pre-specified range.
|
|
|
|
Args:
|
|
hex_str: a string (hex value).
|
|
measure: a string, 'current' or 'voltage'.
|
|
|
|
Returns:
|
|
measure_float: a float, measurement value.
|
|
|
|
Raises:
|
|
InaError: if value doesn't fall in range.
|
|
"""
|
|
measure_float = self._convertHexToFloat(
|
|
hex_str, self.range_dict[measure]['denom'])
|
|
measure_msg = '%s value %.2f' % (measure, measure_float)
|
|
range_msg = '[%(min).2f, %(max).2f]' % self.range_dict[measure]
|
|
if (measure_float < self.range_dict[measure]['min'] or
|
|
measure_float > self.range_dict[measure]['max']):
|
|
raise InaError('%s is out of range %s' % measure_msg, range_msg)
|
|
logging.info('%s is in range %s', measure_msg, range_msg)
|
|
return measure_float
|
|
|
|
def _convertHexToFloat(self, hex_str, denom):
|
|
"""Performs measurement calculation.
|
|
|
|
The measurement reading from INA219 module is a 2-byte hex string.
|
|
To convert this hex string to a float, we need to swap these two bytes
|
|
and perform a division. An example:
|
|
response = 0xca19
|
|
swap bytes to get '0x19ca'
|
|
convert to decimal value = 6602
|
|
divide decimal by 2000.0 = 3.301 (volts)
|
|
|
|
Args:
|
|
hex_str: a string (raw hex value).
|
|
denom: a float, denominator used for hex-to-float conversion.
|
|
|
|
Returns:
|
|
a float, measurement value.
|
|
|
|
Raises:
|
|
InaError: if error converting measurement to float.
|
|
"""
|
|
match = HEX_STR_PATTERN.match(hex_str)
|
|
if not match:
|
|
raise InaError('Error: hex string %s does not match '
|
|
'expected pattern' % hex_str)
|
|
|
|
decimal = int('0x%s%s' % (match.group(2), match.group(1)), 16)
|
|
return decimal/denom
|