hitl_tester.modules.bms.hitl_data
(c) 2020-2024 TurnAround Factor, Inc.
#
CUI DISTRIBUTION CONTROL
Controlled by: DLA J68 R&D SBIP
CUI Category: Small Business Research and Technology
Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS
POC: GOV SBIP Program Manager Denise Price, 571-767-0111
Distribution authorized to U.S. Government Agencies only, to protect information not owned by the
U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that
it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests
for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317,
Fort Belvoir, VA 22060-6221
#
SBIR DATA RIGHTS
Contract No.:SP4701-23-C-0083
Contractor Name: TurnAround Factor, Inc.
Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005
Expiration of SBIR Data Rights Period: September 24, 2029
The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer
software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights
in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause
contained in the above identified contract. No restrictions apply after the expiration date shown above. Any
reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce
the markings.
1# TODO: fix Python warnings 2# fmt: off 3# pylint: skip-file 4 5""" 6# (c) 2020-2024 TurnAround Factor, Inc. 7# 8# CUI DISTRIBUTION CONTROL 9# Controlled by: DLA J68 R&D SBIP 10# CUI Category: Small Business Research and Technology 11# Distribution/Dissemination Controls: PROTECTED BY SBIR DATA RIGHTS 12# POC: GOV SBIP Program Manager Denise Price, 571-767-0111 13# Distribution authorized to U.S. Government Agencies only, to protect information not owned by the 14# U.S. Government and protected by a contractor’s ‘limited rights’ statement, or received with the understanding that 15# it is not routinely transmitted outside the U.S. Government (determination made September 14, 2024). Other requests 16# for this document shall be referred to ACOR DLA Logistics Operations (J-68), 8725 John J. Kingman Rd., Suite 4317, 17# Fort Belvoir, VA 22060-6221 18# 19# SBIR DATA RIGHTS 20# Contract No.:SP4701-23-C-0083 21# Contractor Name: TurnAround Factor, Inc. 22# Contractor Address: 10365 Wood Park Ct. Suite 313 / Ashland, VA 23005 23# Expiration of SBIR Data Rights Period: September 24, 2029 24# The Government's rights to use, modify, reproduce, release, perform, display, or disclose technical data or computer 25# software marked with this legend are restricted during the period shown as provided in paragraph (b)(4) of the Rights 26# in Noncommercial Technical Data and Computer Software--Small Business Innovative Research (SBIR) Program clause 27# contained in the above identified contract. No restrictions apply after the expiration date shown above. Any 28# reproduction of technical data, computer software, or portions thereof marked with this legend must also reproduce 29# the markings. 30""" 31 32import datetime 33import struct 34 35C_CONSTANTS = { 36 "n_currents": 2, 37 "n_cells_series": 4, 38 "n_cells_parallel": 3, 39 "n_temps": 2, 40 "n_batteries": 1, 41 "n_terminals": 1, 42 "n_adcs": 3, 43 "n_smbus_word_size": 2, 44 "n_smbus_string_size": 25, 45} 46board_arguement = "" 47build_folder = "" 48 49 50# {CVARIABLE_START} 51# These translate into C types and help Python struct understand sizing. 52class CVariable: 53 """Represents a C variable that Python can also understand.""" 54 55 def __init__(self, name, *args, enum={}): 56 """ 57 Supply the name of the variable to construct. 58 Supply multiple optional numbers to represent an array or nested array. 59 """ 60 # Save passed in args 61 self.name = name 62 self.array_length = args 63 # track the parent CVariable that this CVariable belongs to 64 self.container_cvar = None 65 # This determines if the CVariable was nested (e.g. StructVar) 66 self.is_nested = False 67 # Mark no value set yet; variables can store values and interpret them. 68 self.value = None 69 # Store the enum of values this can take on for lookup purposes. 70 self.enum = enum 71 # Assume no reverse enum entries at first 72 self.reverse_enum = {} 73 # Populate reverse enum entries if any exist 74 for key, value in enum.items(): 75 self.reverse_enum[value] = key 76 77 def set_value(self, value): 78 self.value = value 79 80 def _C_name(self): 81 return f"{self.c_type} {self.name}" 82 83 def set_container(self, container): 84 self.container_cvar = container 85 86 def __repr__(self): 87 """Basic string representing this object.""" 88 string = "" 89 if self.container_cvar is not None: 90 string += str(self.container_cvar) 91 string += self.name 92 return string 93 94 def __str__(self): 95 """Make this object look nice when printing.""" 96 string = repr(self) 97 if self.value is not None: 98 if len(self.reverse_enum) > 0: 99 string += f" = {self.reverse_enum[self.value]}" 100 else: 101 string += f" = {self.value}" 102 return string 103 104 def to_C(self): 105 """Returns a C-compatible version of this variable.""" 106 code = self._C_name() 107 for array in self.array_length: 108 code += f"[{array}]" 109 return code 110 111 def to_Python(self): 112 """Returns a Python struct version of this variable.""" 113 if self.is_nested: 114 # the struct_string will already be fully formed if the CVar 115 # is nested from another object 116 return self.struct_string 117 repeats = 1 118 for array in self.array_length: 119 # check to see if the value is a string 120 if array == str(array): 121 # look up the number by the string name 122 array = C_CONSTANTS[array] 123 # multiply the total number of nested values together to represent 124 # the full length of the primitive type. 125 repeats *= array 126 return f"{repeats}{self.struct_string}" 127 128 def _array_depth_first_search(self, array_length, prefix=[]): 129 """ 130 Helper recursive function to parse through a nested array of CVars 131 until leaf CVars are found. Tracks variable names as it recurses 132 through and converts to C-friendly text reference. 133 """ 134 current_set = [] 135 array = array_length[0] 136 # check to see if the value is a string 137 if array == str(array): 138 # look up the number by the string name 139 array = C_CONSTANTS[array] 140 for i in range(0, array): 141 if len(array_length) > 1: 142 # nested array; recurse after removing one nested level 143 current_set += self._array_depth_first_search(array_length[1:], prefix + [i]) 144 else: 145 lookup = prefix + [i] 146 # leaf node; add it to the list! 147 array_self = ArrayVar(self, lookup) 148 current_set.append(array_self) 149 return current_set 150 151 def to_variables(self): 152 """ 153 Converts this structure into an ordered list of variables that 154 matches the order the data will be presented by serial; 155 generates multiple results for the array structures. 156 """ 157 current_set = [] 158 # if self.is_nested: 159 # return self.struct.to_variables() 160 if self.array_length: 161 # concatenate nested list 162 current_set += self._array_depth_first_search(self.array_length) 163 else: 164 # leaf node. add it to the list! 165 current_set.append(self) 166 return current_set 167 168 169class Uint8(CVariable): 170 c_type = "uint8_t" 171 struct_string = "B" 172 173 174class Uint16(CVariable): 175 c_type = "uint16_t" 176 struct_string = "H" 177 178 179class Uint32(CVariable): 180 c_type = "uint32_t" 181 struct_string = "I" 182 183 184class Int32(CVariable): 185 c_type = "int32_t" 186 struct_string = "i" 187 188 189class Uint64(CVariable): 190 c_type = "uint64_t" 191 struct_string = "Q" 192 193 194class Timestamp(Uint64): 195 """ 196 64-bit milliseconds-since-epoch. 197 It's a uint64, but make it print dates nicely. 198 """ 199 200 def __str__(self): 201 """Make this object look nice when printing.""" 202 string = super(Uint64, self).__str__() 203 if self.value is not None: 204 # convert value from epoch to string 205 seconds = int(self.value / 1000) 206 mseconds = self.value - (seconds * 1000) 207 string += " (" + str(datetime.datetime.utcfromtimestamp(seconds)) + "." + str(mseconds) + ")" 208 return string 209 210 211class StructVar(CVariable): 212 """ 213 Represent a Structure as if it is a CVariable. Useful when a struct contains 214 a struct. 215 """ 216 217 def __init__(self, struct, *args, **kwargs): 218 """ 219 Pass in the structure that will be a variable. 220 Pass in all the other arguments expected of a CVariable after that. 221 """ 222 # Call the original CVariable 223 super().__init__(*args, **kwargs) 224 # Parent struct 225 self.struct = struct 226 # The type is the struct's name 227 self.c_type = f"struct {struct.name}" 228 # The Python struct string is that of the structure 229 self.struct_string = struct.to_Python(root=False) 230 # Make sure Python struct is formatted correctly 231 self.is_nested = True 232 233 def __str__(self): 234 """Clean up structure naming""" 235 string = "" 236 if self.container_cvar is not None: 237 string += str(self.container_cvar) 238 string += self.name 239 string += "." 240 return string 241 242 def to_variables(self): 243 """ 244 Converts this structure into an ordered list of variables that 245 matches the order the data will be presented by serial. 246 """ 247 current_set = [] 248 for cvar in self.struct.cvars: 249 cvar.set_container(self) 250 current_set += cvar.to_variables() 251 return current_set 252 253 # TODO values don't get saved for ArrayVar cuz they are generated by 254 # to_variables then thrown away. 255 def set_values(self, values): 256 """ 257 Pass in values for each variable all at once. 258 Returns a list of variables with their value assignments. 259 """ 260 # generate the full set of vars 261 all_vars = self.to_variables() 262 n_vars = len(all_vars) 263 if len(all_vars) != len(values): 264 raise f"Expected {n_vars} values but got {len(values)}" 265 for i in range(0, n_vars): 266 all_vars[i].set_value(values[i]) 267 return all_vars 268 269 270class ArrayVar(CVariable): 271 """ 272 Represent a particular index of some CVariable that is an array or a 273 nested array of the CVariable type in question. 274 """ 275 276 def __init__(self, parent, index_path): 277 """ 278 Pass in the parent CVariable that this is an element of. 279 Pass in the index path (nested index) for this element within the parent. 280 """ 281 # Call the original CVariable 282 super().__init__(parent.name, parent.array_length, enum=parent.enum) 283 # The type matches the parent. 284 self.c_type = parent.c_type 285 # Save passed in args 286 self.parent = parent 287 self.index_path = index_path 288 289 def __repr__(self): 290 """Override string representation to include element position.""" 291 return str(self.parent) + f"{self.index_path}" 292 293 294class Struct: 295 """Represents a C struct that contains CVariables.""" 296 297 def __init__(self, name): 298 """Supply the name of the variable to construct.""" 299 self.name = name 300 self.cvars = [] 301 302 def __iter__(self): 303 """Iterate over CVariables within the struct.""" 304 return iter(self.cvars) 305 306 def __add__(self, cvar): 307 """ 308 Override the + operator so that this object can '+ Uint32()' for example. 309 returns itself, but tracks the C variable that was added in order. 310 """ 311 self.cvars.append(cvar) 312 return self 313 314 def __len__(self): 315 """ 316 Override the len() operator to return byte size of this structure. 317 """ 318 return struct.calcsize(self.to_Python()) 319 320 def __str__(self): 321 """Human readable form for this struct.""" 322 return f"struct {self.name}" 323 324 def __call__(self, name, *args, **kwargs): 325 """ 326 overload the () operator so that this struct can be used as a cvar in 327 other structs. 328 Pass in arguments as for a new CVariable. 329 """ 330 return StructVar(self, name, *args, **kwargs) 331 332 def to_C(self): 333 """Converts this struct into C code.""" 334 defines = [] 335 code = f"struct {self.name} {{\n" 336 # convert each contained CVar into C code. 337 for cvar in self.cvars: 338 if cvar.enum is not None: 339 # track enums defined in each CVar for later 340 defines.append(cvar.enum) 341 code += f" {cvar.to_C()};\n" 342 code += "};\n" 343 # convert each enum into #defines 344 for enum in defines: 345 for key, value in enum.items(): 346 code += f"#define {key} {value}\n" 347 return code 348 349 def to_Python(self, root=True): 350 """Converts this struct into Python struct string.""" 351 code = "" 352 if root: 353 # Only add the endian indicator on the outer-most call 354 code += "<" 355 for cvar in self.cvars: 356 code += cvar.to_Python() 357 return code 358 359 360# {FLAG_PROCESSOR} 361class GenerateFile: 362 """Code to dynamically generate files for C""" 363 364 front_matter = """ 365/* 366 * THIS FILE WAS GENERATED BY A SCRIPT. 367 * EDIT FILES IN project_root/processing INSTEAD OF THIS FILE. 368 */ 369""" 370 371 def __init__(self, filename): 372 """ 373 Provide a filename without path or extension; subclasses will handle 374 those parts. 375 """ 376 self.filename = filename 377 378 def to_C(self, rep_dict): 379 """ 380 Pass in replacement dictionary with keys being search text and value 381 being replace text. 382 Returns the full file content. 383 """ 384 with open(f"{self.path}{self.filename}{self.ext}", "w") as out_data: 385 with open(f"{self.filename}/{self.filename}{self.ext}", "r") as in_data: 386 code = self.front_matter 387 code += in_data.read() 388 for key, value in rep_dict.items(): 389 code_chunk = "" 390 if type(value) is not list: 391 # pack the value into a list so the for loop works 392 value = [value] 393 for val in value: 394 if hasattr(val, "to_C"): 395 # the value can be converted to C code, so do that 396 code_chunk += val.to_C() 397 else: 398 # the value is already C code 399 code_chunk += str(val) 400 code = code.replace(key, str(code_chunk)) 401 out_data.write(code) 402 print(f"{self.filename}{self.ext} generated") 403 404 def to_Code(self, table): 405 with open(f"{self.path}{self.filename}{self.ext}", "w") as out_data: 406 with open(f"{self.filename}/{self.filename}{self.ext}", "r") as in_data: 407 code = self.front_matter 408 code += in_data.read() 409 code_chunk = "" 410 for x in table: 411 if self.ext == ".h": 412 code_chunk += x.to_h() 413 else: 414 code_chunk += x.to_c() 415 416 if self.ext == ".c": 417 code_chunk += f'const char Chemistry[] = "{x.chemistry}"' + " ; \n" 418 if self.ext == ".h": 419 code_chunk += f"extern const char Chemistry[];" 420 code = code.replace("//{DYNAMIC_CONTENT}//", str(code_chunk)) 421 out_data.write(code) 422 print(f"{self.filename}{self.ext} generated") 423 424 def to_Mock(self): 425 with open(f"{self.main_path}{self.filename}{self.ext}", "r") as source_data: 426 with open(f"{self.filename}_mocks/{self.filename}{self.ext}", "r") as place_holder_data: 427 code = place_holder_data.read() 428 code_chunk = source_data.read() 429 code = code.replace("//{DYNAMIC_CONTENT}//", code_chunk) 430 with open(f"{self.mock_path}{self.filename}{self.ext}", "w") as mock_data: 431 mock_data.write(code) 432 print(f"{self.filename}{self.ext} generated") 433 434 435class HeaderFile(GenerateFile): 436 """Code to dynamically generate a HeaderFile""" 437 438 ext = ".h" 439 path = "../" + build_folder + "/Core/Inc/" 440 main_path = "../Core/Inc/" 441 mock_path = "../test/mocks/" 442 443 444class CFile(GenerateFile): 445 """Code to dynamically generate a CFile""" 446 447 ext = ".c" 448 path = "../" + build_folder + "/Core/Src/" 449 main_path = "../Core/Src/" 450 mock_path = "../test/mocks/" 451 452 453class FlagProcessor: 454 """ 455 Manage and build flag content. 456 """ 457 458 # redundant h code to be generated 459 h_template = """ 460// {comment} 461uint8_t flags_runtime_read_{section}_{name}(); 462void flags_runtime_set_{section}_{name}(); 463void flags_runtime_reset_{section}_{name}(); 464""" 465 466 # redundant c code 467 c_template = """ 468// {comment} is table {table}, bit {bit} 469uint8_t flags_runtime_read_{section}_{name}() {{ 470 return flags_read(runtime_get_flags(), {table}, {bit}); 471}} 472void flags_runtime_set_{section}_{name}() {{ 473 flags_set(runtime_get_flags(), {table}, {bit}); 474}} 475void flags_runtime_reset_{section}_{name}() {{ 476 flags_reset(runtime_get_flags(), {table}, {bit}); 477}} 478""" 479 480 # redundant flag section mask code 481 mask_h_template = """ 482extern const uint64_t flags_runtime_{section}_any_mask; 483uint8_t flags_runtime_{section}_any_flag(); 484""" 485 mask_c_template = """ 486const uint64_t flags_runtime_{section}_any_mask = {mask}; 487 488// returns 0 if no {section} flags, 1 if any {section} flags are present 489uint8_t flags_runtime_{section}_any_flag() {{ 490 struct flags_collection *flags_runtime = runtime_get_flags(); 491 return (flags_runtime->flags[{table}] & flags_runtime_{section}_any_mask) > 0; 492}} 493 494""" 495 496 # TODO calculate flag collection size dynamically 497 def __init__(self, flag_collection_size, flag_definitions): 498 """ 499 Supply flag collection size; this is how many 64-bit pages are used. 500 Supply flag definitions. It should be a dictionary with named sections. 501 Each section's value should be a list of dictionaries. Each dictionary 502 inside should define the fields "name" and "comment". 503 """ 504 self.flag_collection_size = flag_collection_size 505 self.sections = flag_definitions 506 self.section_masks = {} 507 self.name2bit = {} 508 self.bit2name = {} 509 self.bit2comment = {} 510 511 # preprocess bit data 512 self.generate_table_bits() 513 514 def get_name(self, section, item): 515 """Convert the given item and section into a canonical name.""" 516 return f"{section}_{item}" 517 518 def generate_table_bits(self): 519 """Enumerates all flags in all sections with table and bit values.""" 520 table_counter = 0 521 bit_counter = 0 522 section_mask_previous = 0 523 # Iterate over all items in sections 524 for section in self.sections: 525 section_name = section["name"] 526 for item in section["fields"]: 527 # assign table and bit 528 item["section"] = section_name 529 item["table"] = table_counter 530 item["bit"] = bit_counter 531 # establish simple lookups 532 c_name = self.get_name(section_name, item["name"]) 533 self.name2bit[c_name] = (table_counter, bit_counter) 534 self.bit2name[(table_counter, bit_counter)] = c_name 535 self.bit2comment[(table_counter, bit_counter)] = item["comment"] 536 # increment bit 537 bit_counter += 1 538 # TODO sections should NOT cross table boundaries so that flag 539 # masks work correctly. This needs to be more intelligent. 540 # increment table if necessary 541 if bit_counter == 64: 542 bit_counter = 0 543 table_counter += 1 544 # generate a bit mask to cover all bits from this section but excluding 545 # other sections 546 section_mask = 2**bit_counter - 1 - section_mask_previous 547 self.section_masks[section_name] = section_mask 548 # include this new section in the mask of previous sections to be 549 # excluded later 550 section_mask_previous |= section_mask 551 552 def write_header(self): 553 """Generate a header file.""" 554 DYNAMIC_CONTENT = "" 555 for section in self.sections: 556 section_name = section["name"] 557 DYNAMIC_CONTENT += f"\n//// {section_name}s\n" 558 for item in section["fields"]: 559 # item["section"] = section_name 560 DYNAMIC_CONTENT += self.h_template.format(**item) 561 DYNAMIC_CONTENT += self.mask_h_template.format(section=section_name) 562 563 HeaderFile("task_flags").to_C( 564 { 565 "{FLAG_COLLECTION_SIZE}": str(self.flag_collection_size), 566 "//{DYNAMIC_CONTENT}//": DYNAMIC_CONTENT, 567 } 568 ) 569 570 def write_c(self): 571 """Generate a C file.""" 572 DYNAMIC_CONTENT = "" 573 for section in self.sections: 574 section_name = section["name"] 575 DYNAMIC_CONTENT += f"//// {section_name}s\n" 576 for item in section["fields"]: 577 DYNAMIC_CONTENT += self.c_template.format(**item) 578 # fetch section mask 579 section_mask = self.section_masks[section_name] 580 # fetch the table for the last item processed. all items should be on 581 # the same table. 582 table_counter = self.name2bit[self.get_name(section_name, item["name"])][0] 583 DYNAMIC_CONTENT += self.mask_c_template.format(section=section_name, mask=section_mask, table=table_counter) 584 585 CFile("task_flags").to_C( 586 { 587 "//{DYNAMIC_CONTENT}//": DYNAMIC_CONTENT, 588 } 589 ) 590 591 def interpret_flags(self, table, value): 592 """Given a table and its value, interpret the flags.""" 593 flags = [] 594 for i in range(0, 64): 595 # iterate over 64 bits of the table 596 if value & 2**i != 0: 597 flags.append(self.bit2name[(table, i)] + " // " + self.bit2comment[(table, i)]) 598 return flags 599 600 def bit_2_name(self, table, i): 601 return self.bit2name[(table, i)] 602 603 604# {INITIAL_VARS} 605VERSION = 7 606 607# How many calculation records to keep as history 608CALCULATION_DATA_HISTORY = 3 609 610# This is used to determine how many "pages" of 64-bit integers will be used 611# to store flags. 612 613FLAG_COLLECTION_SIZE = 1 614# {STRUCTS} 615# Determine how many user defined runtime variables to track 616RUNTIME_UINT = { 617 8: 8, 618 16: 4, 619 32: 2, 620 64: 1, 621} 622 623# Build the flags_collection struct 624flags_collection = Struct("flags_collection") + Uint64("flags", FLAG_COLLECTION_SIZE) 625 626# Build the measurements_raw struct 627measurements_raw = ( 628 Struct("measurements_raw") 629 + Timestamp("timestamp") 630 + Uint32("current", "n_currents") 631 + Uint32("voltage_cell", "n_cells_series", 1) 632 + Uint32("voltage_terminal", "n_terminals") 633 + Uint32("temperature", "n_temps") 634 + Uint16("micro_temperature_raw") 635 + Uint8("ncharge_en_gpio") 636 + Uint8("n_wakeup_gpio") 637) 638 639# Build the measurements_cooked struct 640# TODO meas_timestamp is maybe not necessary since raw and cooked go together 641# linked inside of runtime_data now. no longer need time to correlate 642# calculations with their raw data? 643measurements_cooked = ( 644 Struct("measurements_cooked") 645 + Timestamp("meas_timestamp") 646 + Timestamp("timestamp") 647 + Int32("mamps") 648 + Uint32("mvolt_battery", "n_batteries") 649 + Uint32("mvolt_cell", "n_cells_series") 650 + Uint32("mvolt_terminal", "n_terminals") 651 + Uint32("dk_temp", "n_temps") 652 + Uint16("micro_temperature_dc") 653 + Uint8("ncharge_en_gpio") 654 + Uint8("n_wakeup_gpio") 655) # + Uint32('time_delta') \ # TODO add this for logging? \ 656if board_arguement == "AVEX_v1_BOARD" or board_arguement == "AVEX_v2_BOARD": 657 measurements_cooked += Int32("charger_mamps") 658 659state_of_charge = ( 660 Struct("state_of_charge") 661 + Uint8( 662 "cell_state", 663 "n_cells_series", 664 enum={ 665 "SOC_CELL_STATE_UNKNOWN": 0, 666 "SOC_CELL_STATE_RESTING": 1, 667 "SOC_CELL_STATE_CHARGING_EXCITED": 2, 668 "SOC_CELL_STATE_DISCHARGING_EXCITED": 3, 669 "SOC_CELL_STATE_CHARGING_TIMER": 4, 670 "SOC_CELL_STATE_DISCHARGING_TIMER": 5, 671 "CALCULATION_DATA_HISTORY": CALCULATION_DATA_HISTORY, 672 }, 673 ) 674 + Uint8("percent_charged") 675 + Uint8("percent_health") 676 + Uint16("milliamp_hour_used") 677 + Uint16("milliamp_hour_remaining") 678 + Uint16("charge_cycles") 679 + Uint16("milliamp_hour_capacity") 680 + Uint32("Q_Max") 681 + Uint32("git_hash") 682 + Uint64("Columb_Count") 683) 684 685# Build the runtime_data_format struct 686runtime_data_format = ( 687 Struct("runtime_data_format") 688 + Uint8("version") 689 + Uint32("build_date") 690 + measurements_raw("measures") 691 + measurements_cooked("calcs") 692 + flags_collection("flags") 693 + state_of_charge("soc") 694 + Uint8("BMS_State") 695 + Uint16("Wakeup_Counter") 696 + Uint32("Reset_Flags") 697 + Uint8("userdef_8", RUNTIME_UINT[8]) 698 + Uint16("userdef_16", RUNTIME_UINT[16]) 699 + Uint32("userdef_32", RUNTIME_UINT[32]) 700 + Uint64("userdef_64", RUNTIME_UINT[64]) 701) 702 703calculation_history = Struct("calculation_history_format") + measurements_cooked("calcs", CALCULATION_DATA_HISTORY) 704 705FLAGS_SECTION = [ 706 { 707 "name": "interrupt", 708 "fields": [ 709 {"comment": "RTC Wakeup Interrupt", "name": "rtc_wakeup"}, 710 {"comment": "SMBUS Interrupt", "name": "smbus_wakeup"}, 711 {"comment": "n_WAKEUP Interrupt", "name": "n_wakeup"}, 712 {"comment": "OC_ONESHOT Interrupt", "name": "oc_oneshot"}, 713 {"comment": "nCHARGE_EN Interrupt", "name": "ncharge_en"}, 714 ], 715 }, 716 { 717 "name": "fault", 718 "fields": [ 719 {"comment": "Too hot to charge", "name": "overtemp_charge"}, 720 {"comment": "Too hot to discharge", "name": "overtemp_discharge"}, 721 {"comment": "Too cold to charge", "name": "undertemp_charge"}, 722 {"comment": "Too cold to discharge", "name": "undertemp_discharge"}, 723 {"comment": "Any cell voltage too high to charge", "name": "overvoltage_charge"}, 724 {"comment": "Any cell voltage too high to discharge", "name": "overvoltage_discharge"}, 725 {"comment": "Any cell voltage too low to charge", "name": "undervoltage_charge"}, 726 {"comment": "Terminal voltage too high", "name": "terminalvoltage_charge"}, 727 {"comment": "Max charging current exceeded", "name": "overcurrent_charge"}, 728 {"comment": "Battery charging when nCHARGE_EN == HIGH", "name": "nce_charge"}, 729 {"comment": "Over current interrupt event", "name": "overcurrent_discharge"}, 730 {"comment": "Sw overcurrent protection low current longer time", "name": "sw_overcurrent_discharge_1"}, 731 {"comment": "Sw overcurrent protection high current short time", "name": "sw_overcurrent_discharge_2"}, 732 {"comment": "Charging is too high for the temperature", "name": "undertemp_charge_rate"}, 733 {"comment": "We can not sit on the charger for too long if so we will fault", "name": "overtime_charge"}, 734 ], 735 }, 736 { 737 "name": "faultslumber", 738 "fields": [{"comment": "Any cell voltage too low to discharge", "name": "undervoltage_discharge"}], 739 }, 740 { 741 "name": "permanentdisable", 742 "fields": [ 743 {"comment": "OverTemp - Cell Temperature too High", "name": "overtemp"}, 744 {"comment": "OverVoltage - Cell Voltage too High", "name": "overvoltage"}, 745 {"comment": "UnderVoltage - Cell Voltage too Low", "name": "undervoltage"}, 746 {"comment": "CellImbalance - Cell Imbalance too Large", "name": "cellimbalance"}, 747 ], 748 }, 749 { 750 "name": "prefault", 751 "fields": [ 752 {"comment": "Too hot to charge", "name": "overtemp_charge"}, 753 {"comment": "Too hot to discharge", "name": "overtemp_discharge"}, 754 {"comment": "Too cold to charge", "name": "undertemp_charge"}, 755 {"comment": "Too cold to discharge", "name": "undertemp_discharge"}, 756 {"comment": "Any cell voltage too high to charge", "name": "overvoltage_charge"}, 757 {"comment": "Any cell voltage too high to discharge", "name": "overvoltage_discharge"}, 758 {"comment": "Any cell voltage too low to charge", "name": "undervoltage_charge"}, 759 {"comment": "Terminal voltage too high", "name": "terminalvoltage_charge"}, 760 {"comment": "Max charging current exceeded", "name": "overcurrent_charge"}, 761 {"comment": "Battery charging when nCHARGE_EN == HIGH", "name": "nce_charge"}, 762 {"comment": "Over current interrupt event", "name": "overcurrent_discharge"}, 763 {"comment": "Sw overcurrent protection low current longer time", "name": "sw_overcurrent_discharge_1"}, 764 {"comment": "Sw overcurrent protection high current short time", "name": "sw_overcurrent_discharge_2"}, 765 {"comment": "Charging is too high for the temperature", "name": "undertemp_charge_rate"}, 766 {"comment": "We can not sit on the charger for too long if so we will fault", "name": "overtime_charge"}, 767 {"comment": "Any cell voltage too low to discharge", "name": "undervoltage_discharge"}, 768 {"comment": "OverTemp - Cell Temperature too High", "name": "overtemp"}, 769 {"comment": "OverVoltage - Cell Voltage too High", "name": "overvoltage"}, 770 {"comment": "UnderVoltage - Cell Voltage too Low", "name": "undervoltage"}, 771 {"comment": "CellImbalance - Cell Imbalance too Large", "name": "cellimbalance"}, 772 ], 773 }, 774 { 775 "name": "measure", 776 "fields": [ 777 {"comment": "Output fets are disabled so current cannot be measured", "name": "output_fets_disabled"} 778 ], 779 }, 780 { 781 "name": "output", 782 "fields": [ 783 {"comment": "Tracks whether C1_BAL is high or low", "name": "c1_bal"}, 784 {"comment": "Tracks whether C2_BAL is high or low", "name": "c2_bal"}, 785 {"comment": "Tracks whether C3_BAL is high or low", "name": "c3_bal"}, 786 {"comment": "Tracks whether C4_BAL is high or low", "name": "c4_bal"}, 787 {"comment": "If enabled writes runtime data to serial every cycle", "name": "constant_serial_runtime_data"}, 788 { 789 "comment": "Excited lets us know we are (dis)charging past a certain current threshold", 790 "name": "excited", 791 }, 792 ], 793 }, 794 { 795 "name": "state", 796 "fields": [ 797 {"comment": "Current state is calibration", "name": "calibration"}, 798 {"comment": "Current state is Deep Slumber", "name": "deepslumber"}, 799 {"comment": "Current state is Fast Sample", "name": "fastsample"}, 800 {"comment": "Current state is Fault", "name": "fault"}, 801 {"comment": "Current state is Fault Permanently Disabled", "name": "faultpermanentdisable"}, 802 {"comment": "Current state is Fault Slumber", "name": "faultslumber"}, 803 {"comment": "Current state is Prefault", "name": "prefault"}, 804 {"comment": "Current state is Slow Sample", "name": "slowsample"}, 805 {"comment": "State that doesnt sleep to take smbus packets", "name": "mcuboot"} 806 ], 807 }, 808 { 809 "name": "cell_state", 810 "fields": [ 811 {"comment": "Charging is active when current is above a certain threshold", "name": "charging"}, 812 {"comment": "Discharging is active when current is below a certain threshold", "name": "discharging"}, 813 {"comment": "Relaxed is active when the current is neither charging or discharging", "name": "relaxed"}, 814 ], 815 }, 816]
53class CVariable: 54 """Represents a C variable that Python can also understand.""" 55 56 def __init__(self, name, *args, enum={}): 57 """ 58 Supply the name of the variable to construct. 59 Supply multiple optional numbers to represent an array or nested array. 60 """ 61 # Save passed in args 62 self.name = name 63 self.array_length = args 64 # track the parent CVariable that this CVariable belongs to 65 self.container_cvar = None 66 # This determines if the CVariable was nested (e.g. StructVar) 67 self.is_nested = False 68 # Mark no value set yet; variables can store values and interpret them. 69 self.value = None 70 # Store the enum of values this can take on for lookup purposes. 71 self.enum = enum 72 # Assume no reverse enum entries at first 73 self.reverse_enum = {} 74 # Populate reverse enum entries if any exist 75 for key, value in enum.items(): 76 self.reverse_enum[value] = key 77 78 def set_value(self, value): 79 self.value = value 80 81 def _C_name(self): 82 return f"{self.c_type} {self.name}" 83 84 def set_container(self, container): 85 self.container_cvar = container 86 87 def __repr__(self): 88 """Basic string representing this object.""" 89 string = "" 90 if self.container_cvar is not None: 91 string += str(self.container_cvar) 92 string += self.name 93 return string 94 95 def __str__(self): 96 """Make this object look nice when printing.""" 97 string = repr(self) 98 if self.value is not None: 99 if len(self.reverse_enum) > 0: 100 string += f" = {self.reverse_enum[self.value]}" 101 else: 102 string += f" = {self.value}" 103 return string 104 105 def to_C(self): 106 """Returns a C-compatible version of this variable.""" 107 code = self._C_name() 108 for array in self.array_length: 109 code += f"[{array}]" 110 return code 111 112 def to_Python(self): 113 """Returns a Python struct version of this variable.""" 114 if self.is_nested: 115 # the struct_string will already be fully formed if the CVar 116 # is nested from another object 117 return self.struct_string 118 repeats = 1 119 for array in self.array_length: 120 # check to see if the value is a string 121 if array == str(array): 122 # look up the number by the string name 123 array = C_CONSTANTS[array] 124 # multiply the total number of nested values together to represent 125 # the full length of the primitive type. 126 repeats *= array 127 return f"{repeats}{self.struct_string}" 128 129 def _array_depth_first_search(self, array_length, prefix=[]): 130 """ 131 Helper recursive function to parse through a nested array of CVars 132 until leaf CVars are found. Tracks variable names as it recurses 133 through and converts to C-friendly text reference. 134 """ 135 current_set = [] 136 array = array_length[0] 137 # check to see if the value is a string 138 if array == str(array): 139 # look up the number by the string name 140 array = C_CONSTANTS[array] 141 for i in range(0, array): 142 if len(array_length) > 1: 143 # nested array; recurse after removing one nested level 144 current_set += self._array_depth_first_search(array_length[1:], prefix + [i]) 145 else: 146 lookup = prefix + [i] 147 # leaf node; add it to the list! 148 array_self = ArrayVar(self, lookup) 149 current_set.append(array_self) 150 return current_set 151 152 def to_variables(self): 153 """ 154 Converts this structure into an ordered list of variables that 155 matches the order the data will be presented by serial; 156 generates multiple results for the array structures. 157 """ 158 current_set = [] 159 # if self.is_nested: 160 # return self.struct.to_variables() 161 if self.array_length: 162 # concatenate nested list 163 current_set += self._array_depth_first_search(self.array_length) 164 else: 165 # leaf node. add it to the list! 166 current_set.append(self) 167 return current_set
Represents a C variable that Python can also understand.
56 def __init__(self, name, *args, enum={}): 57 """ 58 Supply the name of the variable to construct. 59 Supply multiple optional numbers to represent an array or nested array. 60 """ 61 # Save passed in args 62 self.name = name 63 self.array_length = args 64 # track the parent CVariable that this CVariable belongs to 65 self.container_cvar = None 66 # This determines if the CVariable was nested (e.g. StructVar) 67 self.is_nested = False 68 # Mark no value set yet; variables can store values and interpret them. 69 self.value = None 70 # Store the enum of values this can take on for lookup purposes. 71 self.enum = enum 72 # Assume no reverse enum entries at first 73 self.reverse_enum = {} 74 # Populate reverse enum entries if any exist 75 for key, value in enum.items(): 76 self.reverse_enum[value] = key
Supply the name of the variable to construct. Supply multiple optional numbers to represent an array or nested array.
105 def to_C(self): 106 """Returns a C-compatible version of this variable.""" 107 code = self._C_name() 108 for array in self.array_length: 109 code += f"[{array}]" 110 return code
Returns a C-compatible version of this variable.
112 def to_Python(self): 113 """Returns a Python struct version of this variable.""" 114 if self.is_nested: 115 # the struct_string will already be fully formed if the CVar 116 # is nested from another object 117 return self.struct_string 118 repeats = 1 119 for array in self.array_length: 120 # check to see if the value is a string 121 if array == str(array): 122 # look up the number by the string name 123 array = C_CONSTANTS[array] 124 # multiply the total number of nested values together to represent 125 # the full length of the primitive type. 126 repeats *= array 127 return f"{repeats}{self.struct_string}"
Returns a Python struct version of this variable.
152 def to_variables(self): 153 """ 154 Converts this structure into an ordered list of variables that 155 matches the order the data will be presented by serial; 156 generates multiple results for the array structures. 157 """ 158 current_set = [] 159 # if self.is_nested: 160 # return self.struct.to_variables() 161 if self.array_length: 162 # concatenate nested list 163 current_set += self._array_depth_first_search(self.array_length) 164 else: 165 # leaf node. add it to the list! 166 current_set.append(self) 167 return current_set
Converts this structure into an ordered list of variables that matches the order the data will be presented by serial; generates multiple results for the array structures.
Represents a C variable that Python can also understand.
Represents a C variable that Python can also understand.
Represents a C variable that Python can also understand.
Represents a C variable that Python can also understand.
Represents a C variable that Python can also understand.
195class Timestamp(Uint64): 196 """ 197 64-bit milliseconds-since-epoch. 198 It's a uint64, but make it print dates nicely. 199 """ 200 201 def __str__(self): 202 """Make this object look nice when printing.""" 203 string = super(Uint64, self).__str__() 204 if self.value is not None: 205 # convert value from epoch to string 206 seconds = int(self.value / 1000) 207 mseconds = self.value - (seconds * 1000) 208 string += " (" + str(datetime.datetime.utcfromtimestamp(seconds)) + "." + str(mseconds) + ")" 209 return string
64-bit milliseconds-since-epoch. It's a uint64, but make it print dates nicely.
212class StructVar(CVariable): 213 """ 214 Represent a Structure as if it is a CVariable. Useful when a struct contains 215 a struct. 216 """ 217 218 def __init__(self, struct, *args, **kwargs): 219 """ 220 Pass in the structure that will be a variable. 221 Pass in all the other arguments expected of a CVariable after that. 222 """ 223 # Call the original CVariable 224 super().__init__(*args, **kwargs) 225 # Parent struct 226 self.struct = struct 227 # The type is the struct's name 228 self.c_type = f"struct {struct.name}" 229 # The Python struct string is that of the structure 230 self.struct_string = struct.to_Python(root=False) 231 # Make sure Python struct is formatted correctly 232 self.is_nested = True 233 234 def __str__(self): 235 """Clean up structure naming""" 236 string = "" 237 if self.container_cvar is not None: 238 string += str(self.container_cvar) 239 string += self.name 240 string += "." 241 return string 242 243 def to_variables(self): 244 """ 245 Converts this structure into an ordered list of variables that 246 matches the order the data will be presented by serial. 247 """ 248 current_set = [] 249 for cvar in self.struct.cvars: 250 cvar.set_container(self) 251 current_set += cvar.to_variables() 252 return current_set 253 254 # TODO values don't get saved for ArrayVar cuz they are generated by 255 # to_variables then thrown away. 256 def set_values(self, values): 257 """ 258 Pass in values for each variable all at once. 259 Returns a list of variables with their value assignments. 260 """ 261 # generate the full set of vars 262 all_vars = self.to_variables() 263 n_vars = len(all_vars) 264 if len(all_vars) != len(values): 265 raise f"Expected {n_vars} values but got {len(values)}" 266 for i in range(0, n_vars): 267 all_vars[i].set_value(values[i]) 268 return all_vars
Represent a Structure as if it is a CVariable. Useful when a struct contains a struct.
218 def __init__(self, struct, *args, **kwargs): 219 """ 220 Pass in the structure that will be a variable. 221 Pass in all the other arguments expected of a CVariable after that. 222 """ 223 # Call the original CVariable 224 super().__init__(*args, **kwargs) 225 # Parent struct 226 self.struct = struct 227 # The type is the struct's name 228 self.c_type = f"struct {struct.name}" 229 # The Python struct string is that of the structure 230 self.struct_string = struct.to_Python(root=False) 231 # Make sure Python struct is formatted correctly 232 self.is_nested = True
Pass in the structure that will be a variable. Pass in all the other arguments expected of a CVariable after that.
243 def to_variables(self): 244 """ 245 Converts this structure into an ordered list of variables that 246 matches the order the data will be presented by serial. 247 """ 248 current_set = [] 249 for cvar in self.struct.cvars: 250 cvar.set_container(self) 251 current_set += cvar.to_variables() 252 return current_set
Converts this structure into an ordered list of variables that matches the order the data will be presented by serial.
256 def set_values(self, values): 257 """ 258 Pass in values for each variable all at once. 259 Returns a list of variables with their value assignments. 260 """ 261 # generate the full set of vars 262 all_vars = self.to_variables() 263 n_vars = len(all_vars) 264 if len(all_vars) != len(values): 265 raise f"Expected {n_vars} values but got {len(values)}" 266 for i in range(0, n_vars): 267 all_vars[i].set_value(values[i]) 268 return all_vars
Pass in values for each variable all at once. Returns a list of variables with their value assignments.
Inherited Members
271class ArrayVar(CVariable): 272 """ 273 Represent a particular index of some CVariable that is an array or a 274 nested array of the CVariable type in question. 275 """ 276 277 def __init__(self, parent, index_path): 278 """ 279 Pass in the parent CVariable that this is an element of. 280 Pass in the index path (nested index) for this element within the parent. 281 """ 282 # Call the original CVariable 283 super().__init__(parent.name, parent.array_length, enum=parent.enum) 284 # The type matches the parent. 285 self.c_type = parent.c_type 286 # Save passed in args 287 self.parent = parent 288 self.index_path = index_path 289 290 def __repr__(self): 291 """Override string representation to include element position.""" 292 return str(self.parent) + f"{self.index_path}"
Represent a particular index of some CVariable that is an array or a nested array of the CVariable type in question.
277 def __init__(self, parent, index_path): 278 """ 279 Pass in the parent CVariable that this is an element of. 280 Pass in the index path (nested index) for this element within the parent. 281 """ 282 # Call the original CVariable 283 super().__init__(parent.name, parent.array_length, enum=parent.enum) 284 # The type matches the parent. 285 self.c_type = parent.c_type 286 # Save passed in args 287 self.parent = parent 288 self.index_path = index_path
Pass in the parent CVariable that this is an element of. Pass in the index path (nested index) for this element within the parent.
Inherited Members
295class Struct: 296 """Represents a C struct that contains CVariables.""" 297 298 def __init__(self, name): 299 """Supply the name of the variable to construct.""" 300 self.name = name 301 self.cvars = [] 302 303 def __iter__(self): 304 """Iterate over CVariables within the struct.""" 305 return iter(self.cvars) 306 307 def __add__(self, cvar): 308 """ 309 Override the + operator so that this object can '+ Uint32()' for example. 310 returns itself, but tracks the C variable that was added in order. 311 """ 312 self.cvars.append(cvar) 313 return self 314 315 def __len__(self): 316 """ 317 Override the len() operator to return byte size of this structure. 318 """ 319 return struct.calcsize(self.to_Python()) 320 321 def __str__(self): 322 """Human readable form for this struct.""" 323 return f"struct {self.name}" 324 325 def __call__(self, name, *args, **kwargs): 326 """ 327 overload the () operator so that this struct can be used as a cvar in 328 other structs. 329 Pass in arguments as for a new CVariable. 330 """ 331 return StructVar(self, name, *args, **kwargs) 332 333 def to_C(self): 334 """Converts this struct into C code.""" 335 defines = [] 336 code = f"struct {self.name} {{\n" 337 # convert each contained CVar into C code. 338 for cvar in self.cvars: 339 if cvar.enum is not None: 340 # track enums defined in each CVar for later 341 defines.append(cvar.enum) 342 code += f" {cvar.to_C()};\n" 343 code += "};\n" 344 # convert each enum into #defines 345 for enum in defines: 346 for key, value in enum.items(): 347 code += f"#define {key} {value}\n" 348 return code 349 350 def to_Python(self, root=True): 351 """Converts this struct into Python struct string.""" 352 code = "" 353 if root: 354 # Only add the endian indicator on the outer-most call 355 code += "<" 356 for cvar in self.cvars: 357 code += cvar.to_Python() 358 return code
Represents a C struct that contains CVariables.
298 def __init__(self, name): 299 """Supply the name of the variable to construct.""" 300 self.name = name 301 self.cvars = []
Supply the name of the variable to construct.
333 def to_C(self): 334 """Converts this struct into C code.""" 335 defines = [] 336 code = f"struct {self.name} {{\n" 337 # convert each contained CVar into C code. 338 for cvar in self.cvars: 339 if cvar.enum is not None: 340 # track enums defined in each CVar for later 341 defines.append(cvar.enum) 342 code += f" {cvar.to_C()};\n" 343 code += "};\n" 344 # convert each enum into #defines 345 for enum in defines: 346 for key, value in enum.items(): 347 code += f"#define {key} {value}\n" 348 return code
Converts this struct into C code.
350 def to_Python(self, root=True): 351 """Converts this struct into Python struct string.""" 352 code = "" 353 if root: 354 # Only add the endian indicator on the outer-most call 355 code += "<" 356 for cvar in self.cvars: 357 code += cvar.to_Python() 358 return code
Converts this struct into Python struct string.
362class GenerateFile: 363 """Code to dynamically generate files for C""" 364 365 front_matter = """ 366/* 367 * THIS FILE WAS GENERATED BY A SCRIPT. 368 * EDIT FILES IN project_root/processing INSTEAD OF THIS FILE. 369 */ 370""" 371 372 def __init__(self, filename): 373 """ 374 Provide a filename without path or extension; subclasses will handle 375 those parts. 376 """ 377 self.filename = filename 378 379 def to_C(self, rep_dict): 380 """ 381 Pass in replacement dictionary with keys being search text and value 382 being replace text. 383 Returns the full file content. 384 """ 385 with open(f"{self.path}{self.filename}{self.ext}", "w") as out_data: 386 with open(f"{self.filename}/{self.filename}{self.ext}", "r") as in_data: 387 code = self.front_matter 388 code += in_data.read() 389 for key, value in rep_dict.items(): 390 code_chunk = "" 391 if type(value) is not list: 392 # pack the value into a list so the for loop works 393 value = [value] 394 for val in value: 395 if hasattr(val, "to_C"): 396 # the value can be converted to C code, so do that 397 code_chunk += val.to_C() 398 else: 399 # the value is already C code 400 code_chunk += str(val) 401 code = code.replace(key, str(code_chunk)) 402 out_data.write(code) 403 print(f"{self.filename}{self.ext} generated") 404 405 def to_Code(self, table): 406 with open(f"{self.path}{self.filename}{self.ext}", "w") as out_data: 407 with open(f"{self.filename}/{self.filename}{self.ext}", "r") as in_data: 408 code = self.front_matter 409 code += in_data.read() 410 code_chunk = "" 411 for x in table: 412 if self.ext == ".h": 413 code_chunk += x.to_h() 414 else: 415 code_chunk += x.to_c() 416 417 if self.ext == ".c": 418 code_chunk += f'const char Chemistry[] = "{x.chemistry}"' + " ; \n" 419 if self.ext == ".h": 420 code_chunk += f"extern const char Chemistry[];" 421 code = code.replace("//{DYNAMIC_CONTENT}//", str(code_chunk)) 422 out_data.write(code) 423 print(f"{self.filename}{self.ext} generated") 424 425 def to_Mock(self): 426 with open(f"{self.main_path}{self.filename}{self.ext}", "r") as source_data: 427 with open(f"{self.filename}_mocks/{self.filename}{self.ext}", "r") as place_holder_data: 428 code = place_holder_data.read() 429 code_chunk = source_data.read() 430 code = code.replace("//{DYNAMIC_CONTENT}//", code_chunk) 431 with open(f"{self.mock_path}{self.filename}{self.ext}", "w") as mock_data: 432 mock_data.write(code) 433 print(f"{self.filename}{self.ext} generated")
Code to dynamically generate files for C
372 def __init__(self, filename): 373 """ 374 Provide a filename without path or extension; subclasses will handle 375 those parts. 376 """ 377 self.filename = filename
Provide a filename without path or extension; subclasses will handle those parts.
379 def to_C(self, rep_dict): 380 """ 381 Pass in replacement dictionary with keys being search text and value 382 being replace text. 383 Returns the full file content. 384 """ 385 with open(f"{self.path}{self.filename}{self.ext}", "w") as out_data: 386 with open(f"{self.filename}/{self.filename}{self.ext}", "r") as in_data: 387 code = self.front_matter 388 code += in_data.read() 389 for key, value in rep_dict.items(): 390 code_chunk = "" 391 if type(value) is not list: 392 # pack the value into a list so the for loop works 393 value = [value] 394 for val in value: 395 if hasattr(val, "to_C"): 396 # the value can be converted to C code, so do that 397 code_chunk += val.to_C() 398 else: 399 # the value is already C code 400 code_chunk += str(val) 401 code = code.replace(key, str(code_chunk)) 402 out_data.write(code) 403 print(f"{self.filename}{self.ext} generated")
Pass in replacement dictionary with keys being search text and value being replace text. Returns the full file content.
405 def to_Code(self, table): 406 with open(f"{self.path}{self.filename}{self.ext}", "w") as out_data: 407 with open(f"{self.filename}/{self.filename}{self.ext}", "r") as in_data: 408 code = self.front_matter 409 code += in_data.read() 410 code_chunk = "" 411 for x in table: 412 if self.ext == ".h": 413 code_chunk += x.to_h() 414 else: 415 code_chunk += x.to_c() 416 417 if self.ext == ".c": 418 code_chunk += f'const char Chemistry[] = "{x.chemistry}"' + " ; \n" 419 if self.ext == ".h": 420 code_chunk += f"extern const char Chemistry[];" 421 code = code.replace("//{DYNAMIC_CONTENT}//", str(code_chunk)) 422 out_data.write(code) 423 print(f"{self.filename}{self.ext} generated")
425 def to_Mock(self): 426 with open(f"{self.main_path}{self.filename}{self.ext}", "r") as source_data: 427 with open(f"{self.filename}_mocks/{self.filename}{self.ext}", "r") as place_holder_data: 428 code = place_holder_data.read() 429 code_chunk = source_data.read() 430 code = code.replace("//{DYNAMIC_CONTENT}//", code_chunk) 431 with open(f"{self.mock_path}{self.filename}{self.ext}", "w") as mock_data: 432 mock_data.write(code) 433 print(f"{self.filename}{self.ext} generated")
436class HeaderFile(GenerateFile): 437 """Code to dynamically generate a HeaderFile""" 438 439 ext = ".h" 440 path = "../" + build_folder + "/Core/Inc/" 441 main_path = "../Core/Inc/" 442 mock_path = "../test/mocks/"
Code to dynamically generate a HeaderFile
Inherited Members
445class CFile(GenerateFile): 446 """Code to dynamically generate a CFile""" 447 448 ext = ".c" 449 path = "../" + build_folder + "/Core/Src/" 450 main_path = "../Core/Src/" 451 mock_path = "../test/mocks/"
Code to dynamically generate a CFile
Inherited Members
454class FlagProcessor: 455 """ 456 Manage and build flag content. 457 """ 458 459 # redundant h code to be generated 460 h_template = """ 461// {comment} 462uint8_t flags_runtime_read_{section}_{name}(); 463void flags_runtime_set_{section}_{name}(); 464void flags_runtime_reset_{section}_{name}(); 465""" 466 467 # redundant c code 468 c_template = """ 469// {comment} is table {table}, bit {bit} 470uint8_t flags_runtime_read_{section}_{name}() {{ 471 return flags_read(runtime_get_flags(), {table}, {bit}); 472}} 473void flags_runtime_set_{section}_{name}() {{ 474 flags_set(runtime_get_flags(), {table}, {bit}); 475}} 476void flags_runtime_reset_{section}_{name}() {{ 477 flags_reset(runtime_get_flags(), {table}, {bit}); 478}} 479""" 480 481 # redundant flag section mask code 482 mask_h_template = """ 483extern const uint64_t flags_runtime_{section}_any_mask; 484uint8_t flags_runtime_{section}_any_flag(); 485""" 486 mask_c_template = """ 487const uint64_t flags_runtime_{section}_any_mask = {mask}; 488 489// returns 0 if no {section} flags, 1 if any {section} flags are present 490uint8_t flags_runtime_{section}_any_flag() {{ 491 struct flags_collection *flags_runtime = runtime_get_flags(); 492 return (flags_runtime->flags[{table}] & flags_runtime_{section}_any_mask) > 0; 493}} 494 495""" 496 497 # TODO calculate flag collection size dynamically 498 def __init__(self, flag_collection_size, flag_definitions): 499 """ 500 Supply flag collection size; this is how many 64-bit pages are used. 501 Supply flag definitions. It should be a dictionary with named sections. 502 Each section's value should be a list of dictionaries. Each dictionary 503 inside should define the fields "name" and "comment". 504 """ 505 self.flag_collection_size = flag_collection_size 506 self.sections = flag_definitions 507 self.section_masks = {} 508 self.name2bit = {} 509 self.bit2name = {} 510 self.bit2comment = {} 511 512 # preprocess bit data 513 self.generate_table_bits() 514 515 def get_name(self, section, item): 516 """Convert the given item and section into a canonical name.""" 517 return f"{section}_{item}" 518 519 def generate_table_bits(self): 520 """Enumerates all flags in all sections with table and bit values.""" 521 table_counter = 0 522 bit_counter = 0 523 section_mask_previous = 0 524 # Iterate over all items in sections 525 for section in self.sections: 526 section_name = section["name"] 527 for item in section["fields"]: 528 # assign table and bit 529 item["section"] = section_name 530 item["table"] = table_counter 531 item["bit"] = bit_counter 532 # establish simple lookups 533 c_name = self.get_name(section_name, item["name"]) 534 self.name2bit[c_name] = (table_counter, bit_counter) 535 self.bit2name[(table_counter, bit_counter)] = c_name 536 self.bit2comment[(table_counter, bit_counter)] = item["comment"] 537 # increment bit 538 bit_counter += 1 539 # TODO sections should NOT cross table boundaries so that flag 540 # masks work correctly. This needs to be more intelligent. 541 # increment table if necessary 542 if bit_counter == 64: 543 bit_counter = 0 544 table_counter += 1 545 # generate a bit mask to cover all bits from this section but excluding 546 # other sections 547 section_mask = 2**bit_counter - 1 - section_mask_previous 548 self.section_masks[section_name] = section_mask 549 # include this new section in the mask of previous sections to be 550 # excluded later 551 section_mask_previous |= section_mask 552 553 def write_header(self): 554 """Generate a header file.""" 555 DYNAMIC_CONTENT = "" 556 for section in self.sections: 557 section_name = section["name"] 558 DYNAMIC_CONTENT += f"\n//// {section_name}s\n" 559 for item in section["fields"]: 560 # item["section"] = section_name 561 DYNAMIC_CONTENT += self.h_template.format(**item) 562 DYNAMIC_CONTENT += self.mask_h_template.format(section=section_name) 563 564 HeaderFile("task_flags").to_C( 565 { 566 "{FLAG_COLLECTION_SIZE}": str(self.flag_collection_size), 567 "//{DYNAMIC_CONTENT}//": DYNAMIC_CONTENT, 568 } 569 ) 570 571 def write_c(self): 572 """Generate a C file.""" 573 DYNAMIC_CONTENT = "" 574 for section in self.sections: 575 section_name = section["name"] 576 DYNAMIC_CONTENT += f"//// {section_name}s\n" 577 for item in section["fields"]: 578 DYNAMIC_CONTENT += self.c_template.format(**item) 579 # fetch section mask 580 section_mask = self.section_masks[section_name] 581 # fetch the table for the last item processed. all items should be on 582 # the same table. 583 table_counter = self.name2bit[self.get_name(section_name, item["name"])][0] 584 DYNAMIC_CONTENT += self.mask_c_template.format(section=section_name, mask=section_mask, table=table_counter) 585 586 CFile("task_flags").to_C( 587 { 588 "//{DYNAMIC_CONTENT}//": DYNAMIC_CONTENT, 589 } 590 ) 591 592 def interpret_flags(self, table, value): 593 """Given a table and its value, interpret the flags.""" 594 flags = [] 595 for i in range(0, 64): 596 # iterate over 64 bits of the table 597 if value & 2**i != 0: 598 flags.append(self.bit2name[(table, i)] + " // " + self.bit2comment[(table, i)]) 599 return flags 600 601 def bit_2_name(self, table, i): 602 return self.bit2name[(table, i)]
Manage and build flag content.
498 def __init__(self, flag_collection_size, flag_definitions): 499 """ 500 Supply flag collection size; this is how many 64-bit pages are used. 501 Supply flag definitions. It should be a dictionary with named sections. 502 Each section's value should be a list of dictionaries. Each dictionary 503 inside should define the fields "name" and "comment". 504 """ 505 self.flag_collection_size = flag_collection_size 506 self.sections = flag_definitions 507 self.section_masks = {} 508 self.name2bit = {} 509 self.bit2name = {} 510 self.bit2comment = {} 511 512 # preprocess bit data 513 self.generate_table_bits()
Supply flag collection size; this is how many 64-bit pages are used. Supply flag definitions. It should be a dictionary with named sections. Each section's value should be a list of dictionaries. Each dictionary inside should define the fields "name" and "comment".
515 def get_name(self, section, item): 516 """Convert the given item and section into a canonical name.""" 517 return f"{section}_{item}"
Convert the given item and section into a canonical name.
519 def generate_table_bits(self): 520 """Enumerates all flags in all sections with table and bit values.""" 521 table_counter = 0 522 bit_counter = 0 523 section_mask_previous = 0 524 # Iterate over all items in sections 525 for section in self.sections: 526 section_name = section["name"] 527 for item in section["fields"]: 528 # assign table and bit 529 item["section"] = section_name 530 item["table"] = table_counter 531 item["bit"] = bit_counter 532 # establish simple lookups 533 c_name = self.get_name(section_name, item["name"]) 534 self.name2bit[c_name] = (table_counter, bit_counter) 535 self.bit2name[(table_counter, bit_counter)] = c_name 536 self.bit2comment[(table_counter, bit_counter)] = item["comment"] 537 # increment bit 538 bit_counter += 1 539 # TODO sections should NOT cross table boundaries so that flag 540 # masks work correctly. This needs to be more intelligent. 541 # increment table if necessary 542 if bit_counter == 64: 543 bit_counter = 0 544 table_counter += 1 545 # generate a bit mask to cover all bits from this section but excluding 546 # other sections 547 section_mask = 2**bit_counter - 1 - section_mask_previous 548 self.section_masks[section_name] = section_mask 549 # include this new section in the mask of previous sections to be 550 # excluded later 551 section_mask_previous |= section_mask
Enumerates all flags in all sections with table and bit values.
553 def write_header(self): 554 """Generate a header file.""" 555 DYNAMIC_CONTENT = "" 556 for section in self.sections: 557 section_name = section["name"] 558 DYNAMIC_CONTENT += f"\n//// {section_name}s\n" 559 for item in section["fields"]: 560 # item["section"] = section_name 561 DYNAMIC_CONTENT += self.h_template.format(**item) 562 DYNAMIC_CONTENT += self.mask_h_template.format(section=section_name) 563 564 HeaderFile("task_flags").to_C( 565 { 566 "{FLAG_COLLECTION_SIZE}": str(self.flag_collection_size), 567 "//{DYNAMIC_CONTENT}//": DYNAMIC_CONTENT, 568 } 569 )
Generate a header file.
571 def write_c(self): 572 """Generate a C file.""" 573 DYNAMIC_CONTENT = "" 574 for section in self.sections: 575 section_name = section["name"] 576 DYNAMIC_CONTENT += f"//// {section_name}s\n" 577 for item in section["fields"]: 578 DYNAMIC_CONTENT += self.c_template.format(**item) 579 # fetch section mask 580 section_mask = self.section_masks[section_name] 581 # fetch the table for the last item processed. all items should be on 582 # the same table. 583 table_counter = self.name2bit[self.get_name(section_name, item["name"])][0] 584 DYNAMIC_CONTENT += self.mask_c_template.format(section=section_name, mask=section_mask, table=table_counter) 585 586 CFile("task_flags").to_C( 587 { 588 "//{DYNAMIC_CONTENT}//": DYNAMIC_CONTENT, 589 } 590 )
Generate a C file.
592 def interpret_flags(self, table, value): 593 """Given a table and its value, interpret the flags.""" 594 flags = [] 595 for i in range(0, 64): 596 # iterate over 64 bits of the table 597 if value & 2**i != 0: 598 flags.append(self.bit2name[(table, i)] + " // " + self.bit2comment[(table, i)]) 599 return flags
Given a table and its value, interpret the flags.