hitl_tester.test_cases.cyber_6t.mil_prf
| Test | SPEC Tests |
|---|---|
| GitHub Issue(s) | turnaroundfactor/HITL#582 |
| Description | Tests for J1939 and MIL-PRF specifications. |
(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.
Used in these test plans:
- mil_prf ⠀⠀⠀(cyber_6t/mil_prf.plan)
Example Command (warning: test plan may run other test cases):
./hitl_tester.py mil_prf -DBATTERY_CHANNEL=can0 -DHARD_RESET=False -DPROFILE="" -DFULL_MEMORY_TESTS=False -DADDRESS=232 -DBATTERY_INFO=namespace()
1""" 2| Test | SPEC Tests | 3| :------------------- | :----------------------------------------------------------- | 4| GitHub Issue(s) | turnaroundfactor/HITL#582 | 5| Description | Tests for J1939 and MIL-PRF specifications. | 6 7# (c) 2020-2024 TurnAround Factor, Inc. 8# 9# CUI DISTRIBUTION CONTROL 10# Controlled by: DLA J68 R&D SBIP 11# CUI Category: Small Business Research and Technology 12# Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS 13# POC: GOV SBIP Program Manager Denise Price, 571-767-0111 14# Distribution authorized to U.S. Government Agencies only, to protect information not owned by the 15# U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that 16# it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests 17# for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317, 18# Fort Belvoir, VA 22060-6221 19# 20# SBIR DATA RIGHTS 21# Contract No.:SP4701-23-C-0083 22# Contractor Name: TurnAround Factor, Inc. 23# Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005 24# Expiration of SBIR Data Rights Period: September 24, 2029 25# The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer 26# software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights 27# in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause 28# contained in the above identified contract. No restrictions apply after the expiration date shown above. Any 29# reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce 30# the markings. 31""" 32 33from __future__ import annotations 34 35import itertools 36import re 37import time 38from enum import Enum, IntEnum 39from types import SimpleNamespace 40from typing import Literal 41 42import pytest # pylint: disable=wrong-import-order 43from hitl_tester.modules.cyber_6t import spn_types 44from hitl_tester.modules.cyber_6t.canbus import CANBus, CANFrame 45from hitl_tester.modules.cyber_6t.j1939da import PGN 46 47from hitl_tester.modules import properties 48from hitl_tester.modules.logger import logger 49 50 51BATTERY_CHANNEL = "can0" 52"""Channel identification. Expected type is backend dependent.""" 53 54HARD_RESET = False 55"""Whether to use a soft or hard reset before each test.""" 56 57PROFILE = "" 58"""The name of the profile to compare against. If this is not provided, a profile json will be generated.""" 59 60FULL_MEMORY_TESTS = False # TODO: Create variable in hitl_tester.py? 61"""Short or long memory tests""" 62 63properties.apply() # Allow modifying the above globals 64 65ADDRESS = 232 66BATTERY_INFO = SimpleNamespace() 67 68 69class ManufacturerID(IntEnum): 70 """Enum to hold Manufacturer IDs.""" 71 72 SAFT = 269 # NOTE: unused 73 BRENTRONICS = 822 74 75 76class Modes(float, Enum): 77 """Enum for Command modes""" 78 79 ERASE = 0 80 READ = 1 81 WRITE = 2 82 BOOT = 6 83 EDCP = 7 84 85 86class BadStates(Enum): 87 """Enum for Bad Test Response States""" 88 89 NO_RESPONSE = 0 90 WRONG_PGN = 1 91 NACK = 2 92 WRONG_PACKETS = 3 93 INVALID_RESPONSE = 4 94 95 96class Errors: 97 """Holds different error messages""" 98 99 @staticmethod 100 def timeout() -> None: 101 """Prints message when connection times out""" 102 message = "Could not locate 6T" 103 logger.write_critical_to_report(message) 104 pytest.exit(message) 105 106 @staticmethod 107 def unexpected_packet(expected: str, frame: CANFrame) -> None: 108 """Prints message when unexpected packet is received""" 109 logger.write_warning_to_report(f"Expected {expected}, got {frame.pgn.short_name}") 110 111 @staticmethod 112 def no_packet(expected: str) -> None: 113 """Prints message when no packet is received""" 114 logger.write_warning_to_report(f"Expected {expected}, got None") 115 116 117# FIXME(JA): change issue to the prop issue / name issue 118 119 120def cmp( 121 a: float | str, 122 sign: Literal["<", "<=", ">", ">=", "=="], 123 b: float | str, 124 unit_a: str = "", 125 unit_b: str = "", 126 form: str = "", 127) -> str: 128 """Generate a formatted string based on a comparison.""" 129 if not unit_b: 130 unit_b = unit_a 131 132 if isinstance(a, str) or isinstance(b, str): 133 return f"{a:{form}}{unit_a} {('≠', '=')[a == b]} {b:{form}}{unit_b}" 134 135 sign_str = { 136 "<": ("≮", "<")[a < b], 137 "<=": ("≰", "≤")[a <= b], 138 ">": ("≯", ">")[a > b], 139 ">=": ("≱", "≥")[a >= b], 140 "==": ("≠", "=")[a == b], 141 } 142 143 return f"{a:{form}}{unit_a} {sign_str[sign]} {b:{form}}{unit_b}" 144 145 146@pytest.fixture(scope="class", autouse=True) 147def reset_test_environment(): 148 """Before each test class, reset the 6T.""" 149 150 try: 151 power_cycle_frame = CANFrame( 152 destination_address=BATTERY_INFO.address, 153 pgn=PGN["PropA"], 154 data=[0, 0, 1, 1, 1, not HARD_RESET, 1, 3, 0, -1], 155 ) 156 maintenance_mode_frame = CANFrame( 157 destination_address=BATTERY_INFO.address, 158 pgn=PGN["PropA"], 159 data=[0, 0, 1, 1, 1, 3, 1, 3, 0, -1], 160 ) 161 factory_reset_frame = CANFrame( 162 destination_address=BATTERY_INFO.address, 163 pgn=PGN["PropA"], 164 data=[1, 0, 0, -1, 3, 0xF, 3, 0, 0x1F, -1], 165 ) 166 with CANBus(BATTERY_CHANNEL) as bus: 167 logger.write_info_to_report("Power-Cycling 6T") 168 bus.process_call(power_cycle_frame) 169 time.sleep(10) 170 logger.write_info_to_report("Entering maintenance mode") 171 bus.process_call(maintenance_mode_frame) 172 logger.write_info_to_report("Factory resetting 6T") 173 bus.process_call(factory_reset_frame) 174 time.sleep(10) 175 except AttributeError: # Battery has not yet been found 176 return 177 178 179class TestLocate6T: 180 """Scan for 6T battery.""" 181 182 def test_locate_6t(self) -> None: 183 """ 184 | Description | Scan the bus for devices | 185 | :------------------- | :--------------------------------------------------------------- | 186 | GitHub Issue | turnaroundfactor/BMS-HW-Test#396 | 187 | Instructions | 1. Request the names of everyone on the bus </br>\ 188 2. Use the response data for all communication | 189 | Estimated Duration | 1 second | 190 """ 191 name_request = CANFrame(pgn=PGN["Request"], data=[PGN["Address Claimed"].id]) 192 with CANBus(BATTERY_CHANNEL) as bus: 193 if name_frame := bus.process_call(name_request): 194 # Save responses to profile 195 BATTERY_INFO.id = int(name_frame.data[0]) 196 BATTERY_INFO.manufacturer_code = name_frame.data[1] 197 if (mfg_name := spn_types.manufacturer_id(name_frame.data[1])) == "Reserved": 198 mfg_name = f"Unknown ({name_frame.data[1]})" 199 BATTERY_INFO.manufacturer_name = mfg_name 200 BATTERY_INFO.address = name_frame.source_address 201 else: 202 message = "Could not locate 6T" 203 logger.write_warning_to_html_report(message) 204 pytest.exit(message) 205 206 # Log results 207 printable_id = "".join( 208 chr(i) if chr(i).isprintable() else "." for i in BATTERY_INFO.id.to_bytes(3, byteorder="big") 209 ) 210 logger.write_result_to_html_report( 211 f"Found {BATTERY_INFO.manufacturer_name} 6T at address {BATTERY_INFO.address} " 212 f"(ID: {BATTERY_INFO.id:06X}, ASCII ID: {printable_id})" 213 ) 214 215 216class TestNameInformation: 217 """Check Unique ID (Identity Number) field in Address Claimed message""" 218 219 def test_name_information(self) -> None: 220 """ 221 | Description | Confirm information in NAME (address claimed) matches \ 222 expected value. | 223 | :------------------- | :--------------------------------------------------------------- | 224 | GitHub Issue | turnaroundfactor/BMS-HW-Test#396 | 225 | Instructions | 1. Request Address Claimed data </br>\ 226 2. Log returned values </br>\ 227 3. Validate if values meet spec requirements | 228 | Estimated Duration | 1 second | 229 """ 230 231 name_values = {} 232 failed_values = [] 233 234 name_request = CANFrame(pgn=PGN["Request"], data=[PGN["Address Claimed"].id]) 235 with CANBus(BATTERY_CHANNEL) as bus: 236 if name_frame := bus.process_call(name_request): 237 name_values["identity_number"] = int(name_frame.data[0]) 238 vehicle_system_instance = 0 239 240 for spn, elem in zip(PGN["Address Claimed"].data_field, name_frame.data): 241 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 242 logger.write_info_to_report(f"{spn.name}: {elem} {description}") 243 244 if spn.name in ("Manufacturer Code", "Reserved"): 245 continue 246 247 high_range = 0 248 if spn.name == "Identity Number": 249 high_range = 2097151 250 251 if spn.name == "ECU Instance": 252 high_range = 7 253 254 if spn.name == "Function Instance": 255 high_range = 31 256 257 if spn.name == "Function": 258 high_range = 254 259 260 if spn.name == "Vehicle System Instance": 261 high_range = 15 262 vehicle_system_instance = elem 263 264 if spn.name == "Industry Group": 265 high_range = 7 266 267 if spn.name == "Arbitrary Address Capable": 268 high_range = 1 269 270 if spn.name == "Vehicle System": 271 if elem not in (127, 0, vehicle_system_instance): 272 logger.write_warning_to_html_report( 273 f"Vehicle System {elem} is not 127, 0, or the same value as " f"Vehicle System Instance" 274 ) 275 failed_values.append(spn.name) 276 else: 277 if not 0 <= elem <= high_range: 278 logger.write_warning_to_html_report( 279 f"{spn.name}: {cmp(elem, '>=', 0)} and " f"{cmp(elem, '<=', high_range)}" 280 ) 281 failed_values.append(spn.name) 282 283 else: 284 message = f"No response to PGN {PGN['Address Claimed', [32]].id} (Address Claimed)" 285 logger.write_failure_to_html_report(message) 286 pytest.fail(message) 287 288 if len(failed_values) > 0: 289 categories = "data values" if len(failed_values) > 1 else "data value" 290 message = ( 291 f"{len(failed_values)} {categories} failed Address Claimed specifications: {', '.join(failed_values)}" 292 ) 293 logger.write_failure_to_html_report(message) 294 pytest.fail(message) 295 296 logger.write_result_to_html_report( 297 f"Found Identity Number: {name_values['identity_number']} (0x{name_values['identity_number']:06x}) " 298 f"for {BATTERY_INFO.manufacturer_name} 6T at address {BATTERY_INFO.address}" 299 ) 300 301 302class TestSoftwareIdentificationInformation: 303 """Retrieve & Validate Software Identification Information""" 304 305 def test_software_identification_information(self) -> None: 306 """ 307 | Description | Validate Software Identification | 308 | :------------------- | :--------------------------------------------------------------- | 309 | GitHub Issue | turnaroundfactor/BMS-HW-Test#394 | 310 | Instructions | 1. Request Software Identification </br>\ 311 2. Validate Software Identification </br>\ 312 3. Log Response | 313 | Estimated Duration | 1 second | 314 """ 315 316 soft_request = CANFrame(pgn=PGN["RQST"], data=[0xFEDA]) 317 soft_data = [] 318 with CANBus(BATTERY_CHANNEL) as bus: 319 if soft_frame := bus.process_call(soft_request): 320 # Complete RTS 321 if len(soft_frame.data) < 3: 322 message = ( 323 f"Unexpected byte response from PGN " 324 f"{PGN['Software Identification', [32]].id} (Software Identification)" 325 ) 326 logger.write_warning_to_html_report(message) 327 return 328 expected_bytes = int(soft_frame.data[1]) 329 expected_packets = 0 330 if BATTERY_INFO.manufacturer_code == ManufacturerID.BRENTRONICS: 331 expected_packets = int(soft_frame.data[2] + 1) 332 else: 333 expected_packets = int(soft_frame.data[2]) 334 335 rts_pgn_id = int(soft_frame.data[-1]) 336 cts_request = CANFrame(pgn=PGN["TP.CM"], data=[17, 0, 1, 0xFF, 0]) 337 cts_request.data[1] = expected_packets 338 cts_request.data[-1] = rts_pgn_id 339 340 # Send CTS 341 bus.send_message(cts_request.message()) 342 343 # Read & Store data from packets 344 for i in range(expected_packets): 345 data_frame = bus.read_frame() 346 if data_frame.pgn.id != 0xEB00: 347 message = f"Expected data frame {i + 1}, got PGN {data_frame.pgn.id}" 348 logger.write_failure_to_html_report(message) 349 pytest.fail(message) 350 soft_data.append(hex(int(data_frame.data[1]))) 351 352 if len(soft_data) != expected_packets: 353 message = f"Expected {expected_packets} packets, got {len(soft_data)}" 354 logger.write_failure_to_html_report(message) 355 pytest.fail(message) 356 357 # Send acknowledgement frame 358 end_acknowledge_frame = CANFrame(pgn=PGN["TP.CM"], data=[19, 0, 0, 0xFF, 0]) 359 end_acknowledge_frame.data[1] = expected_bytes 360 end_acknowledge_frame.data[2] = expected_packets 361 end_acknowledge_frame.data[-1] = rts_pgn_id 362 363 bus.send_message(end_acknowledge_frame.message()) 364 else: 365 message = f"No response for PGN {PGN['Software Identification', [32]].id} (Software Identification)" 366 logger.write_warning_to_html_report(message) 367 return 368 369 hex_string = "0x" 370 add_to_data = False 371 finish_adding = False 372 373 # Find Software Identification Number 374 for element in soft_data: 375 n = len(element) - 1 376 while n >= 1: 377 if add_to_data: 378 if element[n - 1 : n + 1] == "2a": 379 finish_adding = True 380 break 381 if element[n - 1 : n + 1] != "0x": 382 hex_string += element[n - 1 : n + 1] 383 n -= 2 384 else: 385 n -= 2 386 else: 387 if element[n - 3 : n + 1] == "2a31" or element[n - 3 : n + 1] == "2a01": 388 add_to_data = True 389 n -= 4 390 else: 391 n -= 4 392 if finish_adding: 393 break 394 395 if len(hex_string) <= 2: 396 logger.write_warning_to_html_report( 397 f"Software Identification: {hex_string} was not found in expected format" 398 ) 399 return 400 401 software_identification = spn_types.ascii_map(int(hex_string, 16)) 402 403 if re.search(r"[0-9]{2}\.[0-9]{2}\.[a-z]{2}\.[a-z]{2,}", software_identification) is None: 404 message = f"Software Identification: {software_identification} was not in expected format: MM.II.mm.aa.ee" 405 logger.write_failure_to_html_report(message) 406 pytest.fail(message) 407 408 logger.write_result_to_html_report(f"Software Identification: {software_identification}") 409 410 411class TestBatteryRegulationInformation: 412 """Retrieve and validate battery regulation information""" 413 414 def test_battery_regulation_information(self) -> None: 415 """ 416 | Description | Validate Battery Regulation Information 1 & 2 | 417 | :------------------- | :--------------------------------------------------------------- | 418 | GitHub Issue | turnaroundfactor/BMS-HW-Test#392 | 419 | Instructions | 1. Request Battery Regulation Information 1 </br>\ 420 2. Validate Information </br>\ 421 3. Log Response | 422 | Estimated Duration | 2 seconds | 423 """ 424 425 battery_regulation_one_request = CANFrame(pgn=PGN["RQST"], data=[0xFF03]) 426 battery_regulation_two_request = CANFrame(pgn=PGN["RQST"], data=[0xFF04]) 427 428 # Obtain Battery Regulation One Information 429 with CANBus(BATTERY_CHANNEL) as bus: 430 logger.write_result_to_html_report( 431 "<span style='font-weight: bold'>Battery Regulation Information 1 </span>" 432 ) 433 if battery_frame := bus.process_call(battery_regulation_one_request): 434 435 if battery_frame.pgn.id != PGN["PropB_03", [32]].id: 436 message = f"Expected {PGN['PropB_03', [32]].id} 0xFF03, but received PGN {battery_frame.pgn.id}" 437 logger.write_warning_to_html_report(message) 438 439 for spn, elem in zip(PGN["PropB_03"].data_field, battery_frame.data): 440 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 441 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 442 443 high_range = 3212.75 444 low_range = 0.0 445 if spn.name == "Battery Current": 446 high_range = 1600.00 447 low_range = -1600.00 448 449 if not low_range <= elem <= high_range: 450 logger.write_warning_to_html_report( 451 f"{spn.name}: {cmp(elem, '>=', low_range)} and " f"{cmp(elem, '<=', high_range)}" 452 ) 453 else: 454 message = f"Did not receive response for PGN {PGN['PropB_03', [32]].id} (Battery Regulation One)" 455 logger.write_warning_to_html_report(message) 456 457 # Obtain Battery Regulation Two Information 458 with CANBus(BATTERY_CHANNEL) as bus: 459 logger.write_result_to_html_report( 460 "<span style='font-weight: bold'>Battery Regulation Information 2</span>" 461 ) 462 if battery_frame_two := bus.process_call(battery_regulation_two_request): 463 464 if battery_frame_two.pgn.id != PGN["PropB_04", [32]].id: 465 message = f"Expected PGN {PGN['PropB_04', [32]].id}, but received PGN {battery_frame_two.pgn.id}" 466 logger.write_warning_to_html_report(message) 467 468 for spn, elem in zip(PGN["PropB_04"].data_field, battery_frame_two.data): 469 470 if spn.name == "Reserved": 471 continue 472 473 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 474 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 475 476 high_range = 3 477 if spn.name == "Bus Voltage Request": 478 high_range = 3212.75 479 480 if spn.name == "Transportability SOC": 481 high_range = 100 482 483 if not 0 <= elem <= high_range: 484 logger.write_warning_to_html_report( 485 f"{spn.name}: {cmp(elem, '>=', 0)} and {cmp(elem, '<=', high_range)}" 486 ) 487 488 else: 489 message = f"Did not receive response for PGN {PGN['PropB_04', [32]].id} (Battery Regulation Two)" 490 logger.write_warning_to_html_report(message) 491 492 493class TestConfigurationStateMessage: 494 """Confirms Configuration State Message matches profile after factory reset""" 495 496 def test_configuration_state_message(self) -> None: 497 """ 498 | Description | Validates if Configuration State Message matches profile | 499 | :------------------- | :--------------------------------------------------------------- | 500 | GitHub Issue | turnaroundfactor/BMS-HW-Test#393 | 501 | Instructions | 1. Request Configuration State </br>\ 502 2. Check if Configuration State matches default values </br>\ 503 3. Log results | 504 | Estimated Duration | 1 second | 505 """ 506 failed_values = [] 507 configuration_state_request = CANFrame(pgn=PGN["Request"], data=[PGN["Configuration State Message 1"].id]) 508 with CANBus(BATTERY_CHANNEL) as bus: 509 if configuration_frame := bus.process_call(configuration_state_request): 510 if configuration_frame.pgn.id != PGN["Configuration State Message 1", [32]].id: 511 message = ( 512 f"Received PGN {configuration_frame.pgn.id}, not PGN " 513 f"{PGN['Configuration State Message 1', [32]].id} " 514 ) 515 logger.write_failure_to_html_report(message) 516 pytest.fail(message) 517 518 data = configuration_frame.data 519 pgn_data_field = PGN["Configuration State Message 1"].data_field 520 521 if len(data) != len(pgn_data_field): 522 message = ( 523 f"Expected {len(pgn_data_field)} data fields, " 524 f"got {len(data)}. Data is missing from response" 525 ) 526 logger.write_failure_to_html_report(message) 527 pytest.fail(message) 528 529 logger.write_result_to_html_report("<span style='font-weight: bold'>Configuration State Message</span>") 530 531 for spn, elem in zip(pgn_data_field, data): 532 if spn.name == "Reserved": 533 continue 534 535 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 536 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 537 538 if not 0 <= elem <= 3: 539 message = ( 540 f"{spn.name}: {cmp(elem, '>=', 0, f'({spn.data_type(elem)})')} and " 541 f"{cmp(elem, '<=', 3, f'({spn.data_type(elem)})')}" 542 ) 543 logger.write_warning_to_html_report(message) 544 failed_values.append(spn.name) 545 continue 546 547 if spn.name in ("Battery Battle-override State", "Standby State", "Configure VPMS Function State"): 548 message = ( 549 f"{spn.name}: {cmp(elem, '==', 0, f'({spn.data_type(elem)})', f'({spn.data_type(0)})')}" 550 ) 551 if elem != 0: 552 logger.write_warning_to_html_report(message) 553 failed_values.append(spn.name) 554 555 if spn.name in ("Automated Heater Function State", "Contactor(s) Control State"): 556 message = ( 557 f"{spn.name}: {cmp(elem, '==', 1, f'({spn.data_type(elem)})', f'({spn.data_type(1)})')}" 558 ) 559 if elem != 1: 560 logger.write_warning_to_html_report(message) 561 failed_values.append(spn.name) 562 563 if len(failed_values) > 0: 564 categories = "data values" if len(failed_values) > 1 else "data value" 565 message = ( 566 f"{len(failed_values)} {categories} failed " 567 f"Configuration State Message specifications: {', '.join(failed_values)}" 568 ) 569 logger.write_failure_to_html_report(message) 570 pytest.fail(message) 571 572 else: 573 message = ( 574 f"Did not receive a response for PGN {PGN['Configuration State Message 1', [32]].id} " 575 f"(Configuration State Message 1)" 576 ) 577 logger.write_failure_to_html_report(message) 578 pytest.fail(message) 579 580 581def na_maintenance_mode(bus: CANBus) -> bool: 582 """This function will place battery in Maintenance Mode with Reset Value of "3".""" 583 584 maintenance_mode = CANFrame( # Memory access only works in maintenance mode 585 destination_address=BATTERY_INFO.address, 586 pgn=PGN["PropA"], 587 data=[0, 0, 1, 1, 1, 3, 1, 3, 0, -1], 588 ) 589 590 bus.send_message(maintenance_mode.message()) # Enter maintenance mode 591 if response := bus.read_input(): 592 ack_frame = CANFrame.decode(response.arbitration_id, response.data) 593 if ack_frame.pgn.id != 0xE800 and ack_frame.data[0] != 0: 594 logger.write_warning_to_html_report("Unable to enter maintenance mode") 595 return False 596 return True 597 598 message = "Received no maintenance mode response" 599 logger.write_warning_to_html_report(message) 600 return False 601 602 603class TestNameManagementMessage: 604 """This will test the mandatory NAME Management commands""" 605 606 def test_name_management_command(self) -> None: 607 """ 608 | Description | Test Name Management Command | 609 | :------------------- | :--------------------------------------------------------------- | 610 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 611 | Instructions | 1. Get NAME information from Address Claimed </br>\ 612 2. Change ECU value </br>\ 613 3. Test Name Management Command </br>\ 614 4. Check Values updated </br>\ 615 5. Log results | 616 | Estimated Duration | 1 second | 617 """ 618 619 old_ecu_value = 0 620 new_ecu_value = 0 621 checksum_value = 0 622 adopt_name_request_data: list[float] = [255, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 623 address_request = CANFrame(pgn=PGN["Request"], data=[PGN["Address Claimed"].id]) 624 625 with CANBus(BATTERY_CHANNEL) as bus: 626 na_maintenance_mode(bus) 627 628 # Name Management Test 629 if address_claimed := bus.process_call(address_request): 630 if address_claimed.pgn.id != PGN["Address Claimed", [32]].id: 631 Errors.unexpected_packet("Address Claimed", address_claimed) 632 message = f"Unexpected packet PGN {address_claimed.pgn.id} was received" 633 logger.write_failure_to_html_report(message) 634 pytest.fail(message) 635 636 checksum_value = sum(address_claimed.packed_data) & 0xFF 637 old_ecu_value = address_claimed.data[2] 638 if old_ecu_value > 0: 639 new_ecu_value = 0 640 else: 641 new_ecu_value = 1 642 else: 643 message = f"No response received for PGN {[PGN['Address Claimed', [32]].id]}" 644 logger.write_result_to_html_report(message) 645 646 if not checksum_value: 647 message = "Could not condense Name into bits for NAME Management" 648 logger.write_failure_to_html_report(message) 649 pytest.fail(message) 650 651 request_data: list[float] = [ 652 checksum_value, 653 1, 654 0, 655 1, 656 1, 657 1, 658 1, 659 1, 660 1, 661 0, 662 1, 663 1, 664 new_ecu_value, 665 1, 666 1, 667 1, 668 1, 669 1, 670 1, 671 1, 672 ] 673 name_management_set_name = CANFrame( 674 destination_address=BATTERY_INFO.address, pgn=PGN["NAME Management Message"], data=request_data 675 ) 676 677 if pending_response := bus.process_call(name_management_set_name): 678 if pending_response.pgn.id != PGN["NAME Management Message", [32]].id: 679 if pending_response.pgn.id == PGN["Acknowledgement", [32]].id: 680 message = ( 681 f"Battery may not support NAME Management, received PGN " 682 f"{pending_response.pgn.id} ({spn_types.acknowledgement(pending_response.data[0])})" 683 ) 684 logger.write_failure_to_html_report(message) 685 else: 686 Errors.unexpected_packet("NAME Management Message", pending_response) 687 message = f"Unexpected PGN {pending_response.pgn.id} received" 688 logger.write_failure_to_html_report(message) 689 pytest.fail(message) 690 691 if pending_response.data[0] == 3: 692 message = ( 693 f"Message was unsuccessful, received error: " 694 f"{spn_types.name_error_code(pending_response.data[0])}" 695 ) 696 logger.write_failure_to_html_report(message) 697 pytest.fail(message) 698 699 if pending_response.data[12] != new_ecu_value: 700 message = f"ECU Value was not changed from {old_ecu_value} to {new_ecu_value}" 701 logger.write_failure_to_html_report(message) 702 pytest.fail(message) 703 else: 704 message = ( 705 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 706 ) 707 logger.write_failure_to_html_report(message) 708 pytest.fail(message) 709 710 adopt_name_request = CANFrame( 711 destination_address=BATTERY_INFO.address, 712 pgn=PGN["NAME Management Message"], 713 data=adopt_name_request_data, 714 ) 715 716 bus.send_message(adopt_name_request.message()) 717 718 current_name_request_data: list[float] = [255, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 719 current_name_request = CANFrame( 720 destination_address=BATTERY_INFO.address, 721 pgn=PGN["NAME Management Message"], 722 data=current_name_request_data, 723 ) 724 725 if name_management_response := bus.process_call(current_name_request): 726 if name_management_response.data[12] != new_ecu_value: 727 message = ( 728 f"Name's ECU Instance was not updated after Name Management Changes, " 729 f"{cmp(name_management_response.data[12], '==', new_ecu_value)}" 730 ) 731 logger.write_failure_to_html_report(message) 732 pytest.fail(message) 733 else: 734 message = ( 735 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 736 ) 737 logger.write_failure_to_html_report(message) 738 pytest.fail(message) 739 740 if new_address_claimed := bus.process_call(address_request): 741 if new_address_claimed.data[2] != new_ecu_value + 1: 742 message = ( 743 f"Address Claimed was not updated after Name Management Changes. " 744 f"{cmp(new_address_claimed.data[2], '==', new_ecu_value + 1)}" 745 ) 746 logger.write_failure_to_html_report(message) 747 pytest.fail(message) 748 749 logger.write_result_to_html_report( 750 f"Name Management Command was successful. " 751 f"ECU Instance was changed from {old_ecu_value} to {new_ecu_value}" 752 ) 753 754 def test_wrong_name_management_data(self): 755 """ 756 | Description | Test Incorrect Data for NAME Management Command | 757 | :------------------- | :--------------------------------------------------------------- | 758 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 759 | Instructions | 1. Provide wrong Checksum value for Name Command </br>\ 760 2. Check receive Checksum error code as response </br>\ 761 3. Log results | 762 | Estimated Duration | 1 second | 763 """ 764 wrong_checksum = 0 765 new_ecu_value = 0 766 767 current_name_request_data = [255, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 768 current_name_request = CANFrame( 769 destination_address=BATTERY_INFO.address, 770 pgn=PGN["NAME Management Message"], 771 data=current_name_request_data, 772 ) 773 774 with CANBus(BATTERY_CHANNEL) as bus: 775 # Test Checksum Error -- Should return Error Code: 3 776 na_maintenance_mode(bus) 777 778 if current_name := bus.process_call(current_name_request): 779 if current_name.pgn.id != PGN["NAME Management Message", [32]].id: 780 if current_name.pgn.id == PGN["Acknowledgement", [32]].id: 781 message = ( 782 f"Battery may not support NAME Management, received PGN " 783 f"{current_name.pgn.id} ({spn_types.acknowledgement(current_name.data[0])})" 784 ) 785 logger.write_failure_to_html_report(message) 786 pytest.fail(message) 787 Errors.unexpected_packet("NAME Management Message", current_name) 788 message = f"Unexpected packet was received: PGN {current_name.pgn.id}" 789 logger.write_failure_to_html_report(message) 790 pytest.fail(message) 791 792 wrong_checksum = sum(current_name.packed_data) & 0xFF 793 old_ecu_value = current_name.data[12] 794 if old_ecu_value > 0: 795 new_ecu_value = 0 796 else: 797 new_ecu_value = 1 798 else: 799 message = ( 800 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 801 ) 802 logger.write_failure_to_html_report(message) 803 pytest.fail(message) 804 805 request_data = [wrong_checksum, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, new_ecu_value, 1, 1, 1, 1, 1, 1, 1] 806 name_management_set_name = CANFrame( 807 destination_address=BATTERY_INFO.address, pgn=PGN["NAME Management Message"], data=request_data 808 ) 809 810 if pending_response := bus.process_call(name_management_set_name): 811 if pending_response.pgn.id != PGN["NAME Management Message", [32]].id: 812 if pending_response.pgn.id == PGN["Acknowledgement", [32]].id: 813 message = ( 814 f"Battery may not support NAME Management, received PGN {pending_response.pgn.id} " 815 f"({spn_types.acknowledgement(pending_response.data[0])})" 816 ) 817 logger.write_failure_to_html_report(message) 818 pytest.fail(message) 819 Errors.unexpected_packet("NAME Management Message", pending_response) 820 message = f"Unexpected packet was received: {pending_response.pgn.id}" 821 logger.write_failure_to_html_report(message) 822 pytest.fail(message) 823 824 if pending_response.data[0] != 3: 825 message = cmp( 826 pending_response.data[0], 827 "==", 828 3, 829 f"({spn_types.name_error_code(pending_response.data[0])})", 830 f"({spn_types.name_error_code(3)})", 831 ) 832 logger.write_failure_to_html_report( 833 f"PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message) " 834 f"updated NAME Management with incorrect checksum value" 835 ) 836 logger.write_failure_to_html_report(message) 837 pytest.fail(message) 838 else: 839 message = ( 840 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 841 ) 842 logger.write_failure_to_html_report(message) 843 pytest.fail(message) 844 845 logger.write_result_to_html_report( 846 "NAME Management successfully did not process request with incorrect checksum value" 847 ) 848 849 850class TestActiveDiagnosticTroubleCodes: 851 """Test that Active Diagnostic Trouble Codes are J1939 Compliant""" 852 853 def test_active_diagnostic_trouble_codes(self) -> None: 854 """ 855 | Description | Test Active Diagnostic Trouble Codes (DM1) | 856 | :------------------- | :--------------------------------------------------------------- | 857 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 858 | Instructions | 1. Request Active Diagnostic Trouble Codes </br>\ 859 2. Validate data is within specifications </br>\ 860 3. Log results | 861 | Estimated Duration | 1 second | 862 """ 863 dm1_request = CANFrame(pgn=PGN["Request"], data=[PGN["Active Diagnostic Trouble Codes", [32]].id]) 864 failed_values = [] 865 with CANBus(BATTERY_CHANNEL) as bus: 866 if dm1_frame := bus.process_call(dm1_request): 867 if dm1_frame.pgn.id != PGN["Active Diagnostic Trouble Codes", [32]].id: 868 Errors.unexpected_packet("Active Diagnostic Trouble Codes", dm1_frame) 869 message = f"Unexpected data packet PGN {dm1_frame.pgn.id} was received" 870 logger.write_failure_to_html_report(message) 871 pytest.fail(message) 872 873 logger.write_result_to_html_report( 874 "<span style='font-weight: bold'>Active Diagnostic Trouble Codes</span>" 875 ) 876 877 dm1_data = dm1_frame.data 878 pgn_data_field = PGN["Active Diagnostic Trouble Codes"].data_field 879 880 for spn, elem in zip(pgn_data_field, dm1_data): 881 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 882 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 883 high_value = 1 884 885 if spn.name in ( 886 "Protect Lamp", 887 "Amber Warning Lamp", 888 "Red Stop Lamp", 889 "Malfunction Indicator Lamp", 890 "DTC1.SPN_Conversion_Method", 891 ): 892 high_value = 1 893 894 if spn.name in ( 895 "Flash Protect Lamp", 896 "Flash Amber Warning Lamp", 897 "Flash Red Stop Lamp", 898 "Flash Malfunction Indicator Lamp", 899 ): 900 high_value = 3 901 902 if spn.name == "DTC1.Suspect_Parameter_Number": 903 high_value = 524287 904 905 if spn.name == "DTC1.Failure_Mode_Identifier": 906 high_value = 31 907 908 if spn.name == "DTC1.Occurrence_Count": 909 high_value = 126 910 911 if not 0 <= elem <= high_value: 912 logger.write_warning_to_html_report( 913 f"{spn.name}: {cmp(elem, '>=', 0, description)} and " 914 f"{cmp(elem, '<=', high_value, description)}" 915 ) 916 failed_values.append(spn.name) 917 918 if len(failed_values) > 0: 919 categories = "data values" if len(failed_values) > 1 else "data value" 920 message = ( 921 f"{len(failed_values)} {categories} failed " 922 f"Active Diagnostic Trouble Code specifications: {', '.join(failed_values)}" 923 ) 924 logger.write_failure_to_html_report(message) 925 pytest.fail(message) 926 927 else: 928 message = ( 929 f"No response was received for mandatory command: " 930 f"PGN {PGN['Active Diagnostic Trouble Codes', [32]].id} (Active Diagnostic Trouble Codes)" 931 ) 932 logger.write_failure_to_html_report(message) 933 pytest.fail(message) 934 935 936# TODO(DF): Fix Stop Start Broadcast Test when battery is able to broadcast, test currently blocked 937# class TestStopStartBroadcast: 938# """Test that Stop/Start Broadcast is J1939 Compliant""" 939# 940# @pytest.mark.profile 941# def test_profile(self) -> None: 942# """ 943# | Description | Test that Stop/Start Broadcast (DM13) | 944# | :------------------- | :--------------------------------------------------------------- | 945# | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 946# | Instructions | 1. Create Start Broadcast request </br>\ 947# 2. Check broadcast began </br>\ 948# 3. Create Stop Broadcast request </br>\ 949# 4. Check broadcast stopped </br>\ 950# 5. Create Suspend Broadcast request </br>\ 951# 6. Check broadcast suspended </br>\ 952# 7. Log results | 953# | Estimated Duration | 1 second | 954# """ 955# 956# with CANBus(BATTERY_CHANNEL) as bus: 957# na_maintenance_mode(bus) 958# TODO(DF): Determine order of data (either SAFT or order in j1939da.py) 959# broadcast_message_data = { 960# "current_data_link": 1, 961# "sae_j1587": 3, 962# "sae_j1922": 3, 963# "sae_j1939_network_1": 1, 964# "sae_j1939_network_2": 3, 965# "iso_9141": 3, 966# "sae_j1850": 3, 967# "other_manufacturer_specified_port": 3, 968# "sae_j1939_network #3": 3, 969# "proprietary_network_1": 3, 970# "proprietary_network_2": 3, 971# "sae_j1939_network_4": 3, 972# "hold_signal": 15, 973# "suspend_signal": 255, 974# "suspend_duration": 0, 975# "sae_j1939_network_5": 3, 976# "sae_j1939_network_6": 3, 977# "sae_j1939_network_7": 3, 978# "sae_j1939_network_8": 3, 979# "reserved": 2, 980# "sae_j1939_network_9": 3, 981# "sae_j1939_network_10": 3, 982# "sae_j1939_network_11": 3, 983# } 984# # Order from SAFT Documentation 985# # broadcast_message_data = { 986# # "sae_j1939_network_1": 1, 987# # "sae_j1922": 3, 988# # "sae_j1587": 3, 989# # "current_data_link": 1, 990# # "other_manufacturer_specified_port": 3, 991# # "sae_j1850": 3, 992# # "iso_9141": 3, 993# # "sae_j1939_network_2": 3, 994# # "sae_j1939_network_4": 3, 995# # "proprietary_network_2": 3, 996# # "proprietary_network_1": 3, 997# # "sae_j1939_network #3": 3, 998# # "suspend_signal": 255, 999# # "hold_signal": 15, 1000# # "suspend_duration": 0, 1001# # "sae_j1939_network_8": 3, 1002# # "sae_j1939_network_7": 3, 1003# # "sae_j1939_network_6": 3, 1004# # "sae_j1939_network_5": 3, 1005# # "sae_j1939_network_11": 3, 1006# # "sae_j1939_network_10": 3, 1007# # "sae_j1939_network_9": 3, 1008# # "reserved": 2, 1009# # } 1010# 1011# dm1_start_request_data = list(broadcast_message_data.values()) 1012# 1013# # Start Broadcast 1014# dm1_request = CANFrame(pgn=PGN["Stop Start Broadcast"], data=dm1_start_request_data) 1015# bus.send_message(dm1_request.message()) 1016# 1017# # Check broadcast 1018# if mystery_response := bus.read_input(): 1019# logger.write_info_to_report("Broadcast was successfully started") 1020# logger.write_info_to_report(f"First broadcast received: {mystery_response}") 1021# else: 1022# message = "Broadcast did not start running Stop Start Broadcast command" 1023# logger.write_warning_to_report(message) 1024# pytest.fail(message) 1025# 1026# # Stop Broadcast 1027# broadcast_message_data["sae_j1939_network_1"] = 3 1028# broadcast_message_data["current_data_link"] = 3 1029# broadcast_message_data["hold_signal"] = 1 1030# dm1_stop_request_data = list(broadcast_message_data.values()) 1031# 1032# dm1_request = CANFrame(pgn=PGN["Stop Start Broadcast"], data=dm1_stop_request_data) 1033# bus.send_message(dm1_request.message()) 1034# 1035# if mystery_response := bus.read_input(): 1036# message = "Broadcast did not stop running after Stop Start Broadcast command" 1037# logger.write_warning_to_report(message) 1038# logger.write_info_to_report(f"Broadcast received: {mystery_response}") 1039# 1040# else: 1041# message = "Broadcast was succesfully stopped after Stop Start Broadcast command" 1042# logger.write_info_to_report(message) 1043# 1044# broadcast_message_data["suspend_signal"] = 0 1045# dm1_suspend_request_data = list(broadcast_message_data.values()) 1046# 1047# dm1_request = CANFrame(pgn=PGN["Stop Start Broadcast"], data=dm1_suspend_request_data) 1048# bus.send_message(dm1_request.message()) 1049# 1050# if mystery_response := bus.read_input(): 1051# message = "Broadcast was not suspended after Stop Start Broadcast command" 1052# logger.write_warning_to_report(message) 1053# logger.write_warning_to_report(f"Broadcast received: {mystery_response}") 1054# else: 1055# message = "Broadcast was suspended after Stop Start Broadcast command" 1056# logger.write_info_to_report(message) 1057# 1058# logger.write_result_to_html_report("Stop Start Broadcast command was successful") 1059# 1060 1061 1062class TestVehicleElectricalPower: 1063 """Test Vehicle Electrical Power command is J1939 Compliant""" 1064 1065 def test_vehicle_electrical_power(self) -> None: 1066 """ 1067 | Description | Test Vehicle Electrical Power #5 (VEP5) command | 1068 | :------------------- | :--------------------------------------------------------------- | 1069 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 1070 | Instructions | 1. Send Vehicle Electrical Power #5 command </br>\ 1071 2. Validate data is within specifications </br>\ 1072 3. Log response | 1073 | Estimated Duration | 1 second | 1074 """ 1075 failed_values = [] 1076 with CANBus(BATTERY_CHANNEL) as bus: 1077 vehicle_electrical_power_request = CANFrame( 1078 destination_address=BATTERY_INFO.address, 1079 pgn=PGN["Request"], 1080 data=[PGN["Vehicle Electrical Power #5"].id], 1081 ) 1082 if vehicle_electrical_power_response := bus.process_call(vehicle_electrical_power_request): 1083 if vehicle_electrical_power_response.pgn.id != PGN["Vehicle Electrical Power #5", [32]].id: 1084 if vehicle_electrical_power_response.pgn.id == 59392: 1085 message = ( 1086 f"Received PGN {vehicle_electrical_power_response.pgn.id} " 1087 f"({spn_types.acknowledgement(vehicle_electrical_power_response.data[0])}), " 1088 f'not PGN {PGN["Vehicle Electrical Power #5", [32]].id} ' 1089 ) 1090 else: 1091 message = ( 1092 f"Received PGN {vehicle_electrical_power_response.pgn.id} " 1093 f"{vehicle_electrical_power_response.pgn.short_name}, " 1094 f'not PGN {PGN["Vehicle Electrical Power #5", [32]].id} ' 1095 ) 1096 logger.write_failure_to_html_report(message) 1097 pytest.fail(message) 1098 else: 1099 message = ( 1100 f"Battery did not respond to PGN {PGN['Vehicle Electrical Power #5', [32]].id} " 1101 f"Vehicle Electrical Power #5 request" 1102 ) 1103 logger.write_failure_to_html_report(message) 1104 pytest.fail(message) 1105 1106 vep5_data = vehicle_electrical_power_response.data 1107 vep5_pgn = PGN["Vehicle Electrical Power #5"].data_field 1108 1109 logger.write_result_to_html_report("<span style='font-weight: bold'>Vehicle Electrical Power #5</span>") 1110 1111 for spn, elem in zip(vep5_pgn, vep5_data): 1112 if spn.name == "Reserved": 1113 continue 1114 1115 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 1116 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 1117 high_value = 1.0 1118 if spn.name == "SLI Battery Pack State of Charge": 1119 high_value = 160.6375 1120 1121 if spn.name == "SLI Battery Pack Capacity": 1122 high_value = 64255 1123 1124 if spn.name == "SLI Battery Pack Health": 1125 high_value = 125 1126 1127 if spn.name == "SLI Cranking Predicted Minimum Battery Voltage": 1128 high_value = 50 1129 1130 if not 0 <= elem <= high_value: 1131 logger.write_warning_to_html_report( 1132 f"{spn.name}: {cmp(elem, '>=', 0, description)} and " 1133 f"{cmp(elem, '<=', high_value, description)}" 1134 ) 1135 failed_values.append(spn.name) 1136 1137 if len(failed_values) > 0: 1138 categories = "data values" if len(failed_values) > 1 else "data value" 1139 message = ( 1140 f"{len(failed_values)} {categories} failed " 1141 f"Configuration State Message specifications: {', '.join(failed_values)}" 1142 ) 1143 logger.write_failure_to_html_report(message) 1144 pytest.fail(message) 1145 1146 logger.write_result_to_html_report("Test Vehicle Electrical Power #5 command was successful") 1147 1148 1149class TestManufacturerCommands: 1150 """Test Manufacturer Commands are compliant with specs""" 1151 1152 def saft_commands_test(self): 1153 """Tests SAFT Manufacturer Commands""" 1154 with CANBus(BATTERY_CHANNEL) as bus: 1155 manufactured_command_request = CANFrame(destination_address=BATTERY_INFO.address, pgn=PGN["RQST"], data=[0]) 1156 invalid_response = [] 1157 for address in itertools.chain( 1158 [0xFFD2], range(0xFFD4, 0xFFD9), range(0xFFDC, 0xFFDF), range(0xFFE0, 0xFFE2), [0xFFE4] 1159 ): 1160 manufactured_command_request.data = [address] 1161 default_pgn = PGN[address] 1162 pgn_name = default_pgn.name 1163 logger.write_result_to_html_report( 1164 f"<span style='font-weight: bold'>PGN {address} ({pgn_name})---</span>" 1165 ) 1166 1167 if response_frame := bus.process_call(manufactured_command_request): 1168 if not response_frame.pgn.id == default_pgn.id: 1169 1170 if response_frame.pgn.id == PGN["ACKM", [32]].id: 1171 message = ( 1172 f"Expected {address} ({pgn_name}): Received PGN {response_frame.pgn.id} " 1173 f"({spn_types.acknowledgement(response_frame.data[0])}) " 1174 ) 1175 logger.write_warning_to_html_report(message) 1176 else: 1177 logger.write_warning_to_html_report( 1178 f"Expected PGN {address} ({pgn_name}), but received " 1179 f"{response_frame.pgn.id} ({response_frame.pgn.name}). " 1180 f"Unable to complete check for command" 1181 ) 1182 invalid_response.append(f"PGN {address} ({pgn_name})") 1183 continue 1184 else: 1185 message = f"Did not receive response from PGN {address} {pgn_name}" 1186 logger.write_warning_to_html_report(message) 1187 invalid_response.append(f"PGN {address} ({pgn_name})") 1188 1189 continue 1190 1191 if response_frame.priority != default_pgn.default_priority: 1192 message = ( 1193 f"Expected priority level of {default_pgn.default_priority}" 1194 f" but got priority level {response_frame.priority} for PGN {address}, {pgn_name}" 1195 ) 1196 invalid_response.append(f"PGN {address} {pgn_name}") 1197 logger.write_warning_to_html_report(message) 1198 1199 if len(response_frame.packed_data) != 8: 1200 message = ( 1201 f"Unexpected data length for PGN {address}, {pgn_name}. Expected length of 8, " 1202 f"received {len(response_frame.packed_data)}" 1203 ) 1204 logger.write_warning_to_html_report(message) 1205 1206 not_passed_elem = [] 1207 for spn, elem in zip(default_pgn.data_field, response_frame.data): 1208 low_range = 0 1209 high_range = 3 1210 spn_name = spn.name 1211 if spn.name == "Reserved": 1212 continue 1213 1214 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 1215 1216 logger.write_result_to_html_report(f"{spn_name}: {elem}{description}") 1217 1218 if pgn_name == "Battery ECU Status": 1219 if spn_name in ("Battery Mode", "FET Array State", "SOC Mode", "Heat Reason"): 1220 high_range = 7 1221 elif spn_name == "Long-Term Fault Log Status": 1222 high_range = 15 1223 elif spn_name == "Software Part Number": 1224 high_range = 64255 1225 1226 if pgn_name in ("Battery Cell Status 1", "Battery Cell Status 2"): 1227 high_range = 6.4255 1228 1229 if pgn_name == "Battery Performance": 1230 if spn_name == "Battery Current": 1231 low_range = -82000.00 1232 high_range = 82495.35 1233 if spn_name == "Internal State of Health": 1234 low_range = -204.800 1235 high_range = 204.775 1236 1237 if pgn_name == "Battery Temperatures": 1238 if spn_name == "MCU Temperature": 1239 low_range = -40 1240 high_range = 210 1241 else: 1242 low_range = -50 1243 high_range = 200 1244 1245 if pgn_name == "Battery Balancing Circuit Info": 1246 if spn.name == "Cell Voltage Difference": 1247 high_range = 6.4255 1248 if spn_name == "Cell Voltage Sum": 1249 high_range = 104.8576 1250 1251 if pgn_name in ("Battery Cell Upper SOC", "Battery Cell Lower SOC"): 1252 low_range = -10 1253 high_range = 115 1254 1255 if pgn_name == "Battery Function Status": 1256 if spn_name == "Heater Set Point": 1257 low_range = -50 1258 high_range = 25 1259 if spn_name == "Storage Delay Time Limit": 1260 high_range = 65535 1261 if spn_name == "Last Storage Duration (Minutes)": 1262 high_range = 59 1263 if spn_name == "Last Storage Duration (Hours)": 1264 high_range = 23 1265 if spn_name == "Last Storage Duration (Days)": 1266 high_range = 31 1267 if spn_name == "Last Storage Duration (Months)": 1268 high_range = 255 1269 if spn_name == "Effective Reset Time": 1270 high_range = 60 1271 1272 if not low_range <= elem <= high_range: 1273 logger.write_warning_to_html_report( 1274 f"{spn.name}: {cmp(elem, '>=', 0, description)} and " 1275 f"{cmp(elem, '<=', high_range, description)}" 1276 ) 1277 not_passed_elem.append(spn_name) 1278 1279 if len(not_passed_elem) == 0: 1280 message = f"✅ All data fields in PGN {default_pgn.id} ({pgn_name}) met requirements" 1281 logger.write_result_to_html_report(message) 1282 1283 if len(invalid_response) > 0: 1284 message = ( 1285 f"{len(invalid_response)} SAFT Manufacturer Command{'s' if len(invalid_response) > 1 else ''} " 1286 f"failed: {', '.join(invalid_response)}" 1287 ) 1288 logger.write_failure_to_html_report(message) 1289 pytest.fail(message) 1290 else: 1291 logger.write_result_to_html_report("All SAFT Manufacturer Commands passed") 1292 1293 def test_manufacturer_commands(self) -> None: 1294 """ 1295 | Description | Fingerprint Manufacturer Commands | 1296 | :------------------- | :--------------------------------------------------------------- | 1297 | GitHub Issue | turnaroundfactor/BMS-HW-Test#388 | 1298 | Instructions | 1. Send request for manufacturer command </br>\ 1299 2. Check values in data </br>\ 1300 3. Log response | 1301 | Estimated Duration | 2 seconds | 1302 """ 1303 1304 if BATTERY_INFO.manufacturer_code == ManufacturerID.SAFT: 1305 self.saft_commands_test() 1306 else: 1307 message = "No known manufacturer commands to test" 1308 logger.write_result_to_html_report(message) 1309 pytest.skip(message) 1310 1311 1312# class TestUDSSession: 1313# """Tests if batteries respond to UDS Session""" 1314# 1315# @pytest.mark.profile 1316# def test_profile(self) -> None: 1317# """ 1318# | Description | Test if batteries respond to UDS Session | 1319# | :------------------- | :--------------------------------------------------------------- | 1320# | GitHub Issue | turnaroundfactor/BMS-HW-Test#294 | 1321# | Instructions | 1. Send request for UDS Session </br>\ 1322# 2. Check if battery responds </br>\ 1323# 3. Log response | 1324# | Estimated Duration | 10 minutes | 1325# """ 1326# 1327# request_data_params = ["0000", "F18C"] 1328# 1329# can_256_ids = [] 1330# for number in range(256): 1331# can_id = 0x18DA00F1 | number << 8 1332# can_256_ids.append(can_id) 1333# 1334# read_data_commands = [] 1335# # Read Data by Identifier 1336# with CANBus(BATTERY_CHANNEL) as bus: 1337# for address in itertools.chain( 1338# [0x7DF], range(0x7E0, 0x7E8), range(0x7E8, 0x7F0), [0x18DB33F1], can_256_ids 1339# ): 1340# respond_command = [] 1341# for param in request_data_params: 1342# data = f"0322{param}AAAAAAAA" 1343# bytes_data = bytes.fromhex(data) 1344# bus.send_message(can.Message(arbitration_id=address, data=bytes_data)) 1345# if response := bus.read_input(timeout=0.5): 1346# data_frame = CANFrame.decode(response.arbitration_id, response.data) 1347# if data_frame.pgn.id != PGN["ACKM"].id: 1348# logger.write_info_to_report( 1349# f"UDS Read Memory by Address response received for address {address}: {response}" 1350# ) 1351# respond_command.append(data) 1352# 1353# if respond_command: 1354# read_data_commands.append(address) 1355# 1356# if read_data_commands: 1357# logger.write_info_to_report( 1358# f"The following CAN addresses responded to UDS request: {read_data_commands}" 1359# ) 1360# else: 1361# logger.write_info_to_report("No UDS responses were received for Read Data By Identifier") 1362# 1363# # Read Memory Address 1364# read_memory_commands = [] 1365# for address in itertools.chain( 1366# [0x7DF], range(0x7E0, 0x7E8), range(0x7E8, 0x7F0), [0x18DB33F1], can_256_ids 1367# ): 1368# respond_command = [] 1369# data = "03230000AAAAAAAA" 1370# bytes_data = bytes.fromhex(data) 1371# bus.send_message(can.Message(arbitration_id=address, data=bytes_data)) 1372# if response := bus.read_input(timeout=0.5): 1373# data_frame = CANFrame.decode(response.arbitration_id, response.data) 1374# if data_frame.pgn.id != PGN["ACKM"].id: 1375# logger.write_info_to_report( 1376# f"UDS Read Memory by Address response received for address {address}: {response}" 1377# ) 1378# respond_command.append(data) 1379# 1380# if respond_command: 1381# read_memory_commands.append(address) 1382# 1383# if read_memory_commands: 1384# logger.write_info_to_report( 1385# f"The following CAN addresses responded to UDS request: {read_memory_commands}" 1386# ) 1387# else: 1388# logger.write_info_to_report("No UDS responses were received for Read Memory By Address") 1389# 1390# _GENERATED_PROFILE.uds_read_data_commands = read_data_commands 1391# _GENERATED_PROFILE.uds_read_memory_commands = read_memory_commands 1392# 1393# def test_comparison(self) -> None: 1394# """ 1395# | Description | Compare UDS responses | 1396# | :------------------- | :--------------------------------------------------------------- | 1397# | GitHub Issue | turnaroundfactor/BMS-HW-Test#294 | 1398# | Instructions | 1. Check if profiles have similar UDS responses | 1399# | Pass / Fail Criteria | Pass if profiles are closely matched | 1400# | Estimated Duration | 1 second | 1401# """ 1402# 1403# if len(_GENERATED_PROFILE.uds_read_data_commands) == 0 and 1404# len(_COMPARISON_PROFILE.uds_read_data_commands) == 0: 1405# logger.write_result_to_report("Both profiles did not receive UDS responses") 1406# 1407# if ( 1408# len(_GENERATED_PROFILE.uds_read_memory_commands) == 0 1409# and len(_COMPARISON_PROFILE.uds_read_memory_commands) == 0 1410# ): 1411# logger.write_result_to_report("Both profiles did not receive UDS responses") 1412# 1413# unique_read_data_commands = set(_GENERATED_PROFILE.uds_read_data_commands) - set( 1414# _COMPARISON_PROFILE.uds_read_data_commands 1415# ) 1416# unique_read_memory_commands = set(_GENERATED_PROFILE.uds_read_memory_commands) - set( 1417# _COMPARISON_PROFILE.uds_read_memory_commands 1418# ) 1419# 1420# if unique_read_data_commands: 1421# logger.write_warning_to_report( 1422# f"Profiles did not match read data CAN addresses: {unique_read_data_commands}" 1423# ) 1424# 1425# if unique_read_memory_commands: 1426# logger.write_warning_to_report( 1427# f"Profiles did not match read memory CAN addresses: {unique_read_memory_commands}" 1428# ) 1429# 1430# if not unique_read_data_commands and not unique_read_memory_commands: 1431# logger.write_result_to_html_report("Profiles shared similar results for UDS requests") 1432# 1433 1434 1435class TestECUInformation: 1436 """Gets information about the physical ECU and its hardware""" 1437 1438 @staticmethod 1439 def bytes_to_ascii(bs: list[float]) -> list[str]: 1440 """Converts bytes to ASCII string""" 1441 s: str = "" 1442 for b in bs: 1443 h = re.sub(r"^[^0-9a-fA-F]+$", "", f"{b:x}") 1444 try: 1445 ba = bytearray.fromhex(h)[::-1] 1446 s += ba.decode("utf-8", "ignore") 1447 s = re.sub(r"[^\x20-\x7E]", "", s) 1448 except ValueError: 1449 # NOTE: This will ignore any invalid packets (from BrenTronics) 1450 logger.write_warning_to_report(f"Skipping invalid hex: {b:x}") 1451 return list(filter(None, s.split("*"))) 1452 1453 def test_ecu_information(self) -> None: 1454 """ 1455 | Description | Get information from ECUID response | 1456 | :------------------- | :--------------------------------------------------------------- | 1457 | GitHub Issue | turnaroundfactor/BMS-HW-Test#395 | 1458 | Instructions | 1. Request ECUID data </br>\ 1459 2. Validate data is within specifications </br>\ 1460 3. Log response | 1461 | Estimated Duration | 10 seconds | 1462 """ 1463 1464 info = [] 1465 with CANBus(BATTERY_CHANNEL) as bus: 1466 ecu_request = CANFrame(pgn=PGN["Request"], data=[PGN["ECUID"].id]) 1467 if tp_cm_frame := bus.process_call(ecu_request): 1468 if tp_cm_frame is not None: 1469 if tp_cm_frame.pgn.id == PGN["TP.CM", [32]].id: 1470 data = [] 1471 cts_request = CANFrame(pgn=PGN["TP.CM"], data=[17, 0, 1, 0xFF, 0]) 1472 if data_frame := bus.process_call(cts_request): 1473 if data_frame is not None: 1474 if data_frame.pgn.id == PGN["TP.DT"].id: 1475 packet_count = int(tp_cm_frame.data[2]) 1476 # NOTE: This will ignore the invalid header packet from BrenTronics 1477 if BATTERY_INFO.manufacturer_code != ManufacturerID.BRENTRONICS: 1478 data.append(data_frame.data[1]) 1479 packet_count -= 1 1480 for _ in range(packet_count): 1481 data_message = bus.read_input() 1482 if data_message is not None: 1483 frame = CANFrame.decode(data_message.arbitration_id, data_message.data) 1484 if frame.pgn.id == PGN["TP.DT"].id: 1485 data.append(frame.data[1]) 1486 else: 1487 Errors.unexpected_packet("TP.DT", frame) 1488 break 1489 else: 1490 Errors.no_packet("TP.DT") 1491 break 1492 info = self.bytes_to_ascii(data) 1493 eom_request = CANFrame( 1494 pgn=PGN["TP.CM"], 1495 data=[17, tp_cm_frame.data[1], packet_count, 0xFF, tp_cm_frame.data[-1]], 1496 ) 1497 if eom_frame := bus.process_call(eom_request): 1498 if eom_frame is not None: 1499 if eom_frame.pgn.id == PGN["DM15"].id: 1500 if eom_frame.data[2] == 4: # Operation Completed 1501 logger.write_info_to_report("ECUID data transfer successful") 1502 else: 1503 logger.write_warning_to_html_report("Unsuccessful EOM response") 1504 else: 1505 Errors.unexpected_packet("DM15", eom_frame) 1506 else: 1507 Errors.no_packet("DM15") 1508 else: 1509 # timeout 1510 logger.write_warning_to_report("No response after sending EOM (DM15)") 1511 else: 1512 Errors.unexpected_packet("TP.DT", data_frame) 1513 else: 1514 Errors.no_packet("TP.DT") 1515 else: 1516 message = f"Did not receive response from PGN {PGN['TP.CM', [32]].id} (TP.CM)" 1517 logger.write_failure_to_html_report(message) 1518 pytest.fail(message) 1519 else: 1520 Errors.unexpected_packet("TP.CM", tp_cm_frame) 1521 else: 1522 Errors.no_packet("TP.CM") 1523 else: 1524 message = f"Did not receive response from PGN {PGN['ECUID', [32]].id}" 1525 logger.write_failure_to_html_report(message) 1526 pytest.fail(message) 1527 1528 if len(info) > 0: 1529 ecu = { 1530 "part_number": info[0], 1531 "serial_number": info[1], 1532 "location_name": info[2], 1533 "manufacturer": info[3], 1534 "classification": info[4], 1535 } 1536 logger.write_result_to_html_report("<span style='font-weight: bold'>ECUID Information </span>") 1537 for key, value in ecu.items(): 1538 logger.write_result_to_html_report(f"{key.strip().replace('_', ' ').title()}: {value}") 1539 else: 1540 logger.write_failure_to_html_report("Could not get ECU information") 1541 pytest.fail("Could not get ECU information") 1542 1543 1544def memory_test(mode: Modes) -> list[int]: 1545 """Memory tester helper""" 1546 found_addresses = [] 1547 with CANBus(BATTERY_CHANNEL) as bus: 1548 read_request_frame = CANFrame(pgn=PGN["DM14"], data=[1, 1, 1, 0, 0, 0, 0, 0]) 1549 read_request_frame.data[2] = mode 1550 addresses = [0, 1107296256, 2147483648, 4294966271] # 0x0, 0x42000000, 0x80000000, 0xfffffbff 1551 for low_address in addresses: 1552 found = 0 1553 high_address = low_address + (1024 if FULL_MEMORY_TESTS else 16) 1554 for i in range(low_address, high_address): 1555 read_request_frame.data[5] = low_address 1556 if response_frame := bus.process_call(read_request_frame, timeout=1): # NOTE: SAFT times out 1557 if response_frame is not None: 1558 logger.write_info_to_report( 1559 f"Address {i} responded with PGN {response_frame.pgn.id} " 1560 f"({response_frame.pgn.short_name}) - status is: " 1561 f"{spn_types.dm15_status(response_frame.data[3])}" 1562 ) 1563 if response_frame.pgn.id == PGN["DM15"].id: 1564 if response_frame.data[5] != 258: # Invalid Length 1565 if response_frame.data[3] == 0: 1566 found_addresses.append(i) 1567 found += 1 1568 else: 1569 Errors.unexpected_packet("DM15", response_frame) 1570 break 1571 else: 1572 Errors.no_packet("DM15") 1573 break 1574 else: 1575 # timeout 1576 pass 1577 verb = "" 1578 match mode: 1579 case Modes.READ: 1580 verb = "readable" 1581 case Modes.WRITE: 1582 verb = "writable" 1583 case Modes.ERASE: 1584 verb = "erasable" 1585 case Modes.BOOT: 1586 verb = "boot load" 1587 message = ( 1588 f"Found {found} {verb} successful address(es) in memory ranges" 1589 f" {hex(low_address)}-{hex(high_address)}" 1590 ) 1591 logger.write_result_to_html_report(message) 1592 1593 if len(found_addresses) > 0: 1594 logger.write_result_to_html_report( 1595 f"Found {len(found_addresses)} {verb} memory address(es) out of " 1596 f"{4096 if FULL_MEMORY_TESTS else 64} possible addresses" 1597 ) 1598 else: 1599 message = f"Found 0 {verb} memory addresses out of {4096 if FULL_MEMORY_TESTS else 64} possible addresses" 1600 logger.write_warning_to_html_report(message) 1601 return found_addresses 1602 1603 1604class TestMemoryRead: 1605 """This will test the read capability of the memory""" 1606 1607 def test_read(self) -> None: 1608 """ 1609 | Description | Try to read from different memory locations | 1610 | :------------------- | :--------------------------------------------------------------- | 1611 | GitHub Issue | turnaroundfactor/BMS-HW-Test#379 </br>\ 1612 turnaroundfactor/BMS-HW-Test#380 | 1613 | Instructions | 1. Request memory read </br>\ 1614 2. Log any successful addresses | 1615 | Estimated Duration | 1 minute | 1616 """ 1617 memory_test(Modes.READ) 1618 1619 1620class TestMemoryWrite: 1621 """This will test the write capability of the memory""" 1622 1623 def test_write(self) -> None: 1624 """ 1625 | Description | Try to write to different memory locations | 1626 | :------------------- | :--------------------------------------------------------------- | 1627 | GitHub Issue | turnaroundfactor/BMS-HW-Test#379 </br>\ 1628 turnaroundfactor/BMS-HW-Test#380 | 1629 | Instructions | 1. Request memory write </br>\ 1630 2. Log any successful addresses | 1631 | Estimated Duration | 1 minute | 1632 """ 1633 memory_test(Modes.WRITE) 1634 1635 1636class TestMemoryErase: 1637 """This will test the erase capability of the memory""" 1638 1639 def test_erase(self) -> None: 1640 """ 1641 | Description | Try to erase different memory locations | 1642 | :------------------- | :--------------------------------------------------------------- | 1643 | GitHub Issue | turnaroundfactor/BMS-HW-Test#379 </br>\ 1644 turnaroundfactor/BMS-HW-Test#380 | 1645 | Instructions | 1. Request memory erase </br>\ 1646 2. Log any successful addresses | 1647 | Estimated Duration | 1 minute | 1648 """ 1649 memory_test(Modes.ERASE) 1650 1651 1652class TestMemoryBootLoad: 1653 """This will test the boot load capability of the memory""" 1654 1655 def test_boot(self) -> None: 1656 """ 1657 | Description | Try to boot load from different memory locations | 1658 | :------------------- | :--------------------------------------------------------------- | 1659 | GitHub Issue | turnaroundfactor/BMS-HW-Test#381 </br>\ 1660 turnaroundfactor/BMS-HW-Test#382 | 1661 | Instructions | 1. Request memory boot load </br>\ 1662 2. Log any successful addresses | 1663 | Estimated Duration | 1 minute | 1664 """ 1665 memory_test(Modes.BOOT)
Channel identification. Expected type is backend dependent.
Whether to use a soft or hard reset before each test.
The name of the profile to compare against. If this is not provided, a profile json will be generated.
Short or long memory tests
70class ManufacturerID(IntEnum): 71 """Enum to hold Manufacturer IDs.""" 72 73 SAFT = 269 # NOTE: unused 74 BRENTRONICS = 822
Enum to hold Manufacturer IDs.
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
77class Modes(float, Enum): 78 """Enum for Command modes""" 79 80 ERASE = 0 81 READ = 1 82 WRITE = 2 83 BOOT = 6 84 EDCP = 7
Enum for Command modes
Inherited Members
- enum.Enum
- name
- value
- builtins.float
- conjugate
- as_integer_ratio
- fromhex
- hex
- is_integer
- real
- imag
87class BadStates(Enum): 88 """Enum for Bad Test Response States""" 89 90 NO_RESPONSE = 0 91 WRONG_PGN = 1 92 NACK = 2 93 WRONG_PACKETS = 3 94 INVALID_RESPONSE = 4
Enum for Bad Test Response States
Inherited Members
- enum.Enum
- name
- value
97class Errors: 98 """Holds different error messages""" 99 100 @staticmethod 101 def timeout() -> None: 102 """Prints message when connection times out""" 103 message = "Could not locate 6T" 104 logger.write_critical_to_report(message) 105 pytest.exit(message) 106 107 @staticmethod 108 def unexpected_packet(expected: str, frame: CANFrame) -> None: 109 """Prints message when unexpected packet is received""" 110 logger.write_warning_to_report(f"Expected {expected}, got {frame.pgn.short_name}") 111 112 @staticmethod 113 def no_packet(expected: str) -> None: 114 """Prints message when no packet is received""" 115 logger.write_warning_to_report(f"Expected {expected}, got None")
Holds different error messages
100 @staticmethod 101 def timeout() -> None: 102 """Prints message when connection times out""" 103 message = "Could not locate 6T" 104 logger.write_critical_to_report(message) 105 pytest.exit(message)
Prints message when connection times out
107 @staticmethod 108 def unexpected_packet(expected: str, frame: CANFrame) -> None: 109 """Prints message when unexpected packet is received""" 110 logger.write_warning_to_report(f"Expected {expected}, got {frame.pgn.short_name}")
Prints message when unexpected packet is received
121def cmp( 122 a: float | str, 123 sign: Literal["<", "<=", ">", ">=", "=="], 124 b: float | str, 125 unit_a: str = "", 126 unit_b: str = "", 127 form: str = "", 128) -> str: 129 """Generate a formatted string based on a comparison.""" 130 if not unit_b: 131 unit_b = unit_a 132 133 if isinstance(a, str) or isinstance(b, str): 134 return f"{a:{form}}{unit_a} {('≠', '=')[a == b]} {b:{form}}{unit_b}" 135 136 sign_str = { 137 "<": ("≮", "<")[a < b], 138 "<=": ("≰", "≤")[a <= b], 139 ">": ("≯", ">")[a > b], 140 ">=": ("≱", "≥")[a >= b], 141 "==": ("≠", "=")[a == b], 142 } 143 144 return f"{a:{form}}{unit_a} {sign_str[sign]} {b:{form}}{unit_b}"
Generate a formatted string based on a comparison.
147@pytest.fixture(scope="class", autouse=True) 148def reset_test_environment(): 149 """Before each test class, reset the 6T.""" 150 151 try: 152 power_cycle_frame = CANFrame( 153 destination_address=BATTERY_INFO.address, 154 pgn=PGN["PropA"], 155 data=[0, 0, 1, 1, 1, not HARD_RESET, 1, 3, 0, -1], 156 ) 157 maintenance_mode_frame = CANFrame( 158 destination_address=BATTERY_INFO.address, 159 pgn=PGN["PropA"], 160 data=[0, 0, 1, 1, 1, 3, 1, 3, 0, -1], 161 ) 162 factory_reset_frame = CANFrame( 163 destination_address=BATTERY_INFO.address, 164 pgn=PGN["PropA"], 165 data=[1, 0, 0, -1, 3, 0xF, 3, 0, 0x1F, -1], 166 ) 167 with CANBus(BATTERY_CHANNEL) as bus: 168 logger.write_info_to_report("Power-Cycling 6T") 169 bus.process_call(power_cycle_frame) 170 time.sleep(10) 171 logger.write_info_to_report("Entering maintenance mode") 172 bus.process_call(maintenance_mode_frame) 173 logger.write_info_to_report("Factory resetting 6T") 174 bus.process_call(factory_reset_frame) 175 time.sleep(10) 176 except AttributeError: # Battery has not yet been found 177 return
Before each test class, reset the 6T.
180class TestLocate6T: 181 """Scan for 6T battery.""" 182 183 def test_locate_6t(self) -> None: 184 """ 185 | Description | Scan the bus for devices | 186 | :------------------- | :--------------------------------------------------------------- | 187 | GitHub Issue | turnaroundfactor/BMS-HW-Test#396 | 188 | Instructions | 1. Request the names of everyone on the bus </br>\ 189 2. Use the response data for all communication | 190 | Estimated Duration | 1 second | 191 """ 192 name_request = CANFrame(pgn=PGN["Request"], data=[PGN["Address Claimed"].id]) 193 with CANBus(BATTERY_CHANNEL) as bus: 194 if name_frame := bus.process_call(name_request): 195 # Save responses to profile 196 BATTERY_INFO.id = int(name_frame.data[0]) 197 BATTERY_INFO.manufacturer_code = name_frame.data[1] 198 if (mfg_name := spn_types.manufacturer_id(name_frame.data[1])) == "Reserved": 199 mfg_name = f"Unknown ({name_frame.data[1]})" 200 BATTERY_INFO.manufacturer_name = mfg_name 201 BATTERY_INFO.address = name_frame.source_address 202 else: 203 message = "Could not locate 6T" 204 logger.write_warning_to_html_report(message) 205 pytest.exit(message) 206 207 # Log results 208 printable_id = "".join( 209 chr(i) if chr(i).isprintable() else "." for i in BATTERY_INFO.id.to_bytes(3, byteorder="big") 210 ) 211 logger.write_result_to_html_report( 212 f"Found {BATTERY_INFO.manufacturer_name} 6T at address {BATTERY_INFO.address} " 213 f"(ID: {BATTERY_INFO.id:06X}, ASCII ID: {printable_id})" 214 )
Scan for 6T battery.
183 def test_locate_6t(self) -> None: 184 """ 185 | Description | Scan the bus for devices | 186 | :------------------- | :--------------------------------------------------------------- | 187 | GitHub Issue | turnaroundfactor/BMS-HW-Test#396 | 188 | Instructions | 1. Request the names of everyone on the bus </br>\ 189 2. Use the response data for all communication | 190 | Estimated Duration | 1 second | 191 """ 192 name_request = CANFrame(pgn=PGN["Request"], data=[PGN["Address Claimed"].id]) 193 with CANBus(BATTERY_CHANNEL) as bus: 194 if name_frame := bus.process_call(name_request): 195 # Save responses to profile 196 BATTERY_INFO.id = int(name_frame.data[0]) 197 BATTERY_INFO.manufacturer_code = name_frame.data[1] 198 if (mfg_name := spn_types.manufacturer_id(name_frame.data[1])) == "Reserved": 199 mfg_name = f"Unknown ({name_frame.data[1]})" 200 BATTERY_INFO.manufacturer_name = mfg_name 201 BATTERY_INFO.address = name_frame.source_address 202 else: 203 message = "Could not locate 6T" 204 logger.write_warning_to_html_report(message) 205 pytest.exit(message) 206 207 # Log results 208 printable_id = "".join( 209 chr(i) if chr(i).isprintable() else "." for i in BATTERY_INFO.id.to_bytes(3, byteorder="big") 210 ) 211 logger.write_result_to_html_report( 212 f"Found {BATTERY_INFO.manufacturer_name} 6T at address {BATTERY_INFO.address} " 213 f"(ID: {BATTERY_INFO.id:06X}, ASCII ID: {printable_id})" 214 )
| Description | Scan the bus for devices |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#396 |
| Instructions | 1. Request the names of everyone on the bus 2. Use the response data for all communication |
| Estimated Duration | 1 second |
217class TestNameInformation: 218 """Check Unique ID (Identity Number) field in Address Claimed message""" 219 220 def test_name_information(self) -> None: 221 """ 222 | Description | Confirm information in NAME (address claimed) matches \ 223 expected value. | 224 | :------------------- | :--------------------------------------------------------------- | 225 | GitHub Issue | turnaroundfactor/BMS-HW-Test#396 | 226 | Instructions | 1. Request Address Claimed data </br>\ 227 2. Log returned values </br>\ 228 3. Validate if values meet spec requirements | 229 | Estimated Duration | 1 second | 230 """ 231 232 name_values = {} 233 failed_values = [] 234 235 name_request = CANFrame(pgn=PGN["Request"], data=[PGN["Address Claimed"].id]) 236 with CANBus(BATTERY_CHANNEL) as bus: 237 if name_frame := bus.process_call(name_request): 238 name_values["identity_number"] = int(name_frame.data[0]) 239 vehicle_system_instance = 0 240 241 for spn, elem in zip(PGN["Address Claimed"].data_field, name_frame.data): 242 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 243 logger.write_info_to_report(f"{spn.name}: {elem} {description}") 244 245 if spn.name in ("Manufacturer Code", "Reserved"): 246 continue 247 248 high_range = 0 249 if spn.name == "Identity Number": 250 high_range = 2097151 251 252 if spn.name == "ECU Instance": 253 high_range = 7 254 255 if spn.name == "Function Instance": 256 high_range = 31 257 258 if spn.name == "Function": 259 high_range = 254 260 261 if spn.name == "Vehicle System Instance": 262 high_range = 15 263 vehicle_system_instance = elem 264 265 if spn.name == "Industry Group": 266 high_range = 7 267 268 if spn.name == "Arbitrary Address Capable": 269 high_range = 1 270 271 if spn.name == "Vehicle System": 272 if elem not in (127, 0, vehicle_system_instance): 273 logger.write_warning_to_html_report( 274 f"Vehicle System {elem} is not 127, 0, or the same value as " f"Vehicle System Instance" 275 ) 276 failed_values.append(spn.name) 277 else: 278 if not 0 <= elem <= high_range: 279 logger.write_warning_to_html_report( 280 f"{spn.name}: {cmp(elem, '>=', 0)} and " f"{cmp(elem, '<=', high_range)}" 281 ) 282 failed_values.append(spn.name) 283 284 else: 285 message = f"No response to PGN {PGN['Address Claimed', [32]].id} (Address Claimed)" 286 logger.write_failure_to_html_report(message) 287 pytest.fail(message) 288 289 if len(failed_values) > 0: 290 categories = "data values" if len(failed_values) > 1 else "data value" 291 message = ( 292 f"{len(failed_values)} {categories} failed Address Claimed specifications: {', '.join(failed_values)}" 293 ) 294 logger.write_failure_to_html_report(message) 295 pytest.fail(message) 296 297 logger.write_result_to_html_report( 298 f"Found Identity Number: {name_values['identity_number']} (0x{name_values['identity_number']:06x}) " 299 f"for {BATTERY_INFO.manufacturer_name} 6T at address {BATTERY_INFO.address}" 300 )
Check Unique ID (Identity Number) field in Address Claimed message
220 def test_name_information(self) -> None: 221 """ 222 | Description | Confirm information in NAME (address claimed) matches \ 223 expected value. | 224 | :------------------- | :--------------------------------------------------------------- | 225 | GitHub Issue | turnaroundfactor/BMS-HW-Test#396 | 226 | Instructions | 1. Request Address Claimed data </br>\ 227 2. Log returned values </br>\ 228 3. Validate if values meet spec requirements | 229 | Estimated Duration | 1 second | 230 """ 231 232 name_values = {} 233 failed_values = [] 234 235 name_request = CANFrame(pgn=PGN["Request"], data=[PGN["Address Claimed"].id]) 236 with CANBus(BATTERY_CHANNEL) as bus: 237 if name_frame := bus.process_call(name_request): 238 name_values["identity_number"] = int(name_frame.data[0]) 239 vehicle_system_instance = 0 240 241 for spn, elem in zip(PGN["Address Claimed"].data_field, name_frame.data): 242 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 243 logger.write_info_to_report(f"{spn.name}: {elem} {description}") 244 245 if spn.name in ("Manufacturer Code", "Reserved"): 246 continue 247 248 high_range = 0 249 if spn.name == "Identity Number": 250 high_range = 2097151 251 252 if spn.name == "ECU Instance": 253 high_range = 7 254 255 if spn.name == "Function Instance": 256 high_range = 31 257 258 if spn.name == "Function": 259 high_range = 254 260 261 if spn.name == "Vehicle System Instance": 262 high_range = 15 263 vehicle_system_instance = elem 264 265 if spn.name == "Industry Group": 266 high_range = 7 267 268 if spn.name == "Arbitrary Address Capable": 269 high_range = 1 270 271 if spn.name == "Vehicle System": 272 if elem not in (127, 0, vehicle_system_instance): 273 logger.write_warning_to_html_report( 274 f"Vehicle System {elem} is not 127, 0, or the same value as " f"Vehicle System Instance" 275 ) 276 failed_values.append(spn.name) 277 else: 278 if not 0 <= elem <= high_range: 279 logger.write_warning_to_html_report( 280 f"{spn.name}: {cmp(elem, '>=', 0)} and " f"{cmp(elem, '<=', high_range)}" 281 ) 282 failed_values.append(spn.name) 283 284 else: 285 message = f"No response to PGN {PGN['Address Claimed', [32]].id} (Address Claimed)" 286 logger.write_failure_to_html_report(message) 287 pytest.fail(message) 288 289 if len(failed_values) > 0: 290 categories = "data values" if len(failed_values) > 1 else "data value" 291 message = ( 292 f"{len(failed_values)} {categories} failed Address Claimed specifications: {', '.join(failed_values)}" 293 ) 294 logger.write_failure_to_html_report(message) 295 pytest.fail(message) 296 297 logger.write_result_to_html_report( 298 f"Found Identity Number: {name_values['identity_number']} (0x{name_values['identity_number']:06x}) " 299 f"for {BATTERY_INFO.manufacturer_name} 6T at address {BATTERY_INFO.address}" 300 )
| Description | Confirm information in NAME (address claimed) matches expected value. |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#396 |
| Instructions | 1. Request Address Claimed data 2. Log returned values 3. Validate if values meet spec requirements |
| Estimated Duration | 1 second |
303class TestSoftwareIdentificationInformation: 304 """Retrieve & Validate Software Identification Information""" 305 306 def test_software_identification_information(self) -> None: 307 """ 308 | Description | Validate Software Identification | 309 | :------------------- | :--------------------------------------------------------------- | 310 | GitHub Issue | turnaroundfactor/BMS-HW-Test#394 | 311 | Instructions | 1. Request Software Identification </br>\ 312 2. Validate Software Identification </br>\ 313 3. Log Response | 314 | Estimated Duration | 1 second | 315 """ 316 317 soft_request = CANFrame(pgn=PGN["RQST"], data=[0xFEDA]) 318 soft_data = [] 319 with CANBus(BATTERY_CHANNEL) as bus: 320 if soft_frame := bus.process_call(soft_request): 321 # Complete RTS 322 if len(soft_frame.data) < 3: 323 message = ( 324 f"Unexpected byte response from PGN " 325 f"{PGN['Software Identification', [32]].id} (Software Identification)" 326 ) 327 logger.write_warning_to_html_report(message) 328 return 329 expected_bytes = int(soft_frame.data[1]) 330 expected_packets = 0 331 if BATTERY_INFO.manufacturer_code == ManufacturerID.BRENTRONICS: 332 expected_packets = int(soft_frame.data[2] + 1) 333 else: 334 expected_packets = int(soft_frame.data[2]) 335 336 rts_pgn_id = int(soft_frame.data[-1]) 337 cts_request = CANFrame(pgn=PGN["TP.CM"], data=[17, 0, 1, 0xFF, 0]) 338 cts_request.data[1] = expected_packets 339 cts_request.data[-1] = rts_pgn_id 340 341 # Send CTS 342 bus.send_message(cts_request.message()) 343 344 # Read & Store data from packets 345 for i in range(expected_packets): 346 data_frame = bus.read_frame() 347 if data_frame.pgn.id != 0xEB00: 348 message = f"Expected data frame {i + 1}, got PGN {data_frame.pgn.id}" 349 logger.write_failure_to_html_report(message) 350 pytest.fail(message) 351 soft_data.append(hex(int(data_frame.data[1]))) 352 353 if len(soft_data) != expected_packets: 354 message = f"Expected {expected_packets} packets, got {len(soft_data)}" 355 logger.write_failure_to_html_report(message) 356 pytest.fail(message) 357 358 # Send acknowledgement frame 359 end_acknowledge_frame = CANFrame(pgn=PGN["TP.CM"], data=[19, 0, 0, 0xFF, 0]) 360 end_acknowledge_frame.data[1] = expected_bytes 361 end_acknowledge_frame.data[2] = expected_packets 362 end_acknowledge_frame.data[-1] = rts_pgn_id 363 364 bus.send_message(end_acknowledge_frame.message()) 365 else: 366 message = f"No response for PGN {PGN['Software Identification', [32]].id} (Software Identification)" 367 logger.write_warning_to_html_report(message) 368 return 369 370 hex_string = "0x" 371 add_to_data = False 372 finish_adding = False 373 374 # Find Software Identification Number 375 for element in soft_data: 376 n = len(element) - 1 377 while n >= 1: 378 if add_to_data: 379 if element[n - 1 : n + 1] == "2a": 380 finish_adding = True 381 break 382 if element[n - 1 : n + 1] != "0x": 383 hex_string += element[n - 1 : n + 1] 384 n -= 2 385 else: 386 n -= 2 387 else: 388 if element[n - 3 : n + 1] == "2a31" or element[n - 3 : n + 1] == "2a01": 389 add_to_data = True 390 n -= 4 391 else: 392 n -= 4 393 if finish_adding: 394 break 395 396 if len(hex_string) <= 2: 397 logger.write_warning_to_html_report( 398 f"Software Identification: {hex_string} was not found in expected format" 399 ) 400 return 401 402 software_identification = spn_types.ascii_map(int(hex_string, 16)) 403 404 if re.search(r"[0-9]{2}\.[0-9]{2}\.[a-z]{2}\.[a-z]{2,}", software_identification) is None: 405 message = f"Software Identification: {software_identification} was not in expected format: MM.II.mm.aa.ee" 406 logger.write_failure_to_html_report(message) 407 pytest.fail(message) 408 409 logger.write_result_to_html_report(f"Software Identification: {software_identification}")
Retrieve & Validate Software Identification Information
306 def test_software_identification_information(self) -> None: 307 """ 308 | Description | Validate Software Identification | 309 | :------------------- | :--------------------------------------------------------------- | 310 | GitHub Issue | turnaroundfactor/BMS-HW-Test#394 | 311 | Instructions | 1. Request Software Identification </br>\ 312 2. Validate Software Identification </br>\ 313 3. Log Response | 314 | Estimated Duration | 1 second | 315 """ 316 317 soft_request = CANFrame(pgn=PGN["RQST"], data=[0xFEDA]) 318 soft_data = [] 319 with CANBus(BATTERY_CHANNEL) as bus: 320 if soft_frame := bus.process_call(soft_request): 321 # Complete RTS 322 if len(soft_frame.data) < 3: 323 message = ( 324 f"Unexpected byte response from PGN " 325 f"{PGN['Software Identification', [32]].id} (Software Identification)" 326 ) 327 logger.write_warning_to_html_report(message) 328 return 329 expected_bytes = int(soft_frame.data[1]) 330 expected_packets = 0 331 if BATTERY_INFO.manufacturer_code == ManufacturerID.BRENTRONICS: 332 expected_packets = int(soft_frame.data[2] + 1) 333 else: 334 expected_packets = int(soft_frame.data[2]) 335 336 rts_pgn_id = int(soft_frame.data[-1]) 337 cts_request = CANFrame(pgn=PGN["TP.CM"], data=[17, 0, 1, 0xFF, 0]) 338 cts_request.data[1] = expected_packets 339 cts_request.data[-1] = rts_pgn_id 340 341 # Send CTS 342 bus.send_message(cts_request.message()) 343 344 # Read & Store data from packets 345 for i in range(expected_packets): 346 data_frame = bus.read_frame() 347 if data_frame.pgn.id != 0xEB00: 348 message = f"Expected data frame {i + 1}, got PGN {data_frame.pgn.id}" 349 logger.write_failure_to_html_report(message) 350 pytest.fail(message) 351 soft_data.append(hex(int(data_frame.data[1]))) 352 353 if len(soft_data) != expected_packets: 354 message = f"Expected {expected_packets} packets, got {len(soft_data)}" 355 logger.write_failure_to_html_report(message) 356 pytest.fail(message) 357 358 # Send acknowledgement frame 359 end_acknowledge_frame = CANFrame(pgn=PGN["TP.CM"], data=[19, 0, 0, 0xFF, 0]) 360 end_acknowledge_frame.data[1] = expected_bytes 361 end_acknowledge_frame.data[2] = expected_packets 362 end_acknowledge_frame.data[-1] = rts_pgn_id 363 364 bus.send_message(end_acknowledge_frame.message()) 365 else: 366 message = f"No response for PGN {PGN['Software Identification', [32]].id} (Software Identification)" 367 logger.write_warning_to_html_report(message) 368 return 369 370 hex_string = "0x" 371 add_to_data = False 372 finish_adding = False 373 374 # Find Software Identification Number 375 for element in soft_data: 376 n = len(element) - 1 377 while n >= 1: 378 if add_to_data: 379 if element[n - 1 : n + 1] == "2a": 380 finish_adding = True 381 break 382 if element[n - 1 : n + 1] != "0x": 383 hex_string += element[n - 1 : n + 1] 384 n -= 2 385 else: 386 n -= 2 387 else: 388 if element[n - 3 : n + 1] == "2a31" or element[n - 3 : n + 1] == "2a01": 389 add_to_data = True 390 n -= 4 391 else: 392 n -= 4 393 if finish_adding: 394 break 395 396 if len(hex_string) <= 2: 397 logger.write_warning_to_html_report( 398 f"Software Identification: {hex_string} was not found in expected format" 399 ) 400 return 401 402 software_identification = spn_types.ascii_map(int(hex_string, 16)) 403 404 if re.search(r"[0-9]{2}\.[0-9]{2}\.[a-z]{2}\.[a-z]{2,}", software_identification) is None: 405 message = f"Software Identification: {software_identification} was not in expected format: MM.II.mm.aa.ee" 406 logger.write_failure_to_html_report(message) 407 pytest.fail(message) 408 409 logger.write_result_to_html_report(f"Software Identification: {software_identification}")
| Description | Validate Software Identification |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#394 |
| Instructions | 1. Request Software Identification 2. Validate Software Identification 3. Log Response |
| Estimated Duration | 1 second |
412class TestBatteryRegulationInformation: 413 """Retrieve and validate battery regulation information""" 414 415 def test_battery_regulation_information(self) -> None: 416 """ 417 | Description | Validate Battery Regulation Information 1 & 2 | 418 | :------------------- | :--------------------------------------------------------------- | 419 | GitHub Issue | turnaroundfactor/BMS-HW-Test#392 | 420 | Instructions | 1. Request Battery Regulation Information 1 </br>\ 421 2. Validate Information </br>\ 422 3. Log Response | 423 | Estimated Duration | 2 seconds | 424 """ 425 426 battery_regulation_one_request = CANFrame(pgn=PGN["RQST"], data=[0xFF03]) 427 battery_regulation_two_request = CANFrame(pgn=PGN["RQST"], data=[0xFF04]) 428 429 # Obtain Battery Regulation One Information 430 with CANBus(BATTERY_CHANNEL) as bus: 431 logger.write_result_to_html_report( 432 "<span style='font-weight: bold'>Battery Regulation Information 1 </span>" 433 ) 434 if battery_frame := bus.process_call(battery_regulation_one_request): 435 436 if battery_frame.pgn.id != PGN["PropB_03", [32]].id: 437 message = f"Expected {PGN['PropB_03', [32]].id} 0xFF03, but received PGN {battery_frame.pgn.id}" 438 logger.write_warning_to_html_report(message) 439 440 for spn, elem in zip(PGN["PropB_03"].data_field, battery_frame.data): 441 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 442 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 443 444 high_range = 3212.75 445 low_range = 0.0 446 if spn.name == "Battery Current": 447 high_range = 1600.00 448 low_range = -1600.00 449 450 if not low_range <= elem <= high_range: 451 logger.write_warning_to_html_report( 452 f"{spn.name}: {cmp(elem, '>=', low_range)} and " f"{cmp(elem, '<=', high_range)}" 453 ) 454 else: 455 message = f"Did not receive response for PGN {PGN['PropB_03', [32]].id} (Battery Regulation One)" 456 logger.write_warning_to_html_report(message) 457 458 # Obtain Battery Regulation Two Information 459 with CANBus(BATTERY_CHANNEL) as bus: 460 logger.write_result_to_html_report( 461 "<span style='font-weight: bold'>Battery Regulation Information 2</span>" 462 ) 463 if battery_frame_two := bus.process_call(battery_regulation_two_request): 464 465 if battery_frame_two.pgn.id != PGN["PropB_04", [32]].id: 466 message = f"Expected PGN {PGN['PropB_04', [32]].id}, but received PGN {battery_frame_two.pgn.id}" 467 logger.write_warning_to_html_report(message) 468 469 for spn, elem in zip(PGN["PropB_04"].data_field, battery_frame_two.data): 470 471 if spn.name == "Reserved": 472 continue 473 474 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 475 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 476 477 high_range = 3 478 if spn.name == "Bus Voltage Request": 479 high_range = 3212.75 480 481 if spn.name == "Transportability SOC": 482 high_range = 100 483 484 if not 0 <= elem <= high_range: 485 logger.write_warning_to_html_report( 486 f"{spn.name}: {cmp(elem, '>=', 0)} and {cmp(elem, '<=', high_range)}" 487 ) 488 489 else: 490 message = f"Did not receive response for PGN {PGN['PropB_04', [32]].id} (Battery Regulation Two)" 491 logger.write_warning_to_html_report(message)
Retrieve and validate battery regulation information
415 def test_battery_regulation_information(self) -> None: 416 """ 417 | Description | Validate Battery Regulation Information 1 & 2 | 418 | :------------------- | :--------------------------------------------------------------- | 419 | GitHub Issue | turnaroundfactor/BMS-HW-Test#392 | 420 | Instructions | 1. Request Battery Regulation Information 1 </br>\ 421 2. Validate Information </br>\ 422 3. Log Response | 423 | Estimated Duration | 2 seconds | 424 """ 425 426 battery_regulation_one_request = CANFrame(pgn=PGN["RQST"], data=[0xFF03]) 427 battery_regulation_two_request = CANFrame(pgn=PGN["RQST"], data=[0xFF04]) 428 429 # Obtain Battery Regulation One Information 430 with CANBus(BATTERY_CHANNEL) as bus: 431 logger.write_result_to_html_report( 432 "<span style='font-weight: bold'>Battery Regulation Information 1 </span>" 433 ) 434 if battery_frame := bus.process_call(battery_regulation_one_request): 435 436 if battery_frame.pgn.id != PGN["PropB_03", [32]].id: 437 message = f"Expected {PGN['PropB_03', [32]].id} 0xFF03, but received PGN {battery_frame.pgn.id}" 438 logger.write_warning_to_html_report(message) 439 440 for spn, elem in zip(PGN["PropB_03"].data_field, battery_frame.data): 441 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 442 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 443 444 high_range = 3212.75 445 low_range = 0.0 446 if spn.name == "Battery Current": 447 high_range = 1600.00 448 low_range = -1600.00 449 450 if not low_range <= elem <= high_range: 451 logger.write_warning_to_html_report( 452 f"{spn.name}: {cmp(elem, '>=', low_range)} and " f"{cmp(elem, '<=', high_range)}" 453 ) 454 else: 455 message = f"Did not receive response for PGN {PGN['PropB_03', [32]].id} (Battery Regulation One)" 456 logger.write_warning_to_html_report(message) 457 458 # Obtain Battery Regulation Two Information 459 with CANBus(BATTERY_CHANNEL) as bus: 460 logger.write_result_to_html_report( 461 "<span style='font-weight: bold'>Battery Regulation Information 2</span>" 462 ) 463 if battery_frame_two := bus.process_call(battery_regulation_two_request): 464 465 if battery_frame_two.pgn.id != PGN["PropB_04", [32]].id: 466 message = f"Expected PGN {PGN['PropB_04', [32]].id}, but received PGN {battery_frame_two.pgn.id}" 467 logger.write_warning_to_html_report(message) 468 469 for spn, elem in zip(PGN["PropB_04"].data_field, battery_frame_two.data): 470 471 if spn.name == "Reserved": 472 continue 473 474 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 475 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 476 477 high_range = 3 478 if spn.name == "Bus Voltage Request": 479 high_range = 3212.75 480 481 if spn.name == "Transportability SOC": 482 high_range = 100 483 484 if not 0 <= elem <= high_range: 485 logger.write_warning_to_html_report( 486 f"{spn.name}: {cmp(elem, '>=', 0)} and {cmp(elem, '<=', high_range)}" 487 ) 488 489 else: 490 message = f"Did not receive response for PGN {PGN['PropB_04', [32]].id} (Battery Regulation Two)" 491 logger.write_warning_to_html_report(message)
| Description | Validate Battery Regulation Information 1 & 2 |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#392 |
| Instructions | 1. Request Battery Regulation Information 1 2. Validate Information 3. Log Response |
| Estimated Duration | 2 seconds |
494class TestConfigurationStateMessage: 495 """Confirms Configuration State Message matches profile after factory reset""" 496 497 def test_configuration_state_message(self) -> None: 498 """ 499 | Description | Validates if Configuration State Message matches profile | 500 | :------------------- | :--------------------------------------------------------------- | 501 | GitHub Issue | turnaroundfactor/BMS-HW-Test#393 | 502 | Instructions | 1. Request Configuration State </br>\ 503 2. Check if Configuration State matches default values </br>\ 504 3. Log results | 505 | Estimated Duration | 1 second | 506 """ 507 failed_values = [] 508 configuration_state_request = CANFrame(pgn=PGN["Request"], data=[PGN["Configuration State Message 1"].id]) 509 with CANBus(BATTERY_CHANNEL) as bus: 510 if configuration_frame := bus.process_call(configuration_state_request): 511 if configuration_frame.pgn.id != PGN["Configuration State Message 1", [32]].id: 512 message = ( 513 f"Received PGN {configuration_frame.pgn.id}, not PGN " 514 f"{PGN['Configuration State Message 1', [32]].id} " 515 ) 516 logger.write_failure_to_html_report(message) 517 pytest.fail(message) 518 519 data = configuration_frame.data 520 pgn_data_field = PGN["Configuration State Message 1"].data_field 521 522 if len(data) != len(pgn_data_field): 523 message = ( 524 f"Expected {len(pgn_data_field)} data fields, " 525 f"got {len(data)}. Data is missing from response" 526 ) 527 logger.write_failure_to_html_report(message) 528 pytest.fail(message) 529 530 logger.write_result_to_html_report("<span style='font-weight: bold'>Configuration State Message</span>") 531 532 for spn, elem in zip(pgn_data_field, data): 533 if spn.name == "Reserved": 534 continue 535 536 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 537 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 538 539 if not 0 <= elem <= 3: 540 message = ( 541 f"{spn.name}: {cmp(elem, '>=', 0, f'({spn.data_type(elem)})')} and " 542 f"{cmp(elem, '<=', 3, f'({spn.data_type(elem)})')}" 543 ) 544 logger.write_warning_to_html_report(message) 545 failed_values.append(spn.name) 546 continue 547 548 if spn.name in ("Battery Battle-override State", "Standby State", "Configure VPMS Function State"): 549 message = ( 550 f"{spn.name}: {cmp(elem, '==', 0, f'({spn.data_type(elem)})', f'({spn.data_type(0)})')}" 551 ) 552 if elem != 0: 553 logger.write_warning_to_html_report(message) 554 failed_values.append(spn.name) 555 556 if spn.name in ("Automated Heater Function State", "Contactor(s) Control State"): 557 message = ( 558 f"{spn.name}: {cmp(elem, '==', 1, f'({spn.data_type(elem)})', f'({spn.data_type(1)})')}" 559 ) 560 if elem != 1: 561 logger.write_warning_to_html_report(message) 562 failed_values.append(spn.name) 563 564 if len(failed_values) > 0: 565 categories = "data values" if len(failed_values) > 1 else "data value" 566 message = ( 567 f"{len(failed_values)} {categories} failed " 568 f"Configuration State Message specifications: {', '.join(failed_values)}" 569 ) 570 logger.write_failure_to_html_report(message) 571 pytest.fail(message) 572 573 else: 574 message = ( 575 f"Did not receive a response for PGN {PGN['Configuration State Message 1', [32]].id} " 576 f"(Configuration State Message 1)" 577 ) 578 logger.write_failure_to_html_report(message) 579 pytest.fail(message)
Confirms Configuration State Message matches profile after factory reset
497 def test_configuration_state_message(self) -> None: 498 """ 499 | Description | Validates if Configuration State Message matches profile | 500 | :------------------- | :--------------------------------------------------------------- | 501 | GitHub Issue | turnaroundfactor/BMS-HW-Test#393 | 502 | Instructions | 1. Request Configuration State </br>\ 503 2. Check if Configuration State matches default values </br>\ 504 3. Log results | 505 | Estimated Duration | 1 second | 506 """ 507 failed_values = [] 508 configuration_state_request = CANFrame(pgn=PGN["Request"], data=[PGN["Configuration State Message 1"].id]) 509 with CANBus(BATTERY_CHANNEL) as bus: 510 if configuration_frame := bus.process_call(configuration_state_request): 511 if configuration_frame.pgn.id != PGN["Configuration State Message 1", [32]].id: 512 message = ( 513 f"Received PGN {configuration_frame.pgn.id}, not PGN " 514 f"{PGN['Configuration State Message 1', [32]].id} " 515 ) 516 logger.write_failure_to_html_report(message) 517 pytest.fail(message) 518 519 data = configuration_frame.data 520 pgn_data_field = PGN["Configuration State Message 1"].data_field 521 522 if len(data) != len(pgn_data_field): 523 message = ( 524 f"Expected {len(pgn_data_field)} data fields, " 525 f"got {len(data)}. Data is missing from response" 526 ) 527 logger.write_failure_to_html_report(message) 528 pytest.fail(message) 529 530 logger.write_result_to_html_report("<span style='font-weight: bold'>Configuration State Message</span>") 531 532 for spn, elem in zip(pgn_data_field, data): 533 if spn.name == "Reserved": 534 continue 535 536 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 537 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 538 539 if not 0 <= elem <= 3: 540 message = ( 541 f"{spn.name}: {cmp(elem, '>=', 0, f'({spn.data_type(elem)})')} and " 542 f"{cmp(elem, '<=', 3, f'({spn.data_type(elem)})')}" 543 ) 544 logger.write_warning_to_html_report(message) 545 failed_values.append(spn.name) 546 continue 547 548 if spn.name in ("Battery Battle-override State", "Standby State", "Configure VPMS Function State"): 549 message = ( 550 f"{spn.name}: {cmp(elem, '==', 0, f'({spn.data_type(elem)})', f'({spn.data_type(0)})')}" 551 ) 552 if elem != 0: 553 logger.write_warning_to_html_report(message) 554 failed_values.append(spn.name) 555 556 if spn.name in ("Automated Heater Function State", "Contactor(s) Control State"): 557 message = ( 558 f"{spn.name}: {cmp(elem, '==', 1, f'({spn.data_type(elem)})', f'({spn.data_type(1)})')}" 559 ) 560 if elem != 1: 561 logger.write_warning_to_html_report(message) 562 failed_values.append(spn.name) 563 564 if len(failed_values) > 0: 565 categories = "data values" if len(failed_values) > 1 else "data value" 566 message = ( 567 f"{len(failed_values)} {categories} failed " 568 f"Configuration State Message specifications: {', '.join(failed_values)}" 569 ) 570 logger.write_failure_to_html_report(message) 571 pytest.fail(message) 572 573 else: 574 message = ( 575 f"Did not receive a response for PGN {PGN['Configuration State Message 1', [32]].id} " 576 f"(Configuration State Message 1)" 577 ) 578 logger.write_failure_to_html_report(message) 579 pytest.fail(message)
| Description | Validates if Configuration State Message matches profile |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#393 |
| Instructions | 1. Request Configuration State 2. Check if Configuration State matches default values 3. Log results |
| Estimated Duration | 1 second |
582def na_maintenance_mode(bus: CANBus) -> bool: 583 """This function will place battery in Maintenance Mode with Reset Value of "3".""" 584 585 maintenance_mode = CANFrame( # Memory access only works in maintenance mode 586 destination_address=BATTERY_INFO.address, 587 pgn=PGN["PropA"], 588 data=[0, 0, 1, 1, 1, 3, 1, 3, 0, -1], 589 ) 590 591 bus.send_message(maintenance_mode.message()) # Enter maintenance mode 592 if response := bus.read_input(): 593 ack_frame = CANFrame.decode(response.arbitration_id, response.data) 594 if ack_frame.pgn.id != 0xE800 and ack_frame.data[0] != 0: 595 logger.write_warning_to_html_report("Unable to enter maintenance mode") 596 return False 597 return True 598 599 message = "Received no maintenance mode response" 600 logger.write_warning_to_html_report(message) 601 return False
This function will place battery in Maintenance Mode with Reset Value of "3".
604class TestNameManagementMessage: 605 """This will test the mandatory NAME Management commands""" 606 607 def test_name_management_command(self) -> None: 608 """ 609 | Description | Test Name Management Command | 610 | :------------------- | :--------------------------------------------------------------- | 611 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 612 | Instructions | 1. Get NAME information from Address Claimed </br>\ 613 2. Change ECU value </br>\ 614 3. Test Name Management Command </br>\ 615 4. Check Values updated </br>\ 616 5. Log results | 617 | Estimated Duration | 1 second | 618 """ 619 620 old_ecu_value = 0 621 new_ecu_value = 0 622 checksum_value = 0 623 adopt_name_request_data: list[float] = [255, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 624 address_request = CANFrame(pgn=PGN["Request"], data=[PGN["Address Claimed"].id]) 625 626 with CANBus(BATTERY_CHANNEL) as bus: 627 na_maintenance_mode(bus) 628 629 # Name Management Test 630 if address_claimed := bus.process_call(address_request): 631 if address_claimed.pgn.id != PGN["Address Claimed", [32]].id: 632 Errors.unexpected_packet("Address Claimed", address_claimed) 633 message = f"Unexpected packet PGN {address_claimed.pgn.id} was received" 634 logger.write_failure_to_html_report(message) 635 pytest.fail(message) 636 637 checksum_value = sum(address_claimed.packed_data) & 0xFF 638 old_ecu_value = address_claimed.data[2] 639 if old_ecu_value > 0: 640 new_ecu_value = 0 641 else: 642 new_ecu_value = 1 643 else: 644 message = f"No response received for PGN {[PGN['Address Claimed', [32]].id]}" 645 logger.write_result_to_html_report(message) 646 647 if not checksum_value: 648 message = "Could not condense Name into bits for NAME Management" 649 logger.write_failure_to_html_report(message) 650 pytest.fail(message) 651 652 request_data: list[float] = [ 653 checksum_value, 654 1, 655 0, 656 1, 657 1, 658 1, 659 1, 660 1, 661 1, 662 0, 663 1, 664 1, 665 new_ecu_value, 666 1, 667 1, 668 1, 669 1, 670 1, 671 1, 672 1, 673 ] 674 name_management_set_name = CANFrame( 675 destination_address=BATTERY_INFO.address, pgn=PGN["NAME Management Message"], data=request_data 676 ) 677 678 if pending_response := bus.process_call(name_management_set_name): 679 if pending_response.pgn.id != PGN["NAME Management Message", [32]].id: 680 if pending_response.pgn.id == PGN["Acknowledgement", [32]].id: 681 message = ( 682 f"Battery may not support NAME Management, received PGN " 683 f"{pending_response.pgn.id} ({spn_types.acknowledgement(pending_response.data[0])})" 684 ) 685 logger.write_failure_to_html_report(message) 686 else: 687 Errors.unexpected_packet("NAME Management Message", pending_response) 688 message = f"Unexpected PGN {pending_response.pgn.id} received" 689 logger.write_failure_to_html_report(message) 690 pytest.fail(message) 691 692 if pending_response.data[0] == 3: 693 message = ( 694 f"Message was unsuccessful, received error: " 695 f"{spn_types.name_error_code(pending_response.data[0])}" 696 ) 697 logger.write_failure_to_html_report(message) 698 pytest.fail(message) 699 700 if pending_response.data[12] != new_ecu_value: 701 message = f"ECU Value was not changed from {old_ecu_value} to {new_ecu_value}" 702 logger.write_failure_to_html_report(message) 703 pytest.fail(message) 704 else: 705 message = ( 706 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 707 ) 708 logger.write_failure_to_html_report(message) 709 pytest.fail(message) 710 711 adopt_name_request = CANFrame( 712 destination_address=BATTERY_INFO.address, 713 pgn=PGN["NAME Management Message"], 714 data=adopt_name_request_data, 715 ) 716 717 bus.send_message(adopt_name_request.message()) 718 719 current_name_request_data: list[float] = [255, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 720 current_name_request = CANFrame( 721 destination_address=BATTERY_INFO.address, 722 pgn=PGN["NAME Management Message"], 723 data=current_name_request_data, 724 ) 725 726 if name_management_response := bus.process_call(current_name_request): 727 if name_management_response.data[12] != new_ecu_value: 728 message = ( 729 f"Name's ECU Instance was not updated after Name Management Changes, " 730 f"{cmp(name_management_response.data[12], '==', new_ecu_value)}" 731 ) 732 logger.write_failure_to_html_report(message) 733 pytest.fail(message) 734 else: 735 message = ( 736 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 737 ) 738 logger.write_failure_to_html_report(message) 739 pytest.fail(message) 740 741 if new_address_claimed := bus.process_call(address_request): 742 if new_address_claimed.data[2] != new_ecu_value + 1: 743 message = ( 744 f"Address Claimed was not updated after Name Management Changes. " 745 f"{cmp(new_address_claimed.data[2], '==', new_ecu_value + 1)}" 746 ) 747 logger.write_failure_to_html_report(message) 748 pytest.fail(message) 749 750 logger.write_result_to_html_report( 751 f"Name Management Command was successful. " 752 f"ECU Instance was changed from {old_ecu_value} to {new_ecu_value}" 753 ) 754 755 def test_wrong_name_management_data(self): 756 """ 757 | Description | Test Incorrect Data for NAME Management Command | 758 | :------------------- | :--------------------------------------------------------------- | 759 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 760 | Instructions | 1. Provide wrong Checksum value for Name Command </br>\ 761 2. Check receive Checksum error code as response </br>\ 762 3. Log results | 763 | Estimated Duration | 1 second | 764 """ 765 wrong_checksum = 0 766 new_ecu_value = 0 767 768 current_name_request_data = [255, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 769 current_name_request = CANFrame( 770 destination_address=BATTERY_INFO.address, 771 pgn=PGN["NAME Management Message"], 772 data=current_name_request_data, 773 ) 774 775 with CANBus(BATTERY_CHANNEL) as bus: 776 # Test Checksum Error -- Should return Error Code: 3 777 na_maintenance_mode(bus) 778 779 if current_name := bus.process_call(current_name_request): 780 if current_name.pgn.id != PGN["NAME Management Message", [32]].id: 781 if current_name.pgn.id == PGN["Acknowledgement", [32]].id: 782 message = ( 783 f"Battery may not support NAME Management, received PGN " 784 f"{current_name.pgn.id} ({spn_types.acknowledgement(current_name.data[0])})" 785 ) 786 logger.write_failure_to_html_report(message) 787 pytest.fail(message) 788 Errors.unexpected_packet("NAME Management Message", current_name) 789 message = f"Unexpected packet was received: PGN {current_name.pgn.id}" 790 logger.write_failure_to_html_report(message) 791 pytest.fail(message) 792 793 wrong_checksum = sum(current_name.packed_data) & 0xFF 794 old_ecu_value = current_name.data[12] 795 if old_ecu_value > 0: 796 new_ecu_value = 0 797 else: 798 new_ecu_value = 1 799 else: 800 message = ( 801 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 802 ) 803 logger.write_failure_to_html_report(message) 804 pytest.fail(message) 805 806 request_data = [wrong_checksum, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, new_ecu_value, 1, 1, 1, 1, 1, 1, 1] 807 name_management_set_name = CANFrame( 808 destination_address=BATTERY_INFO.address, pgn=PGN["NAME Management Message"], data=request_data 809 ) 810 811 if pending_response := bus.process_call(name_management_set_name): 812 if pending_response.pgn.id != PGN["NAME Management Message", [32]].id: 813 if pending_response.pgn.id == PGN["Acknowledgement", [32]].id: 814 message = ( 815 f"Battery may not support NAME Management, received PGN {pending_response.pgn.id} " 816 f"({spn_types.acknowledgement(pending_response.data[0])})" 817 ) 818 logger.write_failure_to_html_report(message) 819 pytest.fail(message) 820 Errors.unexpected_packet("NAME Management Message", pending_response) 821 message = f"Unexpected packet was received: {pending_response.pgn.id}" 822 logger.write_failure_to_html_report(message) 823 pytest.fail(message) 824 825 if pending_response.data[0] != 3: 826 message = cmp( 827 pending_response.data[0], 828 "==", 829 3, 830 f"({spn_types.name_error_code(pending_response.data[0])})", 831 f"({spn_types.name_error_code(3)})", 832 ) 833 logger.write_failure_to_html_report( 834 f"PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message) " 835 f"updated NAME Management with incorrect checksum value" 836 ) 837 logger.write_failure_to_html_report(message) 838 pytest.fail(message) 839 else: 840 message = ( 841 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 842 ) 843 logger.write_failure_to_html_report(message) 844 pytest.fail(message) 845 846 logger.write_result_to_html_report( 847 "NAME Management successfully did not process request with incorrect checksum value" 848 )
This will test the mandatory NAME Management commands
607 def test_name_management_command(self) -> None: 608 """ 609 | Description | Test Name Management Command | 610 | :------------------- | :--------------------------------------------------------------- | 611 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 612 | Instructions | 1. Get NAME information from Address Claimed </br>\ 613 2. Change ECU value </br>\ 614 3. Test Name Management Command </br>\ 615 4. Check Values updated </br>\ 616 5. Log results | 617 | Estimated Duration | 1 second | 618 """ 619 620 old_ecu_value = 0 621 new_ecu_value = 0 622 checksum_value = 0 623 adopt_name_request_data: list[float] = [255, 1, 1, 1, 1, 1, 1, 1, 1, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 624 address_request = CANFrame(pgn=PGN["Request"], data=[PGN["Address Claimed"].id]) 625 626 with CANBus(BATTERY_CHANNEL) as bus: 627 na_maintenance_mode(bus) 628 629 # Name Management Test 630 if address_claimed := bus.process_call(address_request): 631 if address_claimed.pgn.id != PGN["Address Claimed", [32]].id: 632 Errors.unexpected_packet("Address Claimed", address_claimed) 633 message = f"Unexpected packet PGN {address_claimed.pgn.id} was received" 634 logger.write_failure_to_html_report(message) 635 pytest.fail(message) 636 637 checksum_value = sum(address_claimed.packed_data) & 0xFF 638 old_ecu_value = address_claimed.data[2] 639 if old_ecu_value > 0: 640 new_ecu_value = 0 641 else: 642 new_ecu_value = 1 643 else: 644 message = f"No response received for PGN {[PGN['Address Claimed', [32]].id]}" 645 logger.write_result_to_html_report(message) 646 647 if not checksum_value: 648 message = "Could not condense Name into bits for NAME Management" 649 logger.write_failure_to_html_report(message) 650 pytest.fail(message) 651 652 request_data: list[float] = [ 653 checksum_value, 654 1, 655 0, 656 1, 657 1, 658 1, 659 1, 660 1, 661 1, 662 0, 663 1, 664 1, 665 new_ecu_value, 666 1, 667 1, 668 1, 669 1, 670 1, 671 1, 672 1, 673 ] 674 name_management_set_name = CANFrame( 675 destination_address=BATTERY_INFO.address, pgn=PGN["NAME Management Message"], data=request_data 676 ) 677 678 if pending_response := bus.process_call(name_management_set_name): 679 if pending_response.pgn.id != PGN["NAME Management Message", [32]].id: 680 if pending_response.pgn.id == PGN["Acknowledgement", [32]].id: 681 message = ( 682 f"Battery may not support NAME Management, received PGN " 683 f"{pending_response.pgn.id} ({spn_types.acknowledgement(pending_response.data[0])})" 684 ) 685 logger.write_failure_to_html_report(message) 686 else: 687 Errors.unexpected_packet("NAME Management Message", pending_response) 688 message = f"Unexpected PGN {pending_response.pgn.id} received" 689 logger.write_failure_to_html_report(message) 690 pytest.fail(message) 691 692 if pending_response.data[0] == 3: 693 message = ( 694 f"Message was unsuccessful, received error: " 695 f"{spn_types.name_error_code(pending_response.data[0])}" 696 ) 697 logger.write_failure_to_html_report(message) 698 pytest.fail(message) 699 700 if pending_response.data[12] != new_ecu_value: 701 message = f"ECU Value was not changed from {old_ecu_value} to {new_ecu_value}" 702 logger.write_failure_to_html_report(message) 703 pytest.fail(message) 704 else: 705 message = ( 706 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 707 ) 708 logger.write_failure_to_html_report(message) 709 pytest.fail(message) 710 711 adopt_name_request = CANFrame( 712 destination_address=BATTERY_INFO.address, 713 pgn=PGN["NAME Management Message"], 714 data=adopt_name_request_data, 715 ) 716 717 bus.send_message(adopt_name_request.message()) 718 719 current_name_request_data: list[float] = [255, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 720 current_name_request = CANFrame( 721 destination_address=BATTERY_INFO.address, 722 pgn=PGN["NAME Management Message"], 723 data=current_name_request_data, 724 ) 725 726 if name_management_response := bus.process_call(current_name_request): 727 if name_management_response.data[12] != new_ecu_value: 728 message = ( 729 f"Name's ECU Instance was not updated after Name Management Changes, " 730 f"{cmp(name_management_response.data[12], '==', new_ecu_value)}" 731 ) 732 logger.write_failure_to_html_report(message) 733 pytest.fail(message) 734 else: 735 message = ( 736 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 737 ) 738 logger.write_failure_to_html_report(message) 739 pytest.fail(message) 740 741 if new_address_claimed := bus.process_call(address_request): 742 if new_address_claimed.data[2] != new_ecu_value + 1: 743 message = ( 744 f"Address Claimed was not updated after Name Management Changes. " 745 f"{cmp(new_address_claimed.data[2], '==', new_ecu_value + 1)}" 746 ) 747 logger.write_failure_to_html_report(message) 748 pytest.fail(message) 749 750 logger.write_result_to_html_report( 751 f"Name Management Command was successful. " 752 f"ECU Instance was changed from {old_ecu_value} to {new_ecu_value}" 753 )
| Description | Test Name Management Command |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#389 |
| Instructions | 1. Get NAME information from Address Claimed 2. Change ECU value 3. Test Name Management Command 4. Check Values updated 5. Log results |
| Estimated Duration | 1 second |
755 def test_wrong_name_management_data(self): 756 """ 757 | Description | Test Incorrect Data for NAME Management Command | 758 | :------------------- | :--------------------------------------------------------------- | 759 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 760 | Instructions | 1. Provide wrong Checksum value for Name Command </br>\ 761 2. Check receive Checksum error code as response </br>\ 762 3. Log results | 763 | Estimated Duration | 1 second | 764 """ 765 wrong_checksum = 0 766 new_ecu_value = 0 767 768 current_name_request_data = [255, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] 769 current_name_request = CANFrame( 770 destination_address=BATTERY_INFO.address, 771 pgn=PGN["NAME Management Message"], 772 data=current_name_request_data, 773 ) 774 775 with CANBus(BATTERY_CHANNEL) as bus: 776 # Test Checksum Error -- Should return Error Code: 3 777 na_maintenance_mode(bus) 778 779 if current_name := bus.process_call(current_name_request): 780 if current_name.pgn.id != PGN["NAME Management Message", [32]].id: 781 if current_name.pgn.id == PGN["Acknowledgement", [32]].id: 782 message = ( 783 f"Battery may not support NAME Management, received PGN " 784 f"{current_name.pgn.id} ({spn_types.acknowledgement(current_name.data[0])})" 785 ) 786 logger.write_failure_to_html_report(message) 787 pytest.fail(message) 788 Errors.unexpected_packet("NAME Management Message", current_name) 789 message = f"Unexpected packet was received: PGN {current_name.pgn.id}" 790 logger.write_failure_to_html_report(message) 791 pytest.fail(message) 792 793 wrong_checksum = sum(current_name.packed_data) & 0xFF 794 old_ecu_value = current_name.data[12] 795 if old_ecu_value > 0: 796 new_ecu_value = 0 797 else: 798 new_ecu_value = 1 799 else: 800 message = ( 801 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 802 ) 803 logger.write_failure_to_html_report(message) 804 pytest.fail(message) 805 806 request_data = [wrong_checksum, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, new_ecu_value, 1, 1, 1, 1, 1, 1, 1] 807 name_management_set_name = CANFrame( 808 destination_address=BATTERY_INFO.address, pgn=PGN["NAME Management Message"], data=request_data 809 ) 810 811 if pending_response := bus.process_call(name_management_set_name): 812 if pending_response.pgn.id != PGN["NAME Management Message", [32]].id: 813 if pending_response.pgn.id == PGN["Acknowledgement", [32]].id: 814 message = ( 815 f"Battery may not support NAME Management, received PGN {pending_response.pgn.id} " 816 f"({spn_types.acknowledgement(pending_response.data[0])})" 817 ) 818 logger.write_failure_to_html_report(message) 819 pytest.fail(message) 820 Errors.unexpected_packet("NAME Management Message", pending_response) 821 message = f"Unexpected packet was received: {pending_response.pgn.id}" 822 logger.write_failure_to_html_report(message) 823 pytest.fail(message) 824 825 if pending_response.data[0] != 3: 826 message = cmp( 827 pending_response.data[0], 828 "==", 829 3, 830 f"({spn_types.name_error_code(pending_response.data[0])})", 831 f"({spn_types.name_error_code(3)})", 832 ) 833 logger.write_failure_to_html_report( 834 f"PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message) " 835 f"updated NAME Management with incorrect checksum value" 836 ) 837 logger.write_failure_to_html_report(message) 838 pytest.fail(message) 839 else: 840 message = ( 841 f"No response received for PGN {PGN['NAME Management Message', [32]].id} (NAME Management Message)" 842 ) 843 logger.write_failure_to_html_report(message) 844 pytest.fail(message) 845 846 logger.write_result_to_html_report( 847 "NAME Management successfully did not process request with incorrect checksum value" 848 )
| Description | Test Incorrect Data for NAME Management Command |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#389 |
| Instructions | 1. Provide wrong Checksum value for Name Command 2. Check receive Checksum error code as response 3. Log results |
| Estimated Duration | 1 second |
851class TestActiveDiagnosticTroubleCodes: 852 """Test that Active Diagnostic Trouble Codes are J1939 Compliant""" 853 854 def test_active_diagnostic_trouble_codes(self) -> None: 855 """ 856 | Description | Test Active Diagnostic Trouble Codes (DM1) | 857 | :------------------- | :--------------------------------------------------------------- | 858 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 859 | Instructions | 1. Request Active Diagnostic Trouble Codes </br>\ 860 2. Validate data is within specifications </br>\ 861 3. Log results | 862 | Estimated Duration | 1 second | 863 """ 864 dm1_request = CANFrame(pgn=PGN["Request"], data=[PGN["Active Diagnostic Trouble Codes", [32]].id]) 865 failed_values = [] 866 with CANBus(BATTERY_CHANNEL) as bus: 867 if dm1_frame := bus.process_call(dm1_request): 868 if dm1_frame.pgn.id != PGN["Active Diagnostic Trouble Codes", [32]].id: 869 Errors.unexpected_packet("Active Diagnostic Trouble Codes", dm1_frame) 870 message = f"Unexpected data packet PGN {dm1_frame.pgn.id} was received" 871 logger.write_failure_to_html_report(message) 872 pytest.fail(message) 873 874 logger.write_result_to_html_report( 875 "<span style='font-weight: bold'>Active Diagnostic Trouble Codes</span>" 876 ) 877 878 dm1_data = dm1_frame.data 879 pgn_data_field = PGN["Active Diagnostic Trouble Codes"].data_field 880 881 for spn, elem in zip(pgn_data_field, dm1_data): 882 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 883 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 884 high_value = 1 885 886 if spn.name in ( 887 "Protect Lamp", 888 "Amber Warning Lamp", 889 "Red Stop Lamp", 890 "Malfunction Indicator Lamp", 891 "DTC1.SPN_Conversion_Method", 892 ): 893 high_value = 1 894 895 if spn.name in ( 896 "Flash Protect Lamp", 897 "Flash Amber Warning Lamp", 898 "Flash Red Stop Lamp", 899 "Flash Malfunction Indicator Lamp", 900 ): 901 high_value = 3 902 903 if spn.name == "DTC1.Suspect_Parameter_Number": 904 high_value = 524287 905 906 if spn.name == "DTC1.Failure_Mode_Identifier": 907 high_value = 31 908 909 if spn.name == "DTC1.Occurrence_Count": 910 high_value = 126 911 912 if not 0 <= elem <= high_value: 913 logger.write_warning_to_html_report( 914 f"{spn.name}: {cmp(elem, '>=', 0, description)} and " 915 f"{cmp(elem, '<=', high_value, description)}" 916 ) 917 failed_values.append(spn.name) 918 919 if len(failed_values) > 0: 920 categories = "data values" if len(failed_values) > 1 else "data value" 921 message = ( 922 f"{len(failed_values)} {categories} failed " 923 f"Active Diagnostic Trouble Code specifications: {', '.join(failed_values)}" 924 ) 925 logger.write_failure_to_html_report(message) 926 pytest.fail(message) 927 928 else: 929 message = ( 930 f"No response was received for mandatory command: " 931 f"PGN {PGN['Active Diagnostic Trouble Codes', [32]].id} (Active Diagnostic Trouble Codes)" 932 ) 933 logger.write_failure_to_html_report(message) 934 pytest.fail(message)
Test that Active Diagnostic Trouble Codes are J1939 Compliant
854 def test_active_diagnostic_trouble_codes(self) -> None: 855 """ 856 | Description | Test Active Diagnostic Trouble Codes (DM1) | 857 | :------------------- | :--------------------------------------------------------------- | 858 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 859 | Instructions | 1. Request Active Diagnostic Trouble Codes </br>\ 860 2. Validate data is within specifications </br>\ 861 3. Log results | 862 | Estimated Duration | 1 second | 863 """ 864 dm1_request = CANFrame(pgn=PGN["Request"], data=[PGN["Active Diagnostic Trouble Codes", [32]].id]) 865 failed_values = [] 866 with CANBus(BATTERY_CHANNEL) as bus: 867 if dm1_frame := bus.process_call(dm1_request): 868 if dm1_frame.pgn.id != PGN["Active Diagnostic Trouble Codes", [32]].id: 869 Errors.unexpected_packet("Active Diagnostic Trouble Codes", dm1_frame) 870 message = f"Unexpected data packet PGN {dm1_frame.pgn.id} was received" 871 logger.write_failure_to_html_report(message) 872 pytest.fail(message) 873 874 logger.write_result_to_html_report( 875 "<span style='font-weight: bold'>Active Diagnostic Trouble Codes</span>" 876 ) 877 878 dm1_data = dm1_frame.data 879 pgn_data_field = PGN["Active Diagnostic Trouble Codes"].data_field 880 881 for spn, elem in zip(pgn_data_field, dm1_data): 882 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 883 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 884 high_value = 1 885 886 if spn.name in ( 887 "Protect Lamp", 888 "Amber Warning Lamp", 889 "Red Stop Lamp", 890 "Malfunction Indicator Lamp", 891 "DTC1.SPN_Conversion_Method", 892 ): 893 high_value = 1 894 895 if spn.name in ( 896 "Flash Protect Lamp", 897 "Flash Amber Warning Lamp", 898 "Flash Red Stop Lamp", 899 "Flash Malfunction Indicator Lamp", 900 ): 901 high_value = 3 902 903 if spn.name == "DTC1.Suspect_Parameter_Number": 904 high_value = 524287 905 906 if spn.name == "DTC1.Failure_Mode_Identifier": 907 high_value = 31 908 909 if spn.name == "DTC1.Occurrence_Count": 910 high_value = 126 911 912 if not 0 <= elem <= high_value: 913 logger.write_warning_to_html_report( 914 f"{spn.name}: {cmp(elem, '>=', 0, description)} and " 915 f"{cmp(elem, '<=', high_value, description)}" 916 ) 917 failed_values.append(spn.name) 918 919 if len(failed_values) > 0: 920 categories = "data values" if len(failed_values) > 1 else "data value" 921 message = ( 922 f"{len(failed_values)} {categories} failed " 923 f"Active Diagnostic Trouble Code specifications: {', '.join(failed_values)}" 924 ) 925 logger.write_failure_to_html_report(message) 926 pytest.fail(message) 927 928 else: 929 message = ( 930 f"No response was received for mandatory command: " 931 f"PGN {PGN['Active Diagnostic Trouble Codes', [32]].id} (Active Diagnostic Trouble Codes)" 932 ) 933 logger.write_failure_to_html_report(message) 934 pytest.fail(message)
| Description | Test Active Diagnostic Trouble Codes (DM1) |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#389 |
| Instructions | 1. Request Active Diagnostic Trouble Codes 2. Validate data is within specifications 3. Log results |
| Estimated Duration | 1 second |
1063class TestVehicleElectricalPower: 1064 """Test Vehicle Electrical Power command is J1939 Compliant""" 1065 1066 def test_vehicle_electrical_power(self) -> None: 1067 """ 1068 | Description | Test Vehicle Electrical Power #5 (VEP5) command | 1069 | :------------------- | :--------------------------------------------------------------- | 1070 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 1071 | Instructions | 1. Send Vehicle Electrical Power #5 command </br>\ 1072 2. Validate data is within specifications </br>\ 1073 3. Log response | 1074 | Estimated Duration | 1 second | 1075 """ 1076 failed_values = [] 1077 with CANBus(BATTERY_CHANNEL) as bus: 1078 vehicle_electrical_power_request = CANFrame( 1079 destination_address=BATTERY_INFO.address, 1080 pgn=PGN["Request"], 1081 data=[PGN["Vehicle Electrical Power #5"].id], 1082 ) 1083 if vehicle_electrical_power_response := bus.process_call(vehicle_electrical_power_request): 1084 if vehicle_electrical_power_response.pgn.id != PGN["Vehicle Electrical Power #5", [32]].id: 1085 if vehicle_electrical_power_response.pgn.id == 59392: 1086 message = ( 1087 f"Received PGN {vehicle_electrical_power_response.pgn.id} " 1088 f"({spn_types.acknowledgement(vehicle_electrical_power_response.data[0])}), " 1089 f'not PGN {PGN["Vehicle Electrical Power #5", [32]].id} ' 1090 ) 1091 else: 1092 message = ( 1093 f"Received PGN {vehicle_electrical_power_response.pgn.id} " 1094 f"{vehicle_electrical_power_response.pgn.short_name}, " 1095 f'not PGN {PGN["Vehicle Electrical Power #5", [32]].id} ' 1096 ) 1097 logger.write_failure_to_html_report(message) 1098 pytest.fail(message) 1099 else: 1100 message = ( 1101 f"Battery did not respond to PGN {PGN['Vehicle Electrical Power #5', [32]].id} " 1102 f"Vehicle Electrical Power #5 request" 1103 ) 1104 logger.write_failure_to_html_report(message) 1105 pytest.fail(message) 1106 1107 vep5_data = vehicle_electrical_power_response.data 1108 vep5_pgn = PGN["Vehicle Electrical Power #5"].data_field 1109 1110 logger.write_result_to_html_report("<span style='font-weight: bold'>Vehicle Electrical Power #5</span>") 1111 1112 for spn, elem in zip(vep5_pgn, vep5_data): 1113 if spn.name == "Reserved": 1114 continue 1115 1116 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 1117 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 1118 high_value = 1.0 1119 if spn.name == "SLI Battery Pack State of Charge": 1120 high_value = 160.6375 1121 1122 if spn.name == "SLI Battery Pack Capacity": 1123 high_value = 64255 1124 1125 if spn.name == "SLI Battery Pack Health": 1126 high_value = 125 1127 1128 if spn.name == "SLI Cranking Predicted Minimum Battery Voltage": 1129 high_value = 50 1130 1131 if not 0 <= elem <= high_value: 1132 logger.write_warning_to_html_report( 1133 f"{spn.name}: {cmp(elem, '>=', 0, description)} and " 1134 f"{cmp(elem, '<=', high_value, description)}" 1135 ) 1136 failed_values.append(spn.name) 1137 1138 if len(failed_values) > 0: 1139 categories = "data values" if len(failed_values) > 1 else "data value" 1140 message = ( 1141 f"{len(failed_values)} {categories} failed " 1142 f"Configuration State Message specifications: {', '.join(failed_values)}" 1143 ) 1144 logger.write_failure_to_html_report(message) 1145 pytest.fail(message) 1146 1147 logger.write_result_to_html_report("Test Vehicle Electrical Power #5 command was successful")
Test Vehicle Electrical Power command is J1939 Compliant
1066 def test_vehicle_electrical_power(self) -> None: 1067 """ 1068 | Description | Test Vehicle Electrical Power #5 (VEP5) command | 1069 | :------------------- | :--------------------------------------------------------------- | 1070 | GitHub Issue | turnaroundfactor/BMS-HW-Test#389 | 1071 | Instructions | 1. Send Vehicle Electrical Power #5 command </br>\ 1072 2. Validate data is within specifications </br>\ 1073 3. Log response | 1074 | Estimated Duration | 1 second | 1075 """ 1076 failed_values = [] 1077 with CANBus(BATTERY_CHANNEL) as bus: 1078 vehicle_electrical_power_request = CANFrame( 1079 destination_address=BATTERY_INFO.address, 1080 pgn=PGN["Request"], 1081 data=[PGN["Vehicle Electrical Power #5"].id], 1082 ) 1083 if vehicle_electrical_power_response := bus.process_call(vehicle_electrical_power_request): 1084 if vehicle_electrical_power_response.pgn.id != PGN["Vehicle Electrical Power #5", [32]].id: 1085 if vehicle_electrical_power_response.pgn.id == 59392: 1086 message = ( 1087 f"Received PGN {vehicle_electrical_power_response.pgn.id} " 1088 f"({spn_types.acknowledgement(vehicle_electrical_power_response.data[0])}), " 1089 f'not PGN {PGN["Vehicle Electrical Power #5", [32]].id} ' 1090 ) 1091 else: 1092 message = ( 1093 f"Received PGN {vehicle_electrical_power_response.pgn.id} " 1094 f"{vehicle_electrical_power_response.pgn.short_name}, " 1095 f'not PGN {PGN["Vehicle Electrical Power #5", [32]].id} ' 1096 ) 1097 logger.write_failure_to_html_report(message) 1098 pytest.fail(message) 1099 else: 1100 message = ( 1101 f"Battery did not respond to PGN {PGN['Vehicle Electrical Power #5', [32]].id} " 1102 f"Vehicle Electrical Power #5 request" 1103 ) 1104 logger.write_failure_to_html_report(message) 1105 pytest.fail(message) 1106 1107 vep5_data = vehicle_electrical_power_response.data 1108 vep5_pgn = PGN["Vehicle Electrical Power #5"].data_field 1109 1110 logger.write_result_to_html_report("<span style='font-weight: bold'>Vehicle Electrical Power #5</span>") 1111 1112 for spn, elem in zip(vep5_pgn, vep5_data): 1113 if spn.name == "Reserved": 1114 continue 1115 1116 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 1117 logger.write_result_to_html_report(f"{spn.name}: {elem} {description}") 1118 high_value = 1.0 1119 if spn.name == "SLI Battery Pack State of Charge": 1120 high_value = 160.6375 1121 1122 if spn.name == "SLI Battery Pack Capacity": 1123 high_value = 64255 1124 1125 if spn.name == "SLI Battery Pack Health": 1126 high_value = 125 1127 1128 if spn.name == "SLI Cranking Predicted Minimum Battery Voltage": 1129 high_value = 50 1130 1131 if not 0 <= elem <= high_value: 1132 logger.write_warning_to_html_report( 1133 f"{spn.name}: {cmp(elem, '>=', 0, description)} and " 1134 f"{cmp(elem, '<=', high_value, description)}" 1135 ) 1136 failed_values.append(spn.name) 1137 1138 if len(failed_values) > 0: 1139 categories = "data values" if len(failed_values) > 1 else "data value" 1140 message = ( 1141 f"{len(failed_values)} {categories} failed " 1142 f"Configuration State Message specifications: {', '.join(failed_values)}" 1143 ) 1144 logger.write_failure_to_html_report(message) 1145 pytest.fail(message) 1146 1147 logger.write_result_to_html_report("Test Vehicle Electrical Power #5 command was successful")
| Description | Test Vehicle Electrical Power #5 (VEP5) command |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#389 |
| Instructions | 1. Send Vehicle Electrical Power #5 command 2. Validate data is within specifications 3. Log response |
| Estimated Duration | 1 second |
1150class TestManufacturerCommands: 1151 """Test Manufacturer Commands are compliant with specs""" 1152 1153 def saft_commands_test(self): 1154 """Tests SAFT Manufacturer Commands""" 1155 with CANBus(BATTERY_CHANNEL) as bus: 1156 manufactured_command_request = CANFrame(destination_address=BATTERY_INFO.address, pgn=PGN["RQST"], data=[0]) 1157 invalid_response = [] 1158 for address in itertools.chain( 1159 [0xFFD2], range(0xFFD4, 0xFFD9), range(0xFFDC, 0xFFDF), range(0xFFE0, 0xFFE2), [0xFFE4] 1160 ): 1161 manufactured_command_request.data = [address] 1162 default_pgn = PGN[address] 1163 pgn_name = default_pgn.name 1164 logger.write_result_to_html_report( 1165 f"<span style='font-weight: bold'>PGN {address} ({pgn_name})---</span>" 1166 ) 1167 1168 if response_frame := bus.process_call(manufactured_command_request): 1169 if not response_frame.pgn.id == default_pgn.id: 1170 1171 if response_frame.pgn.id == PGN["ACKM", [32]].id: 1172 message = ( 1173 f"Expected {address} ({pgn_name}): Received PGN {response_frame.pgn.id} " 1174 f"({spn_types.acknowledgement(response_frame.data[0])}) " 1175 ) 1176 logger.write_warning_to_html_report(message) 1177 else: 1178 logger.write_warning_to_html_report( 1179 f"Expected PGN {address} ({pgn_name}), but received " 1180 f"{response_frame.pgn.id} ({response_frame.pgn.name}). " 1181 f"Unable to complete check for command" 1182 ) 1183 invalid_response.append(f"PGN {address} ({pgn_name})") 1184 continue 1185 else: 1186 message = f"Did not receive response from PGN {address} {pgn_name}" 1187 logger.write_warning_to_html_report(message) 1188 invalid_response.append(f"PGN {address} ({pgn_name})") 1189 1190 continue 1191 1192 if response_frame.priority != default_pgn.default_priority: 1193 message = ( 1194 f"Expected priority level of {default_pgn.default_priority}" 1195 f" but got priority level {response_frame.priority} for PGN {address}, {pgn_name}" 1196 ) 1197 invalid_response.append(f"PGN {address} {pgn_name}") 1198 logger.write_warning_to_html_report(message) 1199 1200 if len(response_frame.packed_data) != 8: 1201 message = ( 1202 f"Unexpected data length for PGN {address}, {pgn_name}. Expected length of 8, " 1203 f"received {len(response_frame.packed_data)}" 1204 ) 1205 logger.write_warning_to_html_report(message) 1206 1207 not_passed_elem = [] 1208 for spn, elem in zip(default_pgn.data_field, response_frame.data): 1209 low_range = 0 1210 high_range = 3 1211 spn_name = spn.name 1212 if spn.name == "Reserved": 1213 continue 1214 1215 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 1216 1217 logger.write_result_to_html_report(f"{spn_name}: {elem}{description}") 1218 1219 if pgn_name == "Battery ECU Status": 1220 if spn_name in ("Battery Mode", "FET Array State", "SOC Mode", "Heat Reason"): 1221 high_range = 7 1222 elif spn_name == "Long-Term Fault Log Status": 1223 high_range = 15 1224 elif spn_name == "Software Part Number": 1225 high_range = 64255 1226 1227 if pgn_name in ("Battery Cell Status 1", "Battery Cell Status 2"): 1228 high_range = 6.4255 1229 1230 if pgn_name == "Battery Performance": 1231 if spn_name == "Battery Current": 1232 low_range = -82000.00 1233 high_range = 82495.35 1234 if spn_name == "Internal State of Health": 1235 low_range = -204.800 1236 high_range = 204.775 1237 1238 if pgn_name == "Battery Temperatures": 1239 if spn_name == "MCU Temperature": 1240 low_range = -40 1241 high_range = 210 1242 else: 1243 low_range = -50 1244 high_range = 200 1245 1246 if pgn_name == "Battery Balancing Circuit Info": 1247 if spn.name == "Cell Voltage Difference": 1248 high_range = 6.4255 1249 if spn_name == "Cell Voltage Sum": 1250 high_range = 104.8576 1251 1252 if pgn_name in ("Battery Cell Upper SOC", "Battery Cell Lower SOC"): 1253 low_range = -10 1254 high_range = 115 1255 1256 if pgn_name == "Battery Function Status": 1257 if spn_name == "Heater Set Point": 1258 low_range = -50 1259 high_range = 25 1260 if spn_name == "Storage Delay Time Limit": 1261 high_range = 65535 1262 if spn_name == "Last Storage Duration (Minutes)": 1263 high_range = 59 1264 if spn_name == "Last Storage Duration (Hours)": 1265 high_range = 23 1266 if spn_name == "Last Storage Duration (Days)": 1267 high_range = 31 1268 if spn_name == "Last Storage Duration (Months)": 1269 high_range = 255 1270 if spn_name == "Effective Reset Time": 1271 high_range = 60 1272 1273 if not low_range <= elem <= high_range: 1274 logger.write_warning_to_html_report( 1275 f"{spn.name}: {cmp(elem, '>=', 0, description)} and " 1276 f"{cmp(elem, '<=', high_range, description)}" 1277 ) 1278 not_passed_elem.append(spn_name) 1279 1280 if len(not_passed_elem) == 0: 1281 message = f"✅ All data fields in PGN {default_pgn.id} ({pgn_name}) met requirements" 1282 logger.write_result_to_html_report(message) 1283 1284 if len(invalid_response) > 0: 1285 message = ( 1286 f"{len(invalid_response)} SAFT Manufacturer Command{'s' if len(invalid_response) > 1 else ''} " 1287 f"failed: {', '.join(invalid_response)}" 1288 ) 1289 logger.write_failure_to_html_report(message) 1290 pytest.fail(message) 1291 else: 1292 logger.write_result_to_html_report("All SAFT Manufacturer Commands passed") 1293 1294 def test_manufacturer_commands(self) -> None: 1295 """ 1296 | Description | Fingerprint Manufacturer Commands | 1297 | :------------------- | :--------------------------------------------------------------- | 1298 | GitHub Issue | turnaroundfactor/BMS-HW-Test#388 | 1299 | Instructions | 1. Send request for manufacturer command </br>\ 1300 2. Check values in data </br>\ 1301 3. Log response | 1302 | Estimated Duration | 2 seconds | 1303 """ 1304 1305 if BATTERY_INFO.manufacturer_code == ManufacturerID.SAFT: 1306 self.saft_commands_test() 1307 else: 1308 message = "No known manufacturer commands to test" 1309 logger.write_result_to_html_report(message) 1310 pytest.skip(message)
Test Manufacturer Commands are compliant with specs
1153 def saft_commands_test(self): 1154 """Tests SAFT Manufacturer Commands""" 1155 with CANBus(BATTERY_CHANNEL) as bus: 1156 manufactured_command_request = CANFrame(destination_address=BATTERY_INFO.address, pgn=PGN["RQST"], data=[0]) 1157 invalid_response = [] 1158 for address in itertools.chain( 1159 [0xFFD2], range(0xFFD4, 0xFFD9), range(0xFFDC, 0xFFDF), range(0xFFE0, 0xFFE2), [0xFFE4] 1160 ): 1161 manufactured_command_request.data = [address] 1162 default_pgn = PGN[address] 1163 pgn_name = default_pgn.name 1164 logger.write_result_to_html_report( 1165 f"<span style='font-weight: bold'>PGN {address} ({pgn_name})---</span>" 1166 ) 1167 1168 if response_frame := bus.process_call(manufactured_command_request): 1169 if not response_frame.pgn.id == default_pgn.id: 1170 1171 if response_frame.pgn.id == PGN["ACKM", [32]].id: 1172 message = ( 1173 f"Expected {address} ({pgn_name}): Received PGN {response_frame.pgn.id} " 1174 f"({spn_types.acknowledgement(response_frame.data[0])}) " 1175 ) 1176 logger.write_warning_to_html_report(message) 1177 else: 1178 logger.write_warning_to_html_report( 1179 f"Expected PGN {address} ({pgn_name}), but received " 1180 f"{response_frame.pgn.id} ({response_frame.pgn.name}). " 1181 f"Unable to complete check for command" 1182 ) 1183 invalid_response.append(f"PGN {address} ({pgn_name})") 1184 continue 1185 else: 1186 message = f"Did not receive response from PGN {address} {pgn_name}" 1187 logger.write_warning_to_html_report(message) 1188 invalid_response.append(f"PGN {address} ({pgn_name})") 1189 1190 continue 1191 1192 if response_frame.priority != default_pgn.default_priority: 1193 message = ( 1194 f"Expected priority level of {default_pgn.default_priority}" 1195 f" but got priority level {response_frame.priority} for PGN {address}, {pgn_name}" 1196 ) 1197 invalid_response.append(f"PGN {address} {pgn_name}") 1198 logger.write_warning_to_html_report(message) 1199 1200 if len(response_frame.packed_data) != 8: 1201 message = ( 1202 f"Unexpected data length for PGN {address}, {pgn_name}. Expected length of 8, " 1203 f"received {len(response_frame.packed_data)}" 1204 ) 1205 logger.write_warning_to_html_report(message) 1206 1207 not_passed_elem = [] 1208 for spn, elem in zip(default_pgn.data_field, response_frame.data): 1209 low_range = 0 1210 high_range = 3 1211 spn_name = spn.name 1212 if spn.name == "Reserved": 1213 continue 1214 1215 description = f"({desc})" if (desc := spn.data_type(elem)) else "" 1216 1217 logger.write_result_to_html_report(f"{spn_name}: {elem}{description}") 1218 1219 if pgn_name == "Battery ECU Status": 1220 if spn_name in ("Battery Mode", "FET Array State", "SOC Mode", "Heat Reason"): 1221 high_range = 7 1222 elif spn_name == "Long-Term Fault Log Status": 1223 high_range = 15 1224 elif spn_name == "Software Part Number": 1225 high_range = 64255 1226 1227 if pgn_name in ("Battery Cell Status 1", "Battery Cell Status 2"): 1228 high_range = 6.4255 1229 1230 if pgn_name == "Battery Performance": 1231 if spn_name == "Battery Current": 1232 low_range = -82000.00 1233 high_range = 82495.35 1234 if spn_name == "Internal State of Health": 1235 low_range = -204.800 1236 high_range = 204.775 1237 1238 if pgn_name == "Battery Temperatures": 1239 if spn_name == "MCU Temperature": 1240 low_range = -40 1241 high_range = 210 1242 else: 1243 low_range = -50 1244 high_range = 200 1245 1246 if pgn_name == "Battery Balancing Circuit Info": 1247 if spn.name == "Cell Voltage Difference": 1248 high_range = 6.4255 1249 if spn_name == "Cell Voltage Sum": 1250 high_range = 104.8576 1251 1252 if pgn_name in ("Battery Cell Upper SOC", "Battery Cell Lower SOC"): 1253 low_range = -10 1254 high_range = 115 1255 1256 if pgn_name == "Battery Function Status": 1257 if spn_name == "Heater Set Point": 1258 low_range = -50 1259 high_range = 25 1260 if spn_name == "Storage Delay Time Limit": 1261 high_range = 65535 1262 if spn_name == "Last Storage Duration (Minutes)": 1263 high_range = 59 1264 if spn_name == "Last Storage Duration (Hours)": 1265 high_range = 23 1266 if spn_name == "Last Storage Duration (Days)": 1267 high_range = 31 1268 if spn_name == "Last Storage Duration (Months)": 1269 high_range = 255 1270 if spn_name == "Effective Reset Time": 1271 high_range = 60 1272 1273 if not low_range <= elem <= high_range: 1274 logger.write_warning_to_html_report( 1275 f"{spn.name}: {cmp(elem, '>=', 0, description)} and " 1276 f"{cmp(elem, '<=', high_range, description)}" 1277 ) 1278 not_passed_elem.append(spn_name) 1279 1280 if len(not_passed_elem) == 0: 1281 message = f"✅ All data fields in PGN {default_pgn.id} ({pgn_name}) met requirements" 1282 logger.write_result_to_html_report(message) 1283 1284 if len(invalid_response) > 0: 1285 message = ( 1286 f"{len(invalid_response)} SAFT Manufacturer Command{'s' if len(invalid_response) > 1 else ''} " 1287 f"failed: {', '.join(invalid_response)}" 1288 ) 1289 logger.write_failure_to_html_report(message) 1290 pytest.fail(message) 1291 else: 1292 logger.write_result_to_html_report("All SAFT Manufacturer Commands passed")
Tests SAFT Manufacturer Commands
1294 def test_manufacturer_commands(self) -> None: 1295 """ 1296 | Description | Fingerprint Manufacturer Commands | 1297 | :------------------- | :--------------------------------------------------------------- | 1298 | GitHub Issue | turnaroundfactor/BMS-HW-Test#388 | 1299 | Instructions | 1. Send request for manufacturer command </br>\ 1300 2. Check values in data </br>\ 1301 3. Log response | 1302 | Estimated Duration | 2 seconds | 1303 """ 1304 1305 if BATTERY_INFO.manufacturer_code == ManufacturerID.SAFT: 1306 self.saft_commands_test() 1307 else: 1308 message = "No known manufacturer commands to test" 1309 logger.write_result_to_html_report(message) 1310 pytest.skip(message)
| Description | Fingerprint Manufacturer Commands |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#388 |
| Instructions | 1. Send request for manufacturer command 2. Check values in data 3. Log response |
| Estimated Duration | 2 seconds |
1436class TestECUInformation: 1437 """Gets information about the physical ECU and its hardware""" 1438 1439 @staticmethod 1440 def bytes_to_ascii(bs: list[float]) -> list[str]: 1441 """Converts bytes to ASCII string""" 1442 s: str = "" 1443 for b in bs: 1444 h = re.sub(r"^[^0-9a-fA-F]+$", "", f"{b:x}") 1445 try: 1446 ba = bytearray.fromhex(h)[::-1] 1447 s += ba.decode("utf-8", "ignore") 1448 s = re.sub(r"[^\x20-\x7E]", "", s) 1449 except ValueError: 1450 # NOTE: This will ignore any invalid packets (from BrenTronics) 1451 logger.write_warning_to_report(f"Skipping invalid hex: {b:x}") 1452 return list(filter(None, s.split("*"))) 1453 1454 def test_ecu_information(self) -> None: 1455 """ 1456 | Description | Get information from ECUID response | 1457 | :------------------- | :--------------------------------------------------------------- | 1458 | GitHub Issue | turnaroundfactor/BMS-HW-Test#395 | 1459 | Instructions | 1. Request ECUID data </br>\ 1460 2. Validate data is within specifications </br>\ 1461 3. Log response | 1462 | Estimated Duration | 10 seconds | 1463 """ 1464 1465 info = [] 1466 with CANBus(BATTERY_CHANNEL) as bus: 1467 ecu_request = CANFrame(pgn=PGN["Request"], data=[PGN["ECUID"].id]) 1468 if tp_cm_frame := bus.process_call(ecu_request): 1469 if tp_cm_frame is not None: 1470 if tp_cm_frame.pgn.id == PGN["TP.CM", [32]].id: 1471 data = [] 1472 cts_request = CANFrame(pgn=PGN["TP.CM"], data=[17, 0, 1, 0xFF, 0]) 1473 if data_frame := bus.process_call(cts_request): 1474 if data_frame is not None: 1475 if data_frame.pgn.id == PGN["TP.DT"].id: 1476 packet_count = int(tp_cm_frame.data[2]) 1477 # NOTE: This will ignore the invalid header packet from BrenTronics 1478 if BATTERY_INFO.manufacturer_code != ManufacturerID.BRENTRONICS: 1479 data.append(data_frame.data[1]) 1480 packet_count -= 1 1481 for _ in range(packet_count): 1482 data_message = bus.read_input() 1483 if data_message is not None: 1484 frame = CANFrame.decode(data_message.arbitration_id, data_message.data) 1485 if frame.pgn.id == PGN["TP.DT"].id: 1486 data.append(frame.data[1]) 1487 else: 1488 Errors.unexpected_packet("TP.DT", frame) 1489 break 1490 else: 1491 Errors.no_packet("TP.DT") 1492 break 1493 info = self.bytes_to_ascii(data) 1494 eom_request = CANFrame( 1495 pgn=PGN["TP.CM"], 1496 data=[17, tp_cm_frame.data[1], packet_count, 0xFF, tp_cm_frame.data[-1]], 1497 ) 1498 if eom_frame := bus.process_call(eom_request): 1499 if eom_frame is not None: 1500 if eom_frame.pgn.id == PGN["DM15"].id: 1501 if eom_frame.data[2] == 4: # Operation Completed 1502 logger.write_info_to_report("ECUID data transfer successful") 1503 else: 1504 logger.write_warning_to_html_report("Unsuccessful EOM response") 1505 else: 1506 Errors.unexpected_packet("DM15", eom_frame) 1507 else: 1508 Errors.no_packet("DM15") 1509 else: 1510 # timeout 1511 logger.write_warning_to_report("No response after sending EOM (DM15)") 1512 else: 1513 Errors.unexpected_packet("TP.DT", data_frame) 1514 else: 1515 Errors.no_packet("TP.DT") 1516 else: 1517 message = f"Did not receive response from PGN {PGN['TP.CM', [32]].id} (TP.CM)" 1518 logger.write_failure_to_html_report(message) 1519 pytest.fail(message) 1520 else: 1521 Errors.unexpected_packet("TP.CM", tp_cm_frame) 1522 else: 1523 Errors.no_packet("TP.CM") 1524 else: 1525 message = f"Did not receive response from PGN {PGN['ECUID', [32]].id}" 1526 logger.write_failure_to_html_report(message) 1527 pytest.fail(message) 1528 1529 if len(info) > 0: 1530 ecu = { 1531 "part_number": info[0], 1532 "serial_number": info[1], 1533 "location_name": info[2], 1534 "manufacturer": info[3], 1535 "classification": info[4], 1536 } 1537 logger.write_result_to_html_report("<span style='font-weight: bold'>ECUID Information </span>") 1538 for key, value in ecu.items(): 1539 logger.write_result_to_html_report(f"{key.strip().replace('_', ' ').title()}: {value}") 1540 else: 1541 logger.write_failure_to_html_report("Could not get ECU information") 1542 pytest.fail("Could not get ECU information")
Gets information about the physical ECU and its hardware
1439 @staticmethod 1440 def bytes_to_ascii(bs: list[float]) -> list[str]: 1441 """Converts bytes to ASCII string""" 1442 s: str = "" 1443 for b in bs: 1444 h = re.sub(r"^[^0-9a-fA-F]+$", "", f"{b:x}") 1445 try: 1446 ba = bytearray.fromhex(h)[::-1] 1447 s += ba.decode("utf-8", "ignore") 1448 s = re.sub(r"[^\x20-\x7E]", "", s) 1449 except ValueError: 1450 # NOTE: This will ignore any invalid packets (from BrenTronics) 1451 logger.write_warning_to_report(f"Skipping invalid hex: {b:x}") 1452 return list(filter(None, s.split("*")))
Converts bytes to ASCII string
1454 def test_ecu_information(self) -> None: 1455 """ 1456 | Description | Get information from ECUID response | 1457 | :------------------- | :--------------------------------------------------------------- | 1458 | GitHub Issue | turnaroundfactor/BMS-HW-Test#395 | 1459 | Instructions | 1. Request ECUID data </br>\ 1460 2. Validate data is within specifications </br>\ 1461 3. Log response | 1462 | Estimated Duration | 10 seconds | 1463 """ 1464 1465 info = [] 1466 with CANBus(BATTERY_CHANNEL) as bus: 1467 ecu_request = CANFrame(pgn=PGN["Request"], data=[PGN["ECUID"].id]) 1468 if tp_cm_frame := bus.process_call(ecu_request): 1469 if tp_cm_frame is not None: 1470 if tp_cm_frame.pgn.id == PGN["TP.CM", [32]].id: 1471 data = [] 1472 cts_request = CANFrame(pgn=PGN["TP.CM"], data=[17, 0, 1, 0xFF, 0]) 1473 if data_frame := bus.process_call(cts_request): 1474 if data_frame is not None: 1475 if data_frame.pgn.id == PGN["TP.DT"].id: 1476 packet_count = int(tp_cm_frame.data[2]) 1477 # NOTE: This will ignore the invalid header packet from BrenTronics 1478 if BATTERY_INFO.manufacturer_code != ManufacturerID.BRENTRONICS: 1479 data.append(data_frame.data[1]) 1480 packet_count -= 1 1481 for _ in range(packet_count): 1482 data_message = bus.read_input() 1483 if data_message is not None: 1484 frame = CANFrame.decode(data_message.arbitration_id, data_message.data) 1485 if frame.pgn.id == PGN["TP.DT"].id: 1486 data.append(frame.data[1]) 1487 else: 1488 Errors.unexpected_packet("TP.DT", frame) 1489 break 1490 else: 1491 Errors.no_packet("TP.DT") 1492 break 1493 info = self.bytes_to_ascii(data) 1494 eom_request = CANFrame( 1495 pgn=PGN["TP.CM"], 1496 data=[17, tp_cm_frame.data[1], packet_count, 0xFF, tp_cm_frame.data[-1]], 1497 ) 1498 if eom_frame := bus.process_call(eom_request): 1499 if eom_frame is not None: 1500 if eom_frame.pgn.id == PGN["DM15"].id: 1501 if eom_frame.data[2] == 4: # Operation Completed 1502 logger.write_info_to_report("ECUID data transfer successful") 1503 else: 1504 logger.write_warning_to_html_report("Unsuccessful EOM response") 1505 else: 1506 Errors.unexpected_packet("DM15", eom_frame) 1507 else: 1508 Errors.no_packet("DM15") 1509 else: 1510 # timeout 1511 logger.write_warning_to_report("No response after sending EOM (DM15)") 1512 else: 1513 Errors.unexpected_packet("TP.DT", data_frame) 1514 else: 1515 Errors.no_packet("TP.DT") 1516 else: 1517 message = f"Did not receive response from PGN {PGN['TP.CM', [32]].id} (TP.CM)" 1518 logger.write_failure_to_html_report(message) 1519 pytest.fail(message) 1520 else: 1521 Errors.unexpected_packet("TP.CM", tp_cm_frame) 1522 else: 1523 Errors.no_packet("TP.CM") 1524 else: 1525 message = f"Did not receive response from PGN {PGN['ECUID', [32]].id}" 1526 logger.write_failure_to_html_report(message) 1527 pytest.fail(message) 1528 1529 if len(info) > 0: 1530 ecu = { 1531 "part_number": info[0], 1532 "serial_number": info[1], 1533 "location_name": info[2], 1534 "manufacturer": info[3], 1535 "classification": info[4], 1536 } 1537 logger.write_result_to_html_report("<span style='font-weight: bold'>ECUID Information </span>") 1538 for key, value in ecu.items(): 1539 logger.write_result_to_html_report(f"{key.strip().replace('_', ' ').title()}: {value}") 1540 else: 1541 logger.write_failure_to_html_report("Could not get ECU information") 1542 pytest.fail("Could not get ECU information")
| Description | Get information from ECUID response |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#395 |
| Instructions | 1. Request ECUID data 2. Validate data is within specifications 3. Log response |
| Estimated Duration | 10 seconds |
1545def memory_test(mode: Modes) -> list[int]: 1546 """Memory tester helper""" 1547 found_addresses = [] 1548 with CANBus(BATTERY_CHANNEL) as bus: 1549 read_request_frame = CANFrame(pgn=PGN["DM14"], data=[1, 1, 1, 0, 0, 0, 0, 0]) 1550 read_request_frame.data[2] = mode 1551 addresses = [0, 1107296256, 2147483648, 4294966271] # 0x0, 0x42000000, 0x80000000, 0xfffffbff 1552 for low_address in addresses: 1553 found = 0 1554 high_address = low_address + (1024 if FULL_MEMORY_TESTS else 16) 1555 for i in range(low_address, high_address): 1556 read_request_frame.data[5] = low_address 1557 if response_frame := bus.process_call(read_request_frame, timeout=1): # NOTE: SAFT times out 1558 if response_frame is not None: 1559 logger.write_info_to_report( 1560 f"Address {i} responded with PGN {response_frame.pgn.id} " 1561 f"({response_frame.pgn.short_name}) - status is: " 1562 f"{spn_types.dm15_status(response_frame.data[3])}" 1563 ) 1564 if response_frame.pgn.id == PGN["DM15"].id: 1565 if response_frame.data[5] != 258: # Invalid Length 1566 if response_frame.data[3] == 0: 1567 found_addresses.append(i) 1568 found += 1 1569 else: 1570 Errors.unexpected_packet("DM15", response_frame) 1571 break 1572 else: 1573 Errors.no_packet("DM15") 1574 break 1575 else: 1576 # timeout 1577 pass 1578 verb = "" 1579 match mode: 1580 case Modes.READ: 1581 verb = "readable" 1582 case Modes.WRITE: 1583 verb = "writable" 1584 case Modes.ERASE: 1585 verb = "erasable" 1586 case Modes.BOOT: 1587 verb = "boot load" 1588 message = ( 1589 f"Found {found} {verb} successful address(es) in memory ranges" 1590 f" {hex(low_address)}-{hex(high_address)}" 1591 ) 1592 logger.write_result_to_html_report(message) 1593 1594 if len(found_addresses) > 0: 1595 logger.write_result_to_html_report( 1596 f"Found {len(found_addresses)} {verb} memory address(es) out of " 1597 f"{4096 if FULL_MEMORY_TESTS else 64} possible addresses" 1598 ) 1599 else: 1600 message = f"Found 0 {verb} memory addresses out of {4096 if FULL_MEMORY_TESTS else 64} possible addresses" 1601 logger.write_warning_to_html_report(message) 1602 return found_addresses
Memory tester helper
1605class TestMemoryRead: 1606 """This will test the read capability of the memory""" 1607 1608 def test_read(self) -> None: 1609 """ 1610 | Description | Try to read from different memory locations | 1611 | :------------------- | :--------------------------------------------------------------- | 1612 | GitHub Issue | turnaroundfactor/BMS-HW-Test#379 </br>\ 1613 turnaroundfactor/BMS-HW-Test#380 | 1614 | Instructions | 1. Request memory read </br>\ 1615 2. Log any successful addresses | 1616 | Estimated Duration | 1 minute | 1617 """ 1618 memory_test(Modes.READ)
This will test the read capability of the memory
1608 def test_read(self) -> None: 1609 """ 1610 | Description | Try to read from different memory locations | 1611 | :------------------- | :--------------------------------------------------------------- | 1612 | GitHub Issue | turnaroundfactor/BMS-HW-Test#379 </br>\ 1613 turnaroundfactor/BMS-HW-Test#380 | 1614 | Instructions | 1. Request memory read </br>\ 1615 2. Log any successful addresses | 1616 | Estimated Duration | 1 minute | 1617 """ 1618 memory_test(Modes.READ)
| Description | Try to read from different memory locations |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#379 turnaroundfactor/BMS-HW-Test#380 |
| Instructions | 1. Request memory read 2. Log any successful addresses |
| Estimated Duration | 1 minute |
1621class TestMemoryWrite: 1622 """This will test the write capability of the memory""" 1623 1624 def test_write(self) -> None: 1625 """ 1626 | Description | Try to write to different memory locations | 1627 | :------------------- | :--------------------------------------------------------------- | 1628 | GitHub Issue | turnaroundfactor/BMS-HW-Test#379 </br>\ 1629 turnaroundfactor/BMS-HW-Test#380 | 1630 | Instructions | 1. Request memory write </br>\ 1631 2. Log any successful addresses | 1632 | Estimated Duration | 1 minute | 1633 """ 1634 memory_test(Modes.WRITE)
This will test the write capability of the memory
1624 def test_write(self) -> None: 1625 """ 1626 | Description | Try to write to different memory locations | 1627 | :------------------- | :--------------------------------------------------------------- | 1628 | GitHub Issue | turnaroundfactor/BMS-HW-Test#379 </br>\ 1629 turnaroundfactor/BMS-HW-Test#380 | 1630 | Instructions | 1. Request memory write </br>\ 1631 2. Log any successful addresses | 1632 | Estimated Duration | 1 minute | 1633 """ 1634 memory_test(Modes.WRITE)
| Description | Try to write to different memory locations |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#379 turnaroundfactor/BMS-HW-Test#380 |
| Instructions | 1. Request memory write 2. Log any successful addresses |
| Estimated Duration | 1 minute |
1637class TestMemoryErase: 1638 """This will test the erase capability of the memory""" 1639 1640 def test_erase(self) -> None: 1641 """ 1642 | Description | Try to erase different memory locations | 1643 | :------------------- | :--------------------------------------------------------------- | 1644 | GitHub Issue | turnaroundfactor/BMS-HW-Test#379 </br>\ 1645 turnaroundfactor/BMS-HW-Test#380 | 1646 | Instructions | 1. Request memory erase </br>\ 1647 2. Log any successful addresses | 1648 | Estimated Duration | 1 minute | 1649 """ 1650 memory_test(Modes.ERASE)
This will test the erase capability of the memory
1640 def test_erase(self) -> None: 1641 """ 1642 | Description | Try to erase different memory locations | 1643 | :------------------- | :--------------------------------------------------------------- | 1644 | GitHub Issue | turnaroundfactor/BMS-HW-Test#379 </br>\ 1645 turnaroundfactor/BMS-HW-Test#380 | 1646 | Instructions | 1. Request memory erase </br>\ 1647 2. Log any successful addresses | 1648 | Estimated Duration | 1 minute | 1649 """ 1650 memory_test(Modes.ERASE)
| Description | Try to erase different memory locations |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#379 turnaroundfactor/BMS-HW-Test#380 |
| Instructions | 1. Request memory erase 2. Log any successful addresses |
| Estimated Duration | 1 minute |
1653class TestMemoryBootLoad: 1654 """This will test the boot load capability of the memory""" 1655 1656 def test_boot(self) -> None: 1657 """ 1658 | Description | Try to boot load from different memory locations | 1659 | :------------------- | :--------------------------------------------------------------- | 1660 | GitHub Issue | turnaroundfactor/BMS-HW-Test#381 </br>\ 1661 turnaroundfactor/BMS-HW-Test#382 | 1662 | Instructions | 1. Request memory boot load </br>\ 1663 2. Log any successful addresses | 1664 | Estimated Duration | 1 minute | 1665 """ 1666 memory_test(Modes.BOOT)
This will test the boot load capability of the memory
1656 def test_boot(self) -> None: 1657 """ 1658 | Description | Try to boot load from different memory locations | 1659 | :------------------- | :--------------------------------------------------------------- | 1660 | GitHub Issue | turnaroundfactor/BMS-HW-Test#381 </br>\ 1661 turnaroundfactor/BMS-HW-Test#382 | 1662 | Instructions | 1. Request memory boot load </br>\ 1663 2. Log any successful addresses | 1664 | Estimated Duration | 1 minute | 1665 """ 1666 memory_test(Modes.BOOT)
| Description | Try to boot load from different memory locations |
|---|---|
| GitHub Issue | turnaroundfactor/BMS-HW-Test#381 turnaroundfactor/BMS-HW-Test#382 |
| Instructions | 1. Request memory boot load 2. Log any successful addresses |
| Estimated Duration | 1 minute |