import sys import re from collections import defaultdict import argparse # --- .ioc File Parsing --- def parse_ioc_file(file_path): """ Parses an STM32CubeIDE .ioc file. Extracts the MCU Name, and maps pin configurations. """ mcu_name = None pins = defaultdict(dict) print(f"[*] Parsing IOC file: {file_path}") try: with open(file_path, 'r', encoding='utf-8') as f: for line in f: line = line.strip() if not line or line.startswith('#') or '=' not in line: continue key, value = [x.strip() for x in line.split('=', 1)] if key == 'Mcu.Name': mcu_name = value elif '.' in key: pin_match = re.match(r'^([A-Z0-9]+)\.(.+)', key) if pin_match: pin_name, pin_property = pin_match.groups() # Capture PA-PZ pins if pin_name.startswith('P') and len(pin_name) > 1 and pin_name[1].isalpha(): pins[pin_name][pin_property] = value except FileNotFoundError: print(f"[-] Error: IOC file not found at '{file_path}'", file=sys.stderr) return None, None except Exception as e: print(f"[-] An error occurred while parsing the IOC file: {e}", file=sys.stderr) return None, None if mcu_name: print(f" [+] Found MCU in IOC: {mcu_name}") print(f" [+] Found {len(pins)} configured pins.") return mcu_name, dict(pins) # --- S-Expression lib_symbols Finder --- def find_lib_symbols_block(sch_content): """ Finds the start and end indices of the (lib_symbols ...) block at the top of the .kicad_sch file. """ match = re.search(r'\(lib_symbols\s*', sch_content) if not match: return None, None start_pos = match.start() paren_count = 0 for i in range(start_pos, len(sch_content)): char = sch_content[i] if char == '(': paren_count += 1 elif char == ')': paren_count -= 1 if paren_count == 0: return start_pos, i + 1 return None, None # --- KiCad Schematic Parsing and Modification --- def update_kicad_schematic(sch_path, ioc_pins): """Loads and updates the symbol definitions inside (lib_symbols) in the KiCad schematic.""" print(f"\n[*] Processing KiCad schematic: {sch_path}") try: with open(sch_path, 'r', encoding='utf-8') as f: sch_content = f.read() except FileNotFoundError: print(f"[-] Error: KiCad schematic file not found at '{sch_path}'", file=sys.stderr) return # Find the library symbols block where the actual pin list is defined lib_start, lib_end = find_lib_symbols_block(sch_content) if not lib_start: print("[-] Error: Could not locate (lib_symbols) block in the schematic.", file=sys.stderr) return lib_block = sch_content[lib_start:lib_end] # Find the specific STM32 symbol template inside lib_symbols # e.g., (symbol "MCU_ST_STM32F4:STM32F411CEUx" ... ) symbol_match = re.search(r'\(\s*symbol\s+"([^"]*STM32[^"]*)"', lib_block, re.IGNORECASE) if not symbol_match: print("[-] Error: Could not find STM32 symbol definition in (lib_symbols).", file=sys.stderr) return symbol_name = symbol_match.group(1) print(f" [+] Targeting Symbol Template: '{symbol_name}'") # Isolate this specific symbol template S-expression block start_idx = lib_block.find(f'symbol "{symbol_name}"') - 1 paren_count = 0 end_idx = -1 for i in range(start_idx, len(lib_block)): char = lib_block[i] if char == '(': paren_count += 1 elif char == ')': paren_count -= 1 if paren_count == 0: end_idx = i + 1 break if end_idx == -1: print("[-] Error: Failed to isolate STM32 symbol template expression block.", file=sys.stderr) return symbol_template_block = lib_block[start_idx:end_idx] updated_template_block = symbol_template_block pins_updated = 0 # Matches individual pin elements: (pin input line (at ...) ... (name "PA1" (effects ...)) (number "10" ...)) # We are matching the (name "PA1") property inside the pin definitions. pin_name_pattern = re.compile(r'\(pin\s+\w+\s+\w+\s+\(at\s+[^)]+\)\s+\(length\s+[^)]+\)\s+\(name\s+"([^"]+)"', re.DOTALL) # We'll use a direct string line process on the block to find and replace pin names safely lines = symbol_template_block.splitlines() updated_lines = [] for line in lines: # Check if line contains a pin name parameter e.g., (name "PA1" pin_name_match = re.search(r'\(name\s+"([^"]+)"', line) if pin_name_match: original_pin_name = pin_name_match.group(1) # Strip previous runs: e.g., '[PA1] TRIAC_PUMP' -> 'PA1' base_name_match = re.match(r'^\[(P[A-Z]\d{1,2})\]', original_pin_name) base_pin_name = base_name_match.group(1) if base_name_match else original_pin_name if base_pin_name in ioc_pins: pin_config = ioc_pins[base_pin_name] new_suffix = pin_config.get('GPIO_Label') or pin_config.get('Signal') if new_suffix: new_suffix = new_suffix.replace('"', '\\"') new_pin_name = f"[{base_pin_name}] {new_suffix}" if new_pin_name != original_pin_name: line = line.replace(f'name "{original_pin_name}"', f'name "{new_pin_name}"') pins_updated += 1 print(f" Renaming pin: {original_pin_name} -> {new_pin_name}") updated_lines.append(line) if pins_updated > 0: print(f"\n [+] Updating {pins_updated} pins inside the '{symbol_name}' template.") # Reconstruct symbol template updated_template_block = "\n".join(updated_lines) # Splice template back into library block updated_lib_block = lib_block[:start_idx] + updated_template_block + lib_block[end_idx:] # Splice library block back into schematic content final_sch_content = sch_content[:lib_start] + updated_lib_block + sch_content[lib_end:] try: with open(sch_path, 'w', encoding='utf-8') as f: f.write(final_sch_content) print(f"[+] KiCad schematic saved successfully: {sch_path}") except Exception as e: print(f"[-] Error: Failed to save changes to KiCad file. {e}", file=sys.stderr) else: print("\n [+] No pin updates required. Template pins are already synchronized.") # --- Main CLI Router --- if __name__ == '__main__': parser = argparse.ArgumentParser( description='Update template KiCad pin properties using an STM32 .ioc config.', formatter_class=argparse.RawTextHelpFormatter ) parser.add_argument('ioc_file', help='Path to STM32CubeIDE .ioc file.') parser.add_argument('kicad_sch', help='Path to KiCad .kicad_sch schematic file.') args = parser.parse_args() ioc_mcu, ioc_pins = parse_ioc_file(args.ioc_file) if ioc_pins: update_kicad_schematic(args.kicad_sch, ioc_pins)