hitl_tester.modules.bms.smbus_types
Definitions for use in smbus.py
(c) 2020-2024 TurnAround Factor, Inc.
#
CUI DISTRIBUTION CONTROL
Controlled by: DLA J68 R&D SBIP
CUI Category: Small Business Research and Technology
Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS
POC: GOV SBIP Program Manager Denise Price, 571-767-0111
Distribution authorized to U.S. Government Agencies only, to protect information not owned by the
U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that
it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests
for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317,
Fort Belvoir, VA 22060-6221
#
SBIR DATA RIGHTS
Contract No.:SP4701-23-C-0083
Contractor Name: TurnAround Factor, Inc.
Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005
Expiration of SBIR Data Rights Period: September 24, 2029
The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer
software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights
in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause
contained in the above identified contract. No restrictions apply after the expiration date shown above. Any
reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce
the markings.
1""" 2Definitions for use in smbus.py 3 4# (c) 2020-2024 TurnAround Factor, Inc. 5# 6# CUI DISTRIBUTION CONTROL 7# Controlled by: DLA J68 R&D SBIP 8# CUI Category: Small Business Research and Technology 9# Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS 10# POC: GOV SBIP Program Manager Denise Price, 571-767-0111 11# Distribution authorized to U.S. Government Agencies only, to protect information not owned by the 12# U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that 13# it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests 14# for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317, 15# Fort Belvoir, VA 22060-6221 16# 17# SBIR DATA RIGHTS 18# Contract No.:SP4701-23-C-0083 19# Contractor Name: TurnAround Factor, Inc. 20# Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005 21# Expiration of SBIR Data Rights Period: September 24, 2029 22# The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer 23# software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights 24# in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause 25# contained in the above identified contract. No restrictions apply after the expiration date shown above. Any 26# reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce 27# the markings. 28""" 29 30from __future__ import annotations 31 32import datetime 33import re 34from dataclasses import dataclass 35from enum import Enum, IntFlag, IntEnum 36from functools import partial 37from typing import Union 38 39from hitl_tester.modules.logger import logger 40 41 42class StatusCodeAlarm(IntFlag): 43 """Alarms that have been set.""" 44 45 REMAINING_TIME_ALARM = 0x0100 # Average time to empty < remaining time alarm 46 REMAINING_CAPACITY_ALARM = 0x0200 # Remaining capacity < remaining capacity alarm. 47 TERMINATE_DISCHARGE_ALARM = 0x0800 # Battery is depleted, stop discharge 48 OVER_TEMP_ALARM = 0x1000 # Temperature is above preset limit, stop charging 49 TERMINATE_CHARGE_ALARM = 0x4000 # Temporarily suspend charging 50 OVER_CHARGED_ALARM = 0x8000 # Battery is full, stop charging 51 52 def __str__(self) -> str: 53 return " | ".join(str(flag.name).title() for flag in StatusCodeAlarm if self.value & flag) 54 55 56class StatusCodeStatus(IntFlag): 57 """Current status.""" 58 59 FULLY_DISCHARGED = 0x0010 # Battery is completely discharged. 60 FULLY_CHARGED = 0x0020 # Battery is full. 61 DISCHARGING = 0x0040 # Discharge or no charge is occurring. 62 INITIALIZED = 0x0080 # Cleared when calibration data set at factory has been lost. Data unreliable. 63 64 def __str__(self) -> str: 65 return " | ".join(str(flag.name).title() for flag in StatusCodeStatus if self.value & flag) 66 67 68class StatusCodeError(IntEnum): 69 """Errors raised by the battery.""" 70 71 OK = 0 # The Smart Battery processed the function code without detecting any errors. 72 BUSY = 1 # The Smart Battery is unable to process the function code at this time. 73 # The Smart Battery detected an attempt to read or write to a reserved function code. The Smart Battery detected an 74 # attempt to access an unsupported optional manufacturer function code. 75 RESERVED_COMMAND = 2 76 UNSUPPORTED_COMMAND = 3 # The Smart Battery does not support this function code 77 ACCESS_DENIED = 4 # The Smart Battery detected an attempt to write to a read only function code. 78 UNDERFLOW_OVERFLOW = 5 # The Smart Battery detected a data overflow or under flow. 79 BAD_SIZE = 6 # The Smart Battery detected an attempt to write to a function code with an incorrect size data block. 80 UNKNOWN = 7 # The Smart Battery detected an unidentifiable error. 81 MANUFACTURER_ERROR_1 = 8 82 MANUFACTURER_ERROR_2 = 9 83 MANUFACTURER_ERROR_3 = 10 84 MANUFACTURER_ERROR_4 = 11 85 MANUFACTURER_ERROR_5 = 12 86 MANUFACTURER_ERROR_6 = 13 87 MANUFACTURER_ERROR_7 = 14 88 MANUFACTURER_ERROR_8 = 15 89 90 def __str__(self) -> str: 91 return self.name.title() 92 93 94class BatteryStatus: 95 """Contains Alarm and Status bit flags.""" 96 97 def __init__(self, raw_bytes: bytes): 98 raw_int = int.from_bytes(raw_bytes, byteorder="little", signed=False) 99 self.alarm = StatusCodeAlarm(raw_int & 0xFF00) 100 self.status = StatusCodeStatus(raw_int & 0x00F0) 101 self.error = StatusCodeError(raw_int & 0x000F) 102 103 def __str__(self): 104 return f"{self.alarm!s}; {self.status!s}; {self.error!s}" 105 106 107class BatteryMode: 108 """ 109 Describes the various battery operational modes and reports the battery’s capabilities, modes, and flags minor 110 conditions requiring attention. 111 """ 112 113 def __init__(self, raw_bytes: bytes): 114 raw_int = int.from_bytes(raw_bytes, byteorder="little", signed=False) 115 116 def bit(index: int) -> bool: 117 return bool(raw_int & (1 << index)) 118 119 self.internal_charge_controller = bit(0) # Internal Charge Controller is supported 120 self.primary_battery_support = bit(1) # Primary or secondary battery is supported 121 self.condition_cycle_requested = bit(7) # Conditioning Cycle Requested 122 self.charge_controller_enabled = bit(8) # Internal Charge Control Enabled 123 self.primary_battery = bit(9) # Battery operating in its primary role 124 self.alarm_mode = bit(13) # Disable AlarmWarning broadcast to Host and Smart Battery Charger 125 self.charger_mode = bit(14) # Disable broadcasts of ChargingVoltage/ChargingCurrent to Smart Battery Charger 126 self.capacity_mode = bit(15) # Report in 10mW or 10mWh 127 128 def __str__(self): 129 return " | ".join(attribute.title() for attribute, value in self.__dict__.items() if value) 130 131 132class SpecInfoRevision(IntEnum): 133 """Specification revision (4-bit).""" 134 135 REVISION_11_OR_10 = 0x1 # Revision 1.0 or 1.1 136 137 def __str__(self) -> str: 138 text = self.name.replace("_", " ").capitalize() # Modify underscore/case 139 return re.sub(r"(\d)(\d)", r"\1.\2", text) # Place decimal in between digits 140 141 142class SpecInfoVersion(IntEnum): 143 """Specification revision (4-bit).""" 144 145 VERSION_10 = 0x1 # Version 1.0 146 VERSION_11 = 0x2 # Version 1.1 147 VERSION_11_PEC = 0x3 # Version 1.1 with optional PEC support 148 149 def __str__(self) -> str: 150 text = self.name.replace("_", " ").capitalize() # Modify underscore/case 151 return re.sub(r"(\d)(\d)", r"\1.\2", text) # Place decimal in between digits 152 153 154class SpecInfo: 155 """Information on which specification the battery is using.""" 156 157 def __init__(self, raw_bytes: bytes): 158 data = int.from_bytes(raw_bytes, byteorder="little", signed=False) 159 self.revision = SpecInfoRevision(data & 0x000F) 160 self.version = SpecInfoVersion((data & 0x00F0) >> 4) 161 self.voltage_scale = 10 ** ((data & 0x0F00) >> 8) 162 self.current_scale = 10 ** ((data & 0xF000) >> 12) 163 164 def __str__(self): 165 return f"{self.revision!s}; {self.version!s}; {self.voltage_scale}; {self.current_scale}" 166 167 168def to_int(raw_bytes: bytes) -> int: 169 """Convert SMBus signed integer to integer.""" 170 return int.from_bytes(raw_bytes, byteorder="little", signed=True) 171 172 173def to_unsinged_int(raw_bytes: bytes) -> int: 174 """Convert SMBus unsigned integer to integer.""" 175 return int.from_bytes(raw_bytes, byteorder="little", signed=False) 176 177 178def to_bool(raw_bytes: bytes) -> bool: 179 """Convert SMBus bool to bool.""" 180 return bool(int.from_bytes(raw_bytes, byteorder="little", signed=False)) 181 182 183def to_string(raw_bytes: bytes) -> str: 184 """Convert SMBus string to string.""" 185 ascii_string = raw_bytes.decode("ascii", errors="ignore") 186 return ascii_string.translate(dict.fromkeys(range(32)) | {44: None}) # Remove comma/control 187 188 189def to_hex(raw_bytes: bytes) -> str: 190 """Convert SMBus hex to hex string.""" 191 return f"0x{raw_bytes[::-1].hex().upper()}" 192 193 194def to_date(raw_bytes: bytes) -> datetime.datetime: 195 """Convert SMBus date to datetime date.""" 196 data = int.from_bytes(raw_bytes, byteorder="little", signed=False) 197 year = 1980 + (data >> 9) 198 month = (data & 0x01E0) >> 5 199 day = data & 0x001F 200 return datetime.datetime(year, month, day, tzinfo=datetime.timezone.utc) 201 202 203SMBusRegType = Union[int, bool, BatteryStatus, BatteryMode, SpecInfo, str, datetime.datetime] 204 205 206class SMBusFunctionType(Enum): 207 """How to interpret the bytes.""" 208 209 BATTERY_STATUS = partial(BatteryStatus) 210 BATTERY_MODE = partial(BatteryMode) 211 SPEC_INFO = partial(SpecInfo) 212 INT = partial(to_int) 213 UNSIGNED_INT = partial(to_unsinged_int) 214 BOOL = partial(to_bool) 215 STRING = partial(to_string) 216 HEX = partial(to_hex) 217 DATE = partial(to_date) 218 219 220@dataclass 221class SMBusFunction: 222 """Describes how to access specific SMBus addresses.""" 223 224 address: int # Register address 225 size: int | None = 2 # Bytes returned on SMBus 226 type: SMBusFunctionType = SMBusFunctionType.UNSIGNED_INT # How to decode the data 227 writable: bool = False # Register can be written to 228 unit: str = "" # Measurement unit 229 230 231class SMBusReg(Enum): 232 """Register definitions and their return types.""" 233 234 MANUFACTURING_ACCESS = SMBusFunction(address=0x00, type=SMBusFunctionType.INT, writable=True, unit="${capacity}h") 235 REMAINING_CAPACITY_ALARM = SMBusFunction(address=0x01, type=SMBusFunctionType.INT, writable=True, unit="minute") 236 REMAINING_TIME_ALARM = SMBusFunction(address=0x02, writable=True) 237 BATTERY_MODE = SMBusFunction(address=0x03, type=SMBusFunctionType.BATTERY_MODE, writable=True) 238 AT_RATE = SMBusFunction(address=0x04, type=SMBusFunctionType.INT, writable=True, unit="$capacity") 239 AT_RATE_TIME_TO_FULL = SMBusFunction(address=0x05, unit="minute") 240 AT_RATE_TIME_TO_EMPTY = SMBusFunction(address=0x06, unit="minute") 241 AT_RATE_OK = SMBusFunction(address=0x07, type=SMBusFunctionType.BOOL) 242 TEMPERATURE = SMBusFunction(address=0x08, unit="dK") 243 VOLTAGE = SMBusFunction(address=0x09, unit="mV") 244 CURRENT = SMBusFunction(address=0x0A, type=SMBusFunctionType.INT, unit="mA") 245 AVERAGE_CURRENT = SMBusFunction(address=0x0B, type=SMBusFunctionType.INT, unit="mA") 246 MAX_ERROR = SMBusFunction(address=0x0C, size=2, unit="%") 247 RELATIVE_STATE_OF_CHARGE = SMBusFunction(address=0x0D, size=2, unit="%") 248 ABSOLUTE_STATE_OF_CHARGE = SMBusFunction(address=0x0E, size=2, unit="%") 249 REMAINING_CAPACITY = SMBusFunction(address=0x0F, writable=True, unit="${capacity}h") 250 FULL_CHARGE_CAPACITY = SMBusFunction(address=0x10, unit="${capacity}h") 251 RUN_TIME_TO_EMPTY = SMBusFunction(address=0x11, unit="minute") 252 AVERAGE_TIME_TO_EMPTY = SMBusFunction(address=0x12, unit="minute") 253 AVERAGE_TIME_TO_FULL = SMBusFunction(address=0x13, unit="minute") 254 CHARGING_CURRENT = SMBusFunction(address=0x14, unit="mA") 255 CHARGING_VOLTAGE = SMBusFunction(address=0x15, unit="mV") 256 BATTERY_STATUS = SMBusFunction(address=0x16, type=SMBusFunctionType.BATTERY_STATUS) 257 CYCLE_COUNT = SMBusFunction(address=0x17, writable=True) 258 DESIGN_CAPACITY = SMBusFunction(address=0x18, type=SMBusFunctionType.INT, writable=True, unit="${capacity}h") 259 DESIGN_VOLTAGE = SMBusFunction(address=0x19, type=SMBusFunctionType.INT, unit="mV") 260 SPECIFICATION_INFO = SMBusFunction(address=0x1A, type=SMBusFunctionType.SPEC_INFO) 261 MANUFACTURER_DATE = SMBusFunction(address=0x1B, type=SMBusFunctionType.DATE) 262 SERIAL_NUM = SMBusFunction(address=0x1C, type=SMBusFunctionType.HEX, writable=True) 263 MANUFACTURER_NAME = SMBusFunction(address=0x20, size=None, type=SMBusFunctionType.STRING) 264 DEVICE_NAME = SMBusFunction(address=0x21, size=None, type=SMBusFunctionType.STRING) 265 DEVICE_CHEMISTRY = SMBusFunction(address=0x22, size=None, type=SMBusFunctionType.STRING) 266 MANUFACTURER_DATA = SMBusFunction(address=0x23, size=None, type=SMBusFunctionType.STRING) 267 AUTHENTICATE = SMBusFunction(address=0x2F, size=None, type=SMBusFunctionType.STRING, writable=True) 268 CELL_VOLTAGE4 = SMBusFunction(address=0x3C, unit="mV") 269 CELL_VOLTAGE3 = SMBusFunction(address=0x3D, unit="mV") 270 CELL_VOLTAGE2 = SMBusFunction(address=0x3E, unit="mV") 271 CELL_VOLTAGE1 = SMBusFunction(address=0x3F, unit="mV") 272 AFE_DATA = SMBusFunction(address=0x45, size=None, type=SMBusFunctionType.STRING) 273 FET_CONTROL = SMBusFunction(address=0x46, type=SMBusFunctionType.HEX, writable=True) 274 STATE_OF_HEALTH = SMBusFunction(address=0x4F, type=SMBusFunctionType.HEX, unit="%") 275 276 @property 277 def fname(self) -> str: 278 """Format enum name as a string.""" 279 return self.name.title().replace("_", " ") 280 281 282class BMSCommands(IntEnum): 283 """Supported BMS commands.""" 284 285 FAULT_ENABLE = 0xEF 286 """Activate faults that are disable in low battery conditions.""" 287 288 CALIBRATE = 0x0C 289 """Set the current calibration.""" 290 291 ERASE_FLASH = 0xFD 292 """Erase flash memory (calibration, faults, cycle count, etc.).""" 293 294 PRODUCTION_MODE = 0x504D 295 """Once this is called, no going back. We can not erase flash or set calibration.""" 296 297 ASSEMBLY_MODE = 0x414D 298 """This is needed to take us out of manufacturing mode, normally we never write to these flash registers again.""" 299 300 301class SMBusError(Exception): 302 """Log the exception before raising it.""" 303 304 def __init__(self, message): 305 """Output error message and raise exception.""" 306 logger.write_critical_to_report(f"{type(self).__name__} - {message}") 307 super().__init__(message)
43class StatusCodeAlarm(IntFlag): 44 """Alarms that have been set.""" 45 46 REMAINING_TIME_ALARM = 0x0100 # Average time to empty < remaining time alarm 47 REMAINING_CAPACITY_ALARM = 0x0200 # Remaining capacity < remaining capacity alarm. 48 TERMINATE_DISCHARGE_ALARM = 0x0800 # Battery is depleted, stop discharge 49 OVER_TEMP_ALARM = 0x1000 # Temperature is above preset limit, stop charging 50 TERMINATE_CHARGE_ALARM = 0x4000 # Temporarily suspend charging 51 OVER_CHARGED_ALARM = 0x8000 # Battery is full, stop charging 52 53 def __str__(self) -> str: 54 return " | ".join(str(flag.name).title() for flag in StatusCodeAlarm if self.value & flag)
Alarms that have been set.
Inherited Members
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
- enum.Enum
- name
- value
57class StatusCodeStatus(IntFlag): 58 """Current status.""" 59 60 FULLY_DISCHARGED = 0x0010 # Battery is completely discharged. 61 FULLY_CHARGED = 0x0020 # Battery is full. 62 DISCHARGING = 0x0040 # Discharge or no charge is occurring. 63 INITIALIZED = 0x0080 # Cleared when calibration data set at factory has been lost. Data unreliable. 64 65 def __str__(self) -> str: 66 return " | ".join(str(flag.name).title() for flag in StatusCodeStatus if self.value & flag)
Current status.
Inherited Members
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
- enum.Enum
- name
- value
69class StatusCodeError(IntEnum): 70 """Errors raised by the battery.""" 71 72 OK = 0 # The Smart Battery processed the function code without detecting any errors. 73 BUSY = 1 # The Smart Battery is unable to process the function code at this time. 74 # The Smart Battery detected an attempt to read or write to a reserved function code. The Smart Battery detected an 75 # attempt to access an unsupported optional manufacturer function code. 76 RESERVED_COMMAND = 2 77 UNSUPPORTED_COMMAND = 3 # The Smart Battery does not support this function code 78 ACCESS_DENIED = 4 # The Smart Battery detected an attempt to write to a read only function code. 79 UNDERFLOW_OVERFLOW = 5 # The Smart Battery detected a data overflow or under flow. 80 BAD_SIZE = 6 # The Smart Battery detected an attempt to write to a function code with an incorrect size data block. 81 UNKNOWN = 7 # The Smart Battery detected an unidentifiable error. 82 MANUFACTURER_ERROR_1 = 8 83 MANUFACTURER_ERROR_2 = 9 84 MANUFACTURER_ERROR_3 = 10 85 MANUFACTURER_ERROR_4 = 11 86 MANUFACTURER_ERROR_5 = 12 87 MANUFACTURER_ERROR_6 = 13 88 MANUFACTURER_ERROR_7 = 14 89 MANUFACTURER_ERROR_8 = 15 90 91 def __str__(self) -> str: 92 return self.name.title()
Errors raised by the battery.
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
95class BatteryStatus: 96 """Contains Alarm and Status bit flags.""" 97 98 def __init__(self, raw_bytes: bytes): 99 raw_int = int.from_bytes(raw_bytes, byteorder="little", signed=False) 100 self.alarm = StatusCodeAlarm(raw_int & 0xFF00) 101 self.status = StatusCodeStatus(raw_int & 0x00F0) 102 self.error = StatusCodeError(raw_int & 0x000F) 103 104 def __str__(self): 105 return f"{self.alarm!s}; {self.status!s}; {self.error!s}"
Contains Alarm and Status bit flags.
108class BatteryMode: 109 """ 110 Describes the various battery operational modes and reports the battery’s capabilities, modes, and flags minor 111 conditions requiring attention. 112 """ 113 114 def __init__(self, raw_bytes: bytes): 115 raw_int = int.from_bytes(raw_bytes, byteorder="little", signed=False) 116 117 def bit(index: int) -> bool: 118 return bool(raw_int & (1 << index)) 119 120 self.internal_charge_controller = bit(0) # Internal Charge Controller is supported 121 self.primary_battery_support = bit(1) # Primary or secondary battery is supported 122 self.condition_cycle_requested = bit(7) # Conditioning Cycle Requested 123 self.charge_controller_enabled = bit(8) # Internal Charge Control Enabled 124 self.primary_battery = bit(9) # Battery operating in its primary role 125 self.alarm_mode = bit(13) # Disable AlarmWarning broadcast to Host and Smart Battery Charger 126 self.charger_mode = bit(14) # Disable broadcasts of ChargingVoltage/ChargingCurrent to Smart Battery Charger 127 self.capacity_mode = bit(15) # Report in 10mW or 10mWh 128 129 def __str__(self): 130 return " | ".join(attribute.title() for attribute, value in self.__dict__.items() if value)
Describes the various battery operational modes and reports the battery’s capabilities, modes, and flags minor conditions requiring attention.
114 def __init__(self, raw_bytes: bytes): 115 raw_int = int.from_bytes(raw_bytes, byteorder="little", signed=False) 116 117 def bit(index: int) -> bool: 118 return bool(raw_int & (1 << index)) 119 120 self.internal_charge_controller = bit(0) # Internal Charge Controller is supported 121 self.primary_battery_support = bit(1) # Primary or secondary battery is supported 122 self.condition_cycle_requested = bit(7) # Conditioning Cycle Requested 123 self.charge_controller_enabled = bit(8) # Internal Charge Control Enabled 124 self.primary_battery = bit(9) # Battery operating in its primary role 125 self.alarm_mode = bit(13) # Disable AlarmWarning broadcast to Host and Smart Battery Charger 126 self.charger_mode = bit(14) # Disable broadcasts of ChargingVoltage/ChargingCurrent to Smart Battery Charger 127 self.capacity_mode = bit(15) # Report in 10mW or 10mWh
133class SpecInfoRevision(IntEnum): 134 """Specification revision (4-bit).""" 135 136 REVISION_11_OR_10 = 0x1 # Revision 1.0 or 1.1 137 138 def __str__(self) -> str: 139 text = self.name.replace("_", " ").capitalize() # Modify underscore/case 140 return re.sub(r"(\d)(\d)", r"\1.\2", text) # Place decimal in between digits
Specification revision (4-bit).
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
143class SpecInfoVersion(IntEnum): 144 """Specification revision (4-bit).""" 145 146 VERSION_10 = 0x1 # Version 1.0 147 VERSION_11 = 0x2 # Version 1.1 148 VERSION_11_PEC = 0x3 # Version 1.1 with optional PEC support 149 150 def __str__(self) -> str: 151 text = self.name.replace("_", " ").capitalize() # Modify underscore/case 152 return re.sub(r"(\d)(\d)", r"\1.\2", text) # Place decimal in between digits
Specification revision (4-bit).
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
155class SpecInfo: 156 """Information on which specification the battery is using.""" 157 158 def __init__(self, raw_bytes: bytes): 159 data = int.from_bytes(raw_bytes, byteorder="little", signed=False) 160 self.revision = SpecInfoRevision(data & 0x000F) 161 self.version = SpecInfoVersion((data & 0x00F0) >> 4) 162 self.voltage_scale = 10 ** ((data & 0x0F00) >> 8) 163 self.current_scale = 10 ** ((data & 0xF000) >> 12) 164 165 def __str__(self): 166 return f"{self.revision!s}; {self.version!s}; {self.voltage_scale}; {self.current_scale}"
Information on which specification the battery is using.
158 def __init__(self, raw_bytes: bytes): 159 data = int.from_bytes(raw_bytes, byteorder="little", signed=False) 160 self.revision = SpecInfoRevision(data & 0x000F) 161 self.version = SpecInfoVersion((data & 0x00F0) >> 4) 162 self.voltage_scale = 10 ** ((data & 0x0F00) >> 8) 163 self.current_scale = 10 ** ((data & 0xF000) >> 12)
169def to_int(raw_bytes: bytes) -> int: 170 """Convert SMBus signed integer to integer.""" 171 return int.from_bytes(raw_bytes, byteorder="little", signed=True)
Convert SMBus signed integer to integer.
174def to_unsinged_int(raw_bytes: bytes) -> int: 175 """Convert SMBus unsigned integer to integer.""" 176 return int.from_bytes(raw_bytes, byteorder="little", signed=False)
Convert SMBus unsigned integer to integer.
179def to_bool(raw_bytes: bytes) -> bool: 180 """Convert SMBus bool to bool.""" 181 return bool(int.from_bytes(raw_bytes, byteorder="little", signed=False))
Convert SMBus bool to bool.
184def to_string(raw_bytes: bytes) -> str: 185 """Convert SMBus string to string.""" 186 ascii_string = raw_bytes.decode("ascii", errors="ignore") 187 return ascii_string.translate(dict.fromkeys(range(32)) | {44: None}) # Remove comma/control
Convert SMBus string to string.
190def to_hex(raw_bytes: bytes) -> str: 191 """Convert SMBus hex to hex string.""" 192 return f"0x{raw_bytes[::-1].hex().upper()}"
Convert SMBus hex to hex string.
195def to_date(raw_bytes: bytes) -> datetime.datetime: 196 """Convert SMBus date to datetime date.""" 197 data = int.from_bytes(raw_bytes, byteorder="little", signed=False) 198 year = 1980 + (data >> 9) 199 month = (data & 0x01E0) >> 5 200 day = data & 0x001F 201 return datetime.datetime(year, month, day, tzinfo=datetime.timezone.utc)
Convert SMBus date to datetime date.
207class SMBusFunctionType(Enum): 208 """How to interpret the bytes.""" 209 210 BATTERY_STATUS = partial(BatteryStatus) 211 BATTERY_MODE = partial(BatteryMode) 212 SPEC_INFO = partial(SpecInfo) 213 INT = partial(to_int) 214 UNSIGNED_INT = partial(to_unsinged_int) 215 BOOL = partial(to_bool) 216 STRING = partial(to_string) 217 HEX = partial(to_hex) 218 DATE = partial(to_date)
How to interpret the bytes.
Inherited Members
- enum.Enum
- name
- value
221@dataclass 222class SMBusFunction: 223 """Describes how to access specific SMBus addresses.""" 224 225 address: int # Register address 226 size: int | None = 2 # Bytes returned on SMBus 227 type: SMBusFunctionType = SMBusFunctionType.UNSIGNED_INT # How to decode the data 228 writable: bool = False # Register can be written to 229 unit: str = "" # Measurement unit
Describes how to access specific SMBus addresses.
232class SMBusReg(Enum): 233 """Register definitions and their return types.""" 234 235 MANUFACTURING_ACCESS = SMBusFunction(address=0x00, type=SMBusFunctionType.INT, writable=True, unit="${capacity}h") 236 REMAINING_CAPACITY_ALARM = SMBusFunction(address=0x01, type=SMBusFunctionType.INT, writable=True, unit="minute") 237 REMAINING_TIME_ALARM = SMBusFunction(address=0x02, writable=True) 238 BATTERY_MODE = SMBusFunction(address=0x03, type=SMBusFunctionType.BATTERY_MODE, writable=True) 239 AT_RATE = SMBusFunction(address=0x04, type=SMBusFunctionType.INT, writable=True, unit="$capacity") 240 AT_RATE_TIME_TO_FULL = SMBusFunction(address=0x05, unit="minute") 241 AT_RATE_TIME_TO_EMPTY = SMBusFunction(address=0x06, unit="minute") 242 AT_RATE_OK = SMBusFunction(address=0x07, type=SMBusFunctionType.BOOL) 243 TEMPERATURE = SMBusFunction(address=0x08, unit="dK") 244 VOLTAGE = SMBusFunction(address=0x09, unit="mV") 245 CURRENT = SMBusFunction(address=0x0A, type=SMBusFunctionType.INT, unit="mA") 246 AVERAGE_CURRENT = SMBusFunction(address=0x0B, type=SMBusFunctionType.INT, unit="mA") 247 MAX_ERROR = SMBusFunction(address=0x0C, size=2, unit="%") 248 RELATIVE_STATE_OF_CHARGE = SMBusFunction(address=0x0D, size=2, unit="%") 249 ABSOLUTE_STATE_OF_CHARGE = SMBusFunction(address=0x0E, size=2, unit="%") 250 REMAINING_CAPACITY = SMBusFunction(address=0x0F, writable=True, unit="${capacity}h") 251 FULL_CHARGE_CAPACITY = SMBusFunction(address=0x10, unit="${capacity}h") 252 RUN_TIME_TO_EMPTY = SMBusFunction(address=0x11, unit="minute") 253 AVERAGE_TIME_TO_EMPTY = SMBusFunction(address=0x12, unit="minute") 254 AVERAGE_TIME_TO_FULL = SMBusFunction(address=0x13, unit="minute") 255 CHARGING_CURRENT = SMBusFunction(address=0x14, unit="mA") 256 CHARGING_VOLTAGE = SMBusFunction(address=0x15, unit="mV") 257 BATTERY_STATUS = SMBusFunction(address=0x16, type=SMBusFunctionType.BATTERY_STATUS) 258 CYCLE_COUNT = SMBusFunction(address=0x17, writable=True) 259 DESIGN_CAPACITY = SMBusFunction(address=0x18, type=SMBusFunctionType.INT, writable=True, unit="${capacity}h") 260 DESIGN_VOLTAGE = SMBusFunction(address=0x19, type=SMBusFunctionType.INT, unit="mV") 261 SPECIFICATION_INFO = SMBusFunction(address=0x1A, type=SMBusFunctionType.SPEC_INFO) 262 MANUFACTURER_DATE = SMBusFunction(address=0x1B, type=SMBusFunctionType.DATE) 263 SERIAL_NUM = SMBusFunction(address=0x1C, type=SMBusFunctionType.HEX, writable=True) 264 MANUFACTURER_NAME = SMBusFunction(address=0x20, size=None, type=SMBusFunctionType.STRING) 265 DEVICE_NAME = SMBusFunction(address=0x21, size=None, type=SMBusFunctionType.STRING) 266 DEVICE_CHEMISTRY = SMBusFunction(address=0x22, size=None, type=SMBusFunctionType.STRING) 267 MANUFACTURER_DATA = SMBusFunction(address=0x23, size=None, type=SMBusFunctionType.STRING) 268 AUTHENTICATE = SMBusFunction(address=0x2F, size=None, type=SMBusFunctionType.STRING, writable=True) 269 CELL_VOLTAGE4 = SMBusFunction(address=0x3C, unit="mV") 270 CELL_VOLTAGE3 = SMBusFunction(address=0x3D, unit="mV") 271 CELL_VOLTAGE2 = SMBusFunction(address=0x3E, unit="mV") 272 CELL_VOLTAGE1 = SMBusFunction(address=0x3F, unit="mV") 273 AFE_DATA = SMBusFunction(address=0x45, size=None, type=SMBusFunctionType.STRING) 274 FET_CONTROL = SMBusFunction(address=0x46, type=SMBusFunctionType.HEX, writable=True) 275 STATE_OF_HEALTH = SMBusFunction(address=0x4F, type=SMBusFunctionType.HEX, unit="%") 276 277 @property 278 def fname(self) -> str: 279 """Format enum name as a string.""" 280 return self.name.title().replace("_", " ")
Register definitions and their return types.
277 @property 278 def fname(self) -> str: 279 """Format enum name as a string.""" 280 return self.name.title().replace("_", " ")
Format enum name as a string.
Inherited Members
- enum.Enum
- name
- value
283class BMSCommands(IntEnum): 284 """Supported BMS commands.""" 285 286 FAULT_ENABLE = 0xEF 287 """Activate faults that are disable in low battery conditions.""" 288 289 CALIBRATE = 0x0C 290 """Set the current calibration.""" 291 292 ERASE_FLASH = 0xFD 293 """Erase flash memory (calibration, faults, cycle count, etc.).""" 294 295 PRODUCTION_MODE = 0x504D 296 """Once this is called, no going back. We can not erase flash or set calibration.""" 297 298 ASSEMBLY_MODE = 0x414D 299 """This is needed to take us out of manufacturing mode, normally we never write to these flash registers again."""
Supported BMS commands.
Activate faults that are disable in low battery conditions.
Erase flash memory (calibration, faults, cycle count, etc.).
Once this is called, no going back. We can not erase flash or set calibration.
This is needed to take us out of manufacturing mode, normally we never write to these flash registers again.
Inherited Members
- enum.Enum
- name
- value
- builtins.int
- conjugate
- bit_length
- bit_count
- to_bytes
- from_bytes
- as_integer_ratio
- is_integer
- real
- imag
- numerator
- denominator
302class SMBusError(Exception): 303 """Log the exception before raising it.""" 304 305 def __init__(self, message): 306 """Output error message and raise exception.""" 307 logger.write_critical_to_report(f"{type(self).__name__} - {message}") 308 super().__init__(message)
Log the exception before raising it.
305 def __init__(self, message): 306 """Output error message and raise exception.""" 307 logger.write_critical_to_report(f"{type(self).__name__} - {message}") 308 super().__init__(message)
Output error message and raise exception.
Inherited Members
- builtins.BaseException
- with_traceback
- add_note
- args