Add circuit pattern recognition feature for KiCad schematics
This commit introduces a new circuit pattern recognition system that can automatically identify common circuit patterns in KiCad schematics, including: - Power supply circuits (linear regulators, switching converters) - Amplifier circuits (op-amps, transistor amplifiers) - Filter circuits (passive and active) - Oscillator circuits (crystal, RC, IC-based) - Digital interfaces (I2C, SPI, UART, USB) - Microcontroller circuits - Sensor interfaces The implementation includes: - Pattern recognition algorithms for common components - Component value extraction and normalization utilities - MCP tools for running pattern analysis - MCP resources for displaying formatted results - Comprehensive documentation Users can easily extend the pattern recognition by adding new component patterns or circuit recognition functions.
This commit is contained in:
parent
750dd260c4
commit
7343e032c1
61
README.md
61
README.md
@ -12,6 +12,7 @@ This guide will help you set up a Model Context Protocol (MCP) server for KiCad.
|
||||
- [Installation Steps](#installation-steps)
|
||||
- [Understanding MCP Components](#understanding-mcp-components)
|
||||
- [Feature Highlights](#feature-highlights)
|
||||
- [Natural Language Interaction](#natural-language-interaction)
|
||||
- [Documentation](#documentation)
|
||||
- [Configuration](#configuration)
|
||||
- [Development Guide](#development-guide)
|
||||
@ -139,16 +140,54 @@ The Model Context Protocol (MCP) defines three primary ways to provide capabilit
|
||||
|
||||
## Feature Highlights
|
||||
|
||||
The KiCad MCP Server provides several key features:
|
||||
The KiCad MCP Server provides several key features, each with detailed documentation:
|
||||
|
||||
- **Project Management**: List, examine, and open KiCad projects
|
||||
- **PCB Design Analysis**: Get insights about your PCB designs and schematics
|
||||
- **Netlist Extraction**: Extract and analyze component connections from schematics
|
||||
- **BOM Management**: Analyze and export Bills of Materials
|
||||
- **Design Rule Checking**: Run DRC checks and track your progress over time
|
||||
- **PCB Visualization**: Generate visual representations of your PCB layouts
|
||||
- *Example:* "Show me all my recent KiCad projects" → Lists all projects sorted by modification date
|
||||
|
||||
For detailed usage guides, see the [documentation](#documentation).
|
||||
- **PCB Design Analysis**: Get insights about your PCB designs and schematics
|
||||
- *Example:* "Analyze the component density of my temperature sensor board" → Provides component spacing analysis
|
||||
|
||||
- **Netlist Extraction**: Extract and analyze component connections from schematics
|
||||
- *Example:* "What components are connected to the MCU in my Arduino shield?" → Shows all connections to the microcontroller
|
||||
|
||||
- **BOM Management**: Analyze and export Bills of Materials
|
||||
- *Example:* "Generate a BOM for my smart watch project" → Creates a detailed bill of materials
|
||||
|
||||
- **Design Rule Checking**: Run DRC checks and track your progress over time
|
||||
- *Example:* "Run DRC on my power supply board and compare to last week" → Shows progress in fixing violations
|
||||
|
||||
- **PCB Visualization**: Generate visual representations of your PCB layouts
|
||||
- *Example:* "Show me a thumbnail of my audio amplifier PCB" → Displays a visual render of the board
|
||||
|
||||
- **Circuit Pattern Recognition**: Automatically identify common circuit patterns in your schematics
|
||||
- *Example:* "What power supply topologies am I using in my IoT device?" → Identifies buck, boost, or linear regulators
|
||||
|
||||
For more examples and details on each feature, see the dedicated guides in the documentation.
|
||||
|
||||
## Natural Language Interaction
|
||||
|
||||
While our documentation often shows examples like:
|
||||
|
||||
```
|
||||
Show me the DRC report for /Users/username/Documents/KiCad/my_project/my_project.kicad_pro
|
||||
```
|
||||
|
||||
You don't need to type the full path to your files! The LLM can understand more natural language requests.
|
||||
|
||||
For example, instead of the formal command above, you can simply ask:
|
||||
|
||||
```
|
||||
Can you check if there are any design rule violations in my Arduino shield project?
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```
|
||||
I'm working on the temperature sensor circuit. Can you identify what patterns it uses?
|
||||
```
|
||||
|
||||
The LLM will understand your intent and request the relevant information from the KiCad MCP Server. If it needs clarification about which project you're referring to, it will ask.
|
||||
|
||||
## Documentation
|
||||
|
||||
@ -160,6 +199,7 @@ Detailed documentation for each feature is available in the `docs/` directory:
|
||||
- [Bill of Materials (BOM)](docs/bom_guide.md)
|
||||
- [Design Rule Checking (DRC)](docs/drc_guide.md)
|
||||
- [PCB Visualization](docs/thumbnail_guide.md)
|
||||
- [Circuit Pattern Recognition](docs/pattern_guide.md)
|
||||
- [Prompt Templates](docs/prompt_guide.md)
|
||||
|
||||
## Configuration
|
||||
@ -243,6 +283,12 @@ Want to contribute to the KiCad MCP Server? Here's how you can help improve this
|
||||
3. Add your changes
|
||||
4. Submit a pull request
|
||||
|
||||
Key areas for contribution:
|
||||
- Adding support for more component patterns in the Circuit Pattern Recognition system
|
||||
- Improving documentation and examples
|
||||
- Adding new features or enhancing existing ones
|
||||
- Fixing bugs and improving error handling
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.
|
||||
|
||||
## Future Development Ideas
|
||||
@ -258,6 +304,7 @@ Interested in contributing? Here are some ideas for future development:
|
||||
7. **Web UI** - Create a simple web interface for configuration and monitoring
|
||||
8. **Circuit Analysis** - Add automated circuit analysis features
|
||||
9. **Test Coverage** - Improve test coverage across the codebase
|
||||
10. **Circuit Pattern Recognition** - Expand the pattern database with more component types and circuit topologies
|
||||
|
||||
## License
|
||||
|
||||
|
251
docs/pattern_guide.md
Normal file
251
docs/pattern_guide.md
Normal file
@ -0,0 +1,251 @@
|
||||
# KiCad Circuit Pattern Recognition Guide
|
||||
|
||||
This guide explains how to use the circuit pattern recognition features in the KiCad MCP Server to identify common circuit blocks in your schematics.
|
||||
|
||||
## Overview
|
||||
|
||||
The circuit pattern recognition functionality allows you to:
|
||||
|
||||
1. Automatically identify common circuit patterns in your KiCad schematics
|
||||
2. Get detailed information about each identified circuit
|
||||
3. Understand the structure and function of different parts of your design
|
||||
4. Quickly locate specific circuit types (power supplies, amplifiers, etc.)
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Example Prompt |
|
||||
|------|---------------|
|
||||
| Identify patterns in a schematic | `Identify circuit patterns in my schematic at /path/to/schematic.kicad_sch` |
|
||||
| Identify patterns in a project | `Analyze circuit patterns in my KiCad project at /path/to/project.kicad_pro` |
|
||||
| Get a report of identified patterns | `Show me the circuit patterns in my KiCad project at /path/to/project.kicad_pro` |
|
||||
| Find specific patterns | `Find all power supply circuits in my schematic at /path/to/schematic.kicad_sch` |
|
||||
|
||||
## Using Pattern Recognition Features
|
||||
|
||||
### Identifying Circuit Patterns
|
||||
|
||||
To identify circuit patterns in a schematic:
|
||||
|
||||
```
|
||||
Identify circuit patterns in my schematic at /path/to/schematic.kicad_sch
|
||||
```
|
||||
|
||||
This will:
|
||||
- Parse your schematic to extract component and connection information
|
||||
- Apply pattern recognition algorithms to identify common circuit blocks
|
||||
- Generate a comprehensive report of all identified patterns
|
||||
- Provide details about each pattern's components and characteristics
|
||||
|
||||
### Project-Based Pattern Recognition
|
||||
|
||||
To analyze circuit patterns in a KiCad project:
|
||||
|
||||
```
|
||||
Analyze circuit patterns in my KiCad project at /path/to/project.kicad_pro
|
||||
```
|
||||
|
||||
This will find the schematic associated with your project and perform pattern recognition on it.
|
||||
|
||||
### Viewing Pattern Reports
|
||||
|
||||
For a formatted report of identified patterns:
|
||||
|
||||
```
|
||||
Show me the circuit patterns in my KiCad project at /path/to/project.kicad_pro
|
||||
```
|
||||
|
||||
This will load the `kicad://patterns/project/path/to/project.kicad_pro` resource, showing:
|
||||
- A summary of all identified patterns
|
||||
- Detailed information for each pattern type
|
||||
- Component references and values
|
||||
- Additional characteristics specific to each pattern type
|
||||
|
||||
### Searching for Specific Pattern Types
|
||||
|
||||
You can also ask about specific types of patterns:
|
||||
|
||||
```
|
||||
Find all power supply circuits in my schematic at /path/to/schematic.kicad_sch
|
||||
```
|
||||
|
||||
```
|
||||
Show me the microcontroller circuits in my KiCad project at /path/to/project.kicad_pro
|
||||
```
|
||||
|
||||
## Supported Pattern Types
|
||||
|
||||
The pattern recognition system currently identifies the following types of circuits:
|
||||
|
||||
### Power Supply Circuits
|
||||
- Linear voltage regulators (78xx/79xx series, LDOs, etc.)
|
||||
- Switching regulators (buck, boost, buck-boost)
|
||||
|
||||
### Amplifier Circuits
|
||||
- Operational amplifiers (general-purpose, audio, instrumentation)
|
||||
- Transistor amplifiers (BJT, FET)
|
||||
- Audio amplifier ICs
|
||||
|
||||
### Filter Circuits
|
||||
- Passive filters (RC low-pass/high-pass)
|
||||
- Active filters (op-amp based)
|
||||
- Crystal and ceramic filters
|
||||
|
||||
### Oscillator Circuits
|
||||
- Crystal oscillators
|
||||
- Oscillator ICs
|
||||
- RC oscillators (555 timer, etc.)
|
||||
|
||||
### Digital Interface Circuits
|
||||
- I2C interfaces
|
||||
- SPI interfaces
|
||||
- UART/Serial interfaces
|
||||
- USB interfaces
|
||||
- Ethernet interfaces
|
||||
|
||||
### Microcontroller Circuits
|
||||
- Various microcontroller families (AVR, STM32, PIC, ESP, etc.)
|
||||
- Development boards (Arduino, ESP32, Raspberry Pi Pico, etc.)
|
||||
|
||||
### Sensor Interface Circuits
|
||||
- Temperature sensors
|
||||
- Humidity sensors
|
||||
- Pressure sensors
|
||||
- Motion sensors (accelerometers, gyroscopes)
|
||||
- Light sensors
|
||||
- Many other sensor types
|
||||
|
||||
## Extending the Pattern Recognition System
|
||||
|
||||
The pattern recognition system is designed to be extensible. If you find that certain components or circuit patterns you use frequently aren't being recognized, you can contribute to the system.
|
||||
|
||||
### Adding New Component Patterns
|
||||
|
||||
The pattern recognition is primarily based on regular expression matching of component values and library IDs. The patterns are defined in the `kicad_mcp/utils/pattern_recognition.py` file.
|
||||
|
||||
For example, to add support for a new microcontroller family, you could update the `mcu_patterns` dictionary in the `identify_microcontrollers()` function:
|
||||
|
||||
```python
|
||||
mcu_patterns = {
|
||||
# Existing patterns...
|
||||
"AVR": r"ATMEGA\d+|ATTINY\d+|AT90\w+",
|
||||
"STM32": r"STM32\w+",
|
||||
|
||||
# Add your new pattern here
|
||||
"Renesas": r"R[A-Z]\d+|RL78|RX\d+",
|
||||
}
|
||||
```
|
||||
|
||||
Similarly, you can add patterns for new sensors, power supply ICs, or other components in their respective functions.
|
||||
|
||||
### Adding New Circuit Recognition Functions
|
||||
|
||||
For entirely new types of circuits, you can add new recognition functions in the `kicad_mcp/utils/pattern_recognition.py` file, following the pattern of existing functions.
|
||||
|
||||
For example, you might add:
|
||||
|
||||
```python
|
||||
def identify_motor_drivers(components: Dict[str, Any], nets: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Identify motor driver circuits in the schematic."""
|
||||
# Your implementation here
|
||||
...
|
||||
```
|
||||
|
||||
Then, update the `identify_circuit_patterns()` function in `kicad_mcp/tools/pattern_tools.py` to call your new function and include its results.
|
||||
|
||||
### Contributing Your Extensions
|
||||
|
||||
We strongly encourage you to contribute your pattern recognition extensions back to the project so that everyone can benefit from improved recognition capabilities!
|
||||
|
||||
To contribute:
|
||||
|
||||
1. Fork the repository on GitHub
|
||||
2. Make your changes to add new patterns or recognition functions
|
||||
3. Test your changes with your own schematics
|
||||
4. Submit a pull request with:
|
||||
- A description of the new patterns you've added
|
||||
- Examples of components/circuits that can now be recognized
|
||||
- Any test cases you used to verify the recognition
|
||||
|
||||
Your contributions will help build a more comprehensive pattern recognition system that works for a wider variety of designs.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Patterns Not Being Recognized
|
||||
|
||||
If your circuits aren't being recognized:
|
||||
|
||||
1. **Check component naming**: The pattern recognition often relies on standard reference designators (R for resistors, C for capacitors, etc.)
|
||||
2. **Check component values**: Make sure your component values are in standard formats
|
||||
3. **Check library IDs**: The system also looks at library IDs, so using standard libraries can help
|
||||
4. **Look at existing patterns**: Check the pattern_recognition.py file to see if your components match the existing patterns
|
||||
|
||||
### Pattern Recognition Fails
|
||||
|
||||
If the pattern recognition process fails entirely:
|
||||
|
||||
1. **Check file paths**: Ensure the schematic file exists and has the correct extension
|
||||
2. **Verify schematic format**: Make sure it's a valid KiCad 6+ .kicad_sch file
|
||||
3. **Check file permissions**: Ensure you have read access to the file
|
||||
4. **Try a simpler schematic**: Start with a small test case to verify functionality
|
||||
|
||||
## Advanced Usage
|
||||
|
||||
### Integration with Other Features
|
||||
|
||||
Combine pattern recognition with other KiCad MCP Server features:
|
||||
|
||||
1. **DRC + Pattern Recognition**: Find design issues in specific circuit blocks
|
||||
```
|
||||
Find DRC issues affecting the power supply circuits in my schematic
|
||||
```
|
||||
|
||||
2. **BOM + Pattern Recognition**: Analyze component usage by circuit type
|
||||
```
|
||||
Show me the BOM breakdown for the digital interface circuits in my design
|
||||
```
|
||||
|
||||
3. **Netlist + Pattern Recognition**: Understand connectivity in specific patterns
|
||||
```
|
||||
Analyze the connections between the microcontroller and sensor interfaces in my design
|
||||
```
|
||||
|
||||
### Batch Pattern Recognition
|
||||
|
||||
For analyzing multiple projects:
|
||||
|
||||
```
|
||||
Find all projects in my KiCad directory that contain switching regulator circuits
|
||||
```
|
||||
|
||||
```
|
||||
Compare the digital interfaces used across all my KiCad projects
|
||||
```
|
||||
|
||||
## Future Improvements
|
||||
|
||||
We plan to enhance the pattern recognition system with:
|
||||
|
||||
1. **More pattern types**: Support for additional circuit patterns
|
||||
2. **Better connection analysis**: More accurate tracing of connections between components
|
||||
3. **Hierarchical pattern recognition**: Identifying patterns across hierarchical sheets
|
||||
4. **Pattern verification**: Validating that recognized patterns follow design best practices
|
||||
5. **Component suggestions**: Recommending alternative components for recognized patterns
|
||||
|
||||
## Contribute to Pattern Recognition
|
||||
|
||||
The pattern recognition system relies on a community-driven database of component patterns. The more patterns we have, the better the recognition works for everyone!
|
||||
|
||||
If you work with components that aren't being recognized:
|
||||
|
||||
1. Check the current patterns in `kicad_mcp/utils/pattern_recognition.py`
|
||||
2. Add your own patterns for components you use
|
||||
3. Submit a pull request to share with the community
|
||||
|
||||
Common areas where contributions are valuable:
|
||||
- Modern microcontroller families and variants
|
||||
- Specialized sensor types
|
||||
- Power management ICs
|
||||
- Interface and communication chips
|
||||
- Industry-specific components
|
||||
|
||||
Your expertise in specific component types can help make the pattern recognition more useful for everyone!
|
294
kicad_mcp/resources/pattern_resources.py
Normal file
294
kicad_mcp/resources/pattern_resources.py
Normal file
@ -0,0 +1,294 @@
|
||||
"""
|
||||
Circuit pattern recognition resources for KiCad schematics.
|
||||
"""
|
||||
import os
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from kicad_mcp.utils.file_utils import get_project_files
|
||||
from kicad_mcp.utils.netlist_parser import extract_netlist
|
||||
from kicad_mcp.utils.pattern_recognition import (
|
||||
identify_power_supplies,
|
||||
identify_amplifiers,
|
||||
identify_filters,
|
||||
identify_oscillators,
|
||||
identify_digital_interfaces,
|
||||
identify_microcontrollers,
|
||||
identify_sensor_interfaces
|
||||
)
|
||||
|
||||
|
||||
def register_pattern_resources(mcp: FastMCP) -> None:
|
||||
"""Register circuit pattern recognition resources with the MCP server.
|
||||
|
||||
Args:
|
||||
mcp: The FastMCP server instance
|
||||
"""
|
||||
|
||||
@mcp.resource("kicad://patterns/{schematic_path}")
|
||||
def get_circuit_patterns_resource(schematic_path: str) -> str:
|
||||
"""Get a formatted report of identified circuit patterns in a KiCad schematic.
|
||||
|
||||
Args:
|
||||
schematic_path: Path to the KiCad schematic file (.kicad_sch)
|
||||
|
||||
Returns:
|
||||
Markdown-formatted circuit pattern report
|
||||
"""
|
||||
if not os.path.exists(schematic_path):
|
||||
return f"Schematic file not found: {schematic_path}"
|
||||
|
||||
try:
|
||||
# Extract netlist information
|
||||
netlist_data = extract_netlist(schematic_path)
|
||||
|
||||
if "error" in netlist_data:
|
||||
return f"# Circuit Pattern Analysis Error\n\nError: {netlist_data['error']}"
|
||||
|
||||
components = netlist_data.get("components", {})
|
||||
nets = netlist_data.get("nets", {})
|
||||
|
||||
# Identify circuit patterns
|
||||
power_supplies = identify_power_supplies(components, nets)
|
||||
amplifiers = identify_amplifiers(components, nets)
|
||||
filters = identify_filters(components, nets)
|
||||
oscillators = identify_oscillators(components, nets)
|
||||
digital_interfaces = identify_digital_interfaces(components, nets)
|
||||
microcontrollers = identify_microcontrollers(components)
|
||||
sensor_interfaces = identify_sensor_interfaces(components, nets)
|
||||
|
||||
# Format as Markdown report
|
||||
schematic_name = os.path.basename(schematic_path)
|
||||
|
||||
report = f"# Circuit Pattern Analysis for {schematic_name}\n\n"
|
||||
|
||||
# Add summary
|
||||
total_patterns = (
|
||||
len(power_supplies) +
|
||||
len(amplifiers) +
|
||||
len(filters) +
|
||||
len(oscillators) +
|
||||
len(digital_interfaces) +
|
||||
len(microcontrollers) +
|
||||
len(sensor_interfaces)
|
||||
)
|
||||
|
||||
report += f"## Summary\n\n"
|
||||
report += f"- **Total Components**: {netlist_data['component_count']}\n"
|
||||
report += f"- **Total Circuit Patterns Identified**: {total_patterns}\n\n"
|
||||
|
||||
report += "### Pattern Types\n\n"
|
||||
report += f"- **Power Supply Circuits**: {len(power_supplies)}\n"
|
||||
report += f"- **Amplifier Circuits**: {len(amplifiers)}\n"
|
||||
report += f"- **Filter Circuits**: {len(filters)}\n"
|
||||
report += f"- **Oscillator Circuits**: {len(oscillators)}\n"
|
||||
report += f"- **Digital Interface Circuits**: {len(digital_interfaces)}\n"
|
||||
report += f"- **Microcontroller Circuits**: {len(microcontrollers)}\n"
|
||||
report += f"- **Sensor Interface Circuits**: {len(sensor_interfaces)}\n\n"
|
||||
|
||||
# Add detailed sections
|
||||
if power_supplies:
|
||||
report += "## Power Supply Circuits\n\n"
|
||||
for i, ps in enumerate(power_supplies, 1):
|
||||
ps_type = ps.get("type", "Unknown")
|
||||
ps_subtype = ps.get("subtype", "")
|
||||
|
||||
report += f"### Power Supply {i}: {ps_subtype.upper() if ps_subtype else ps_type.title()}\n\n"
|
||||
|
||||
if ps_type == "linear_regulator":
|
||||
report += f"- **Type**: Linear Voltage Regulator\n"
|
||||
report += f"- **Subtype**: {ps_subtype}\n"
|
||||
report += f"- **Main Component**: {ps.get('main_component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {ps.get('value', 'Unknown')}\n"
|
||||
report += f"- **Output Voltage**: {ps.get('output_voltage', 'Unknown')}\n"
|
||||
elif ps_type == "switching_regulator":
|
||||
report += f"- **Type**: Switching Voltage Regulator\n"
|
||||
report += f"- **Topology**: {ps_subtype.title() if ps_subtype else 'Unknown'}\n"
|
||||
report += f"- **Main Component**: {ps.get('main_component', 'Unknown')}\n"
|
||||
report += f"- **Inductor**: {ps.get('inductor', 'Unknown')}\n"
|
||||
report += f"- **Value**: {ps.get('value', 'Unknown')}\n"
|
||||
|
||||
report += "\n"
|
||||
|
||||
if amplifiers:
|
||||
report += "## Amplifier Circuits\n\n"
|
||||
for i, amp in enumerate(amplifiers, 1):
|
||||
amp_type = amp.get("type", "Unknown")
|
||||
amp_subtype = amp.get("subtype", "")
|
||||
|
||||
report += f"### Amplifier {i}: {amp_subtype.upper() if amp_subtype else amp_type.title()}\n\n"
|
||||
|
||||
if amp_type == "operational_amplifier":
|
||||
report += f"- **Type**: Operational Amplifier\n"
|
||||
report += f"- **Subtype**: {amp_subtype.replace('_', ' ').title() if amp_subtype else 'General Purpose'}\n"
|
||||
report += f"- **Component**: {amp.get('component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {amp.get('value', 'Unknown')}\n"
|
||||
elif amp_type == "transistor_amplifier":
|
||||
report += f"- **Type**: Transistor Amplifier\n"
|
||||
report += f"- **Transistor Type**: {amp_subtype}\n"
|
||||
report += f"- **Component**: {amp.get('component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {amp.get('value', 'Unknown')}\n"
|
||||
elif amp_type == "audio_amplifier_ic":
|
||||
report += f"- **Type**: Audio Amplifier IC\n"
|
||||
report += f"- **Component**: {amp.get('component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {amp.get('value', 'Unknown')}\n"
|
||||
|
||||
report += "\n"
|
||||
|
||||
if filters:
|
||||
report += "## Filter Circuits\n\n"
|
||||
for i, filt in enumerate(filters, 1):
|
||||
filt_type = filt.get("type", "Unknown")
|
||||
filt_subtype = filt.get("subtype", "")
|
||||
|
||||
report += f"### Filter {i}: {filt_subtype.upper() if filt_subtype else filt_type.title()}\n\n"
|
||||
|
||||
if filt_type == "passive_filter":
|
||||
report += f"- **Type**: Passive Filter\n"
|
||||
report += f"- **Topology**: {filt_subtype.replace('_', ' ').upper() if filt_subtype else 'Unknown'}\n"
|
||||
report += f"- **Components**: {', '.join(filt.get('components', []))}\n"
|
||||
elif filt_type == "active_filter":
|
||||
report += f"- **Type**: Active Filter\n"
|
||||
report += f"- **Main Component**: {filt.get('main_component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {filt.get('value', 'Unknown')}\n"
|
||||
elif filt_type == "crystal_filter":
|
||||
report += f"- **Type**: Crystal Filter\n"
|
||||
report += f"- **Component**: {filt.get('component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {filt.get('value', 'Unknown')}\n"
|
||||
elif filt_type == "ceramic_filter":
|
||||
report += f"- **Type**: Ceramic Filter\n"
|
||||
report += f"- **Component**: {filt.get('component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {filt.get('value', 'Unknown')}\n"
|
||||
|
||||
report += "\n"
|
||||
|
||||
if oscillators:
|
||||
report += "## Oscillator Circuits\n\n"
|
||||
for i, osc in enumerate(oscillators, 1):
|
||||
osc_type = osc.get("type", "Unknown")
|
||||
osc_subtype = osc.get("subtype", "")
|
||||
|
||||
report += f"### Oscillator {i}: {osc_subtype.upper() if osc_subtype else osc_type.title()}\n\n"
|
||||
|
||||
if osc_type == "crystal_oscillator":
|
||||
report += f"- **Type**: Crystal Oscillator\n"
|
||||
report += f"- **Component**: {osc.get('component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {osc.get('value', 'Unknown')}\n"
|
||||
report += f"- **Frequency**: {osc.get('frequency', 'Unknown')}\n"
|
||||
report += f"- **Has Load Capacitors**: {'Yes' if osc.get('has_load_capacitors', False) else 'No'}\n"
|
||||
elif osc_type == "oscillator_ic":
|
||||
report += f"- **Type**: Oscillator IC\n"
|
||||
report += f"- **Component**: {osc.get('component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {osc.get('value', 'Unknown')}\n"
|
||||
report += f"- **Frequency**: {osc.get('frequency', 'Unknown')}\n"
|
||||
elif osc_type == "rc_oscillator":
|
||||
report += f"- **Type**: RC Oscillator\n"
|
||||
report += f"- **Subtype**: {osc_subtype.replace('_', ' ').title() if osc_subtype else 'Unknown'}\n"
|
||||
report += f"- **Component**: {osc.get('component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {osc.get('value', 'Unknown')}\n"
|
||||
|
||||
report += "\n"
|
||||
|
||||
if digital_interfaces:
|
||||
report += "## Digital Interface Circuits\n\n"
|
||||
for i, iface in enumerate(digital_interfaces, 1):
|
||||
iface_type = iface.get("type", "Unknown")
|
||||
|
||||
report += f"### Interface {i}: {iface_type.replace('_', ' ').upper()}\n\n"
|
||||
report += f"- **Type**: {iface_type.replace('_', ' ').title()}\n"
|
||||
|
||||
signals = iface.get("signals_found", [])
|
||||
if signals:
|
||||
report += f"- **Signals Found**: {', '.join(signals)}\n"
|
||||
|
||||
report += "\n"
|
||||
|
||||
if microcontrollers:
|
||||
report += "## Microcontroller Circuits\n\n"
|
||||
for i, mcu in enumerate(microcontrollers, 1):
|
||||
mcu_type = mcu.get("type", "Unknown")
|
||||
|
||||
if mcu_type == "microcontroller":
|
||||
report += f"### Microcontroller {i}: {mcu.get('model', mcu.get('family', 'Unknown'))}\n\n"
|
||||
report += f"- **Type**: Microcontroller\n"
|
||||
report += f"- **Family**: {mcu.get('family', 'Unknown')}\n"
|
||||
if "model" in mcu:
|
||||
report += f"- **Model**: {mcu['model']}\n"
|
||||
report += f"- **Component**: {mcu.get('component', 'Unknown')}\n"
|
||||
if "common_usage" in mcu:
|
||||
report += f"- **Common Usage**: {mcu['common_usage']}\n"
|
||||
if "features" in mcu:
|
||||
report += f"- **Features**: {mcu['features']}\n"
|
||||
elif mcu_type == "development_board":
|
||||
report += f"### Development Board {i}: {mcu.get('board_type', 'Unknown')}\n\n"
|
||||
report += f"- **Type**: Development Board\n"
|
||||
report += f"- **Board Type**: {mcu.get('board_type', 'Unknown')}\n"
|
||||
report += f"- **Component**: {mcu.get('component', 'Unknown')}\n"
|
||||
report += f"- **Value**: {mcu.get('value', 'Unknown')}\n"
|
||||
|
||||
report += "\n"
|
||||
|
||||
if sensor_interfaces:
|
||||
report += "## Sensor Interface Circuits\n\n"
|
||||
for i, sensor in enumerate(sensor_interfaces, 1):
|
||||
sensor_type = sensor.get("type", "Unknown")
|
||||
sensor_subtype = sensor.get("subtype", "")
|
||||
|
||||
report += f"### Sensor {i}: {sensor_subtype.title() + ' ' if sensor_subtype else ''}{sensor_type.replace('_', ' ').title()}\n\n"
|
||||
report += f"- **Type**: {sensor_type.replace('_', ' ').title()}\n"
|
||||
|
||||
if sensor_subtype:
|
||||
report += f"- **Subtype**: {sensor_subtype}\n"
|
||||
|
||||
report += f"- **Component**: {sensor.get('component', 'Unknown')}\n"
|
||||
|
||||
if "model" in sensor:
|
||||
report += f"- **Model**: {sensor['model']}\n"
|
||||
|
||||
report += f"- **Value**: {sensor.get('value', 'Unknown')}\n"
|
||||
|
||||
if "interface" in sensor:
|
||||
report += f"- **Interface**: {sensor['interface']}\n"
|
||||
|
||||
if "measures" in sensor:
|
||||
if isinstance(sensor["measures"], list):
|
||||
report += f"- **Measures**: {', '.join(sensor['measures'])}\n"
|
||||
else:
|
||||
report += f"- **Measures**: {sensor['measures']}\n"
|
||||
|
||||
if "range" in sensor:
|
||||
report += f"- **Range**: {sensor['range']}\n"
|
||||
|
||||
report += "\n"
|
||||
|
||||
return report
|
||||
|
||||
except Exception as e:
|
||||
return f"# Circuit Pattern Analysis Error\n\nError: {str(e)}"
|
||||
|
||||
@mcp.resource("kicad://patterns/project/{project_path}")
|
||||
def get_project_patterns_resource(project_path: str) -> str:
|
||||
"""Get a formatted report of identified circuit patterns in a KiCad project.
|
||||
|
||||
Args:
|
||||
project_path: Path to the KiCad project file (.kicad_pro)
|
||||
|
||||
Returns:
|
||||
Markdown-formatted circuit pattern report
|
||||
"""
|
||||
if not os.path.exists(project_path):
|
||||
return f"Project not found: {project_path}"
|
||||
|
||||
try:
|
||||
# Get the schematic file from the project
|
||||
files = get_project_files(project_path)
|
||||
|
||||
if "schematic" not in files:
|
||||
return "Schematic file not found in project"
|
||||
|
||||
schematic_path = files["schematic"]
|
||||
|
||||
# Use the existing resource handler to generate the report
|
||||
return get_circuit_patterns_resource(schematic_path)
|
||||
|
||||
except Exception as e:
|
||||
return f"# Circuit Pattern Analysis Error\n\nError: {str(e)}"
|
@ -9,6 +9,8 @@ from kicad_mcp.resources.files import register_file_resources
|
||||
from kicad_mcp.resources.drc_resources import register_drc_resources
|
||||
from kicad_mcp.resources.bom_resources import register_bom_resources
|
||||
from kicad_mcp.resources.netlist_resources import register_netlist_resources
|
||||
from kicad_mcp.resources.pattern_resources import register_pattern_resources
|
||||
|
||||
|
||||
# Import tool handlers
|
||||
from kicad_mcp.tools.project_tools import register_project_tools
|
||||
@ -17,11 +19,13 @@ from kicad_mcp.tools.export_tools import register_export_tools
|
||||
from kicad_mcp.tools.drc_tools import register_drc_tools
|
||||
from kicad_mcp.tools.bom_tools import register_bom_tools
|
||||
from kicad_mcp.tools.netlist_tools import register_netlist_tools
|
||||
from kicad_mcp.tools.pattern_tools import register_pattern_tools
|
||||
|
||||
# Import prompt handlers
|
||||
from kicad_mcp.prompts.templates import register_prompts
|
||||
from kicad_mcp.prompts.drc_prompt import register_drc_prompts
|
||||
from kicad_mcp.prompts.bom_prompts import register_bom_prompts
|
||||
from kicad_mcp.prompts.pattern_prompts import register_pattern_prompts
|
||||
|
||||
# Import utils
|
||||
from kicad_mcp.utils.logger import Logger
|
||||
@ -56,6 +60,7 @@ def create_server() -> FastMCP:
|
||||
register_drc_resources(mcp)
|
||||
register_bom_resources(mcp)
|
||||
register_netlist_resources(mcp)
|
||||
register_pattern_resources(mcp)
|
||||
|
||||
# Register tools
|
||||
logger.debug("Registering tools...")
|
||||
@ -65,12 +70,14 @@ def create_server() -> FastMCP:
|
||||
register_drc_tools(mcp)
|
||||
register_bom_tools(mcp)
|
||||
register_netlist_tools(mcp)
|
||||
register_pattern_tools(mcp)
|
||||
|
||||
# Register prompts
|
||||
logger.debug("Registering prompts...")
|
||||
register_prompts(mcp)
|
||||
register_drc_prompts(mcp)
|
||||
register_bom_prompts(mcp)
|
||||
register_pattern_prompts(mcp)
|
||||
|
||||
logger.info("Server initialization complete")
|
||||
return mcp
|
||||
|
177
kicad_mcp/tools/pattern_tools.py
Normal file
177
kicad_mcp/tools/pattern_tools.py
Normal file
@ -0,0 +1,177 @@
|
||||
"""
|
||||
Circuit pattern recognition tools for KiCad schematics.
|
||||
"""
|
||||
import os
|
||||
from typing import Dict, List, Any, Optional
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
from kicad_mcp.utils.file_utils import get_project_files
|
||||
from kicad_mcp.utils.netlist_parser import extract_netlist, analyze_netlist
|
||||
from kicad_mcp.utils.pattern_recognition import (
|
||||
identify_power_supplies,
|
||||
identify_amplifiers,
|
||||
identify_filters,
|
||||
identify_oscillators,
|
||||
identify_digital_interfaces,
|
||||
identify_microcontrollers,
|
||||
identify_sensor_interfaces
|
||||
)
|
||||
|
||||
def register_pattern_tools(mcp: FastMCP) -> None:
|
||||
"""Register circuit pattern recognition tools with the MCP server.
|
||||
|
||||
Args:
|
||||
mcp: The FastMCP server instance
|
||||
"""
|
||||
|
||||
@mcp.tool()
|
||||
async def identify_circuit_patterns(schematic_path: str, ctx: Context) -> Dict[str, Any]:
|
||||
"""Identify common circuit patterns in a KiCad schematic.
|
||||
|
||||
This tool analyzes a schematic to recognize common circuit blocks such as:
|
||||
- Power supply circuits (linear regulators, switching converters)
|
||||
- Amplifier circuits (op-amps, transistor amplifiers)
|
||||
- Filter circuits (RC, LC, active filters)
|
||||
- Digital interfaces (I2C, SPI, UART)
|
||||
- Microcontroller circuits
|
||||
- And more
|
||||
|
||||
Args:
|
||||
schematic_path: Path to the KiCad schematic file (.kicad_sch)
|
||||
ctx: MCP context for progress reporting
|
||||
|
||||
Returns:
|
||||
Dictionary with identified circuit patterns
|
||||
"""
|
||||
if not os.path.exists(schematic_path):
|
||||
ctx.info(f"Schematic file not found: {schematic_path}")
|
||||
return {"success": False, "error": f"Schematic file not found: {schematic_path}"}
|
||||
|
||||
# Report progress
|
||||
await ctx.report_progress(10, 100)
|
||||
ctx.info(f"Loading schematic file: {os.path.basename(schematic_path)}")
|
||||
|
||||
try:
|
||||
# Extract netlist information
|
||||
await ctx.report_progress(20, 100)
|
||||
ctx.info("Parsing schematic structure...")
|
||||
|
||||
netlist_data = extract_netlist(schematic_path)
|
||||
|
||||
if "error" in netlist_data:
|
||||
ctx.info(f"Error extracting netlist: {netlist_data['error']}")
|
||||
return {"success": False, "error": netlist_data['error']}
|
||||
|
||||
# Analyze components and nets
|
||||
await ctx.report_progress(30, 100)
|
||||
ctx.info("Analyzing components and connections...")
|
||||
|
||||
components = netlist_data.get("components", {})
|
||||
nets = netlist_data.get("nets", {})
|
||||
|
||||
# Start pattern recognition
|
||||
await ctx.report_progress(50, 100)
|
||||
ctx.info("Identifying circuit patterns...")
|
||||
|
||||
identified_patterns = {
|
||||
"power_supply_circuits": [],
|
||||
"amplifier_circuits": [],
|
||||
"filter_circuits": [],
|
||||
"oscillator_circuits": [],
|
||||
"digital_interface_circuits": [],
|
||||
"microcontroller_circuits": [],
|
||||
"sensor_interface_circuits": [],
|
||||
"other_patterns": []
|
||||
}
|
||||
|
||||
# Identify power supply circuits
|
||||
await ctx.report_progress(60, 100)
|
||||
identified_patterns["power_supply_circuits"] = identify_power_supplies(components, nets)
|
||||
|
||||
# Identify amplifier circuits
|
||||
await ctx.report_progress(70, 100)
|
||||
identified_patterns["amplifier_circuits"] = identify_amplifiers(components, nets)
|
||||
|
||||
# Identify filter circuits
|
||||
await ctx.report_progress(75, 100)
|
||||
identified_patterns["filter_circuits"] = identify_filters(components, nets)
|
||||
|
||||
# Identify oscillator circuits
|
||||
await ctx.report_progress(80, 100)
|
||||
identified_patterns["oscillator_circuits"] = identify_oscillators(components, nets)
|
||||
|
||||
# Identify digital interface circuits
|
||||
await ctx.report_progress(85, 100)
|
||||
identified_patterns["digital_interface_circuits"] = identify_digital_interfaces(components, nets)
|
||||
|
||||
# Identify microcontroller circuits
|
||||
await ctx.report_progress(90, 100)
|
||||
identified_patterns["microcontroller_circuits"] = identify_microcontrollers(components)
|
||||
|
||||
# Identify sensor interface circuits
|
||||
await ctx.report_progress(95, 100)
|
||||
identified_patterns["sensor_interface_circuits"] = identify_sensor_interfaces(components, nets)
|
||||
|
||||
# Build result
|
||||
result = {
|
||||
"success": True,
|
||||
"schematic_path": schematic_path,
|
||||
"component_count": netlist_data["component_count"],
|
||||
"identified_patterns": identified_patterns
|
||||
}
|
||||
|
||||
# Count total patterns
|
||||
total_patterns = sum(len(patterns) for patterns in identified_patterns.values())
|
||||
result["total_patterns_found"] = total_patterns
|
||||
|
||||
# Complete progress
|
||||
await ctx.report_progress(100, 100)
|
||||
ctx.info(f"Pattern recognition complete. Found {total_patterns} circuit patterns.")
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
ctx.info(f"Error identifying circuit patterns: {str(e)}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
@mcp.tool()
|
||||
async def analyze_project_circuit_patterns(project_path: str, ctx: Context) -> Dict[str, Any]:
|
||||
"""Identify circuit patterns in a KiCad project's schematic.
|
||||
|
||||
Args:
|
||||
project_path: Path to the KiCad project file (.kicad_pro)
|
||||
ctx: MCP context for progress reporting
|
||||
|
||||
Returns:
|
||||
Dictionary with identified circuit patterns
|
||||
"""
|
||||
if not os.path.exists(project_path):
|
||||
ctx.info(f"Project not found: {project_path}")
|
||||
return {"success": False, "error": f"Project not found: {project_path}"}
|
||||
|
||||
# Report progress
|
||||
await ctx.report_progress(10, 100)
|
||||
|
||||
# Get the schematic file
|
||||
try:
|
||||
files = get_project_files(project_path)
|
||||
|
||||
if "schematic" not in files:
|
||||
ctx.info("Schematic file not found in project")
|
||||
return {"success": False, "error": "Schematic file not found in project"}
|
||||
|
||||
schematic_path = files["schematic"]
|
||||
ctx.info(f"Found schematic file: {os.path.basename(schematic_path)}")
|
||||
|
||||
# Identify patterns in the schematic
|
||||
result = await identify_circuit_patterns(schematic_path, ctx)
|
||||
|
||||
# Add project path to result
|
||||
if "success" in result and result["success"]:
|
||||
result["project_path"] = project_path
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
ctx.info(f"Error analyzing project circuit patterns: {str(e)}")
|
||||
return {"success": False, "error": str(e)}
|
433
kicad_mcp/utils/component_utils.py
Normal file
433
kicad_mcp/utils/component_utils.py
Normal file
@ -0,0 +1,433 @@
|
||||
"""
|
||||
Utility functions for working with KiCad component values and properties.
|
||||
"""
|
||||
import re
|
||||
from typing import Optional, Tuple, Union, Dict
|
||||
|
||||
def extract_voltage_from_regulator(value: str) -> str:
|
||||
"""Extract output voltage from a voltage regulator part number or description.
|
||||
|
||||
Args:
|
||||
value: Regulator part number or description
|
||||
|
||||
Returns:
|
||||
Extracted voltage as a string or "unknown" if not found
|
||||
"""
|
||||
# Common patterns:
|
||||
# 78xx/79xx series: 7805 = 5V, 7812 = 12V
|
||||
# LDOs often have voltage in the part number, like LM1117-3.3
|
||||
|
||||
# 78xx/79xx series
|
||||
match = re.search(r'78(\d\d)|79(\d\d)', value, re.IGNORECASE)
|
||||
if match:
|
||||
group = match.group(1) or match.group(2)
|
||||
# Convert code to voltage (e.g., 05 -> 5V, 12 -> 12V)
|
||||
try:
|
||||
voltage = int(group)
|
||||
# For 78xx series, voltage code is directly in volts
|
||||
if voltage < 50: # Sanity check to prevent weird values
|
||||
return f"{voltage}V"
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Look for common voltage indicators in the string
|
||||
voltage_patterns = [
|
||||
r'(\d+\.?\d*)V', # 3.3V, 5V, etc.
|
||||
r'-(\d+\.?\d*)V', # -5V, -12V, etc. (for negative regulators)
|
||||
r'(\d+\.?\d*)[_-]?V', # 3.3_V, 5-V, etc.
|
||||
r'[_-](\d+\.?\d*)', # LM1117-3.3, LD1117-3.3, etc.
|
||||
]
|
||||
|
||||
for pattern in voltage_patterns:
|
||||
match = re.search(pattern, value, re.IGNORECASE)
|
||||
if match:
|
||||
try:
|
||||
voltage = float(match.group(1))
|
||||
if 0 < voltage < 50: # Sanity check
|
||||
# Format as integer if it's a whole number
|
||||
if voltage.is_integer():
|
||||
return f"{int(voltage)}V"
|
||||
else:
|
||||
return f"{voltage}V"
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Check for common fixed voltage regulators
|
||||
regulators = {
|
||||
"LM7805": "5V",
|
||||
"LM7809": "9V",
|
||||
"LM7812": "12V",
|
||||
"LM7905": "-5V",
|
||||
"LM7912": "-12V",
|
||||
"LM1117-3.3": "3.3V",
|
||||
"LM1117-5": "5V",
|
||||
"LM317": "Adjustable",
|
||||
"LM337": "Adjustable (Negative)",
|
||||
"AP1117-3.3": "3.3V",
|
||||
"AMS1117-3.3": "3.3V",
|
||||
"L7805": "5V",
|
||||
"L7812": "12V",
|
||||
"MCP1700-3.3": "3.3V",
|
||||
"MCP1700-5.0": "5V"
|
||||
}
|
||||
|
||||
for reg, volt in regulators.items():
|
||||
if re.search(re.escape(reg), value, re.IGNORECASE):
|
||||
return volt
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
def extract_frequency_from_value(value: str) -> str:
|
||||
"""Extract frequency information from a component value or description.
|
||||
|
||||
Args:
|
||||
value: Component value or description (e.g., "16MHz", "Crystal 8MHz")
|
||||
|
||||
Returns:
|
||||
Frequency as a string or "unknown" if not found
|
||||
"""
|
||||
# Common frequency patterns with various units
|
||||
frequency_patterns = [
|
||||
r'(\d+\.?\d*)[\s-]*([kKmMgG]?)[hH][zZ]', # 16MHz, 32.768 kHz, etc.
|
||||
r'(\d+\.?\d*)[\s-]*([kKmMgG])', # 16M, 32.768k, etc.
|
||||
]
|
||||
|
||||
for pattern in frequency_patterns:
|
||||
match = re.search(pattern, value, re.IGNORECASE)
|
||||
if match:
|
||||
try:
|
||||
freq = float(match.group(1))
|
||||
unit = match.group(2).upper() if match.group(2) else ""
|
||||
|
||||
# Make sure the frequency is in a reasonable range
|
||||
if freq > 0:
|
||||
# Format the output
|
||||
if unit == "K":
|
||||
if freq >= 1000:
|
||||
return f"{freq/1000:.3f}MHz"
|
||||
else:
|
||||
return f"{freq:.3f}kHz"
|
||||
elif unit == "M":
|
||||
if freq >= 1000:
|
||||
return f"{freq/1000:.3f}GHz"
|
||||
else:
|
||||
return f"{freq:.3f}MHz"
|
||||
elif unit == "G":
|
||||
return f"{freq:.3f}GHz"
|
||||
else: # No unit, need to determine based on value
|
||||
if freq < 1000:
|
||||
return f"{freq:.3f}Hz"
|
||||
elif freq < 1000000:
|
||||
return f"{freq/1000:.3f}kHz"
|
||||
elif freq < 1000000000:
|
||||
return f"{freq/1000000:.3f}MHz"
|
||||
else:
|
||||
return f"{freq/1000000000:.3f}GHz"
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Check for common crystal frequencies
|
||||
if "32.768" in value or "32768" in value:
|
||||
return "32.768kHz" # Common RTC crystal
|
||||
elif "16M" in value or "16MHZ" in value.upper():
|
||||
return "16MHz" # Common MCU crystal
|
||||
elif "8M" in value or "8MHZ" in value.upper():
|
||||
return "8MHz"
|
||||
elif "20M" in value or "20MHZ" in value.upper():
|
||||
return "20MHz"
|
||||
elif "27M" in value or "27MHZ" in value.upper():
|
||||
return "27MHz"
|
||||
elif "25M" in value or "25MHZ" in value.upper():
|
||||
return "25MHz"
|
||||
|
||||
return "unknown"
|
||||
|
||||
|
||||
def extract_resistance_value(value: str) -> Tuple[Optional[float], Optional[str]]:
|
||||
"""Extract resistance value and unit from component value.
|
||||
|
||||
Args:
|
||||
value: Resistance value (e.g., "10k", "4.7k", "100")
|
||||
|
||||
Returns:
|
||||
Tuple of (numeric value, unit) or (None, None) if parsing fails
|
||||
"""
|
||||
# Common resistance patterns
|
||||
# 10k, 4.7k, 100R, 1M, 10, etc.
|
||||
match = re.search(r'(\d+\.?\d*)([kKmMrRΩ]?)', value)
|
||||
if match:
|
||||
try:
|
||||
resistance = float(match.group(1))
|
||||
unit = match.group(2).upper() if match.group(2) else "Ω"
|
||||
|
||||
# Normalize unit
|
||||
if unit == "R" or unit == "":
|
||||
unit = "Ω"
|
||||
|
||||
return resistance, unit
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Handle special case like "4k7" (means 4.7k)
|
||||
match = re.search(r'(\d+)[kKmM](\d+)', value)
|
||||
if match:
|
||||
try:
|
||||
value1 = int(match.group(1))
|
||||
value2 = int(match.group(2))
|
||||
resistance = float(f"{value1}.{value2}")
|
||||
unit = "k" if "k" in value.lower() else "M" if "m" in value.lower() else "Ω"
|
||||
|
||||
return resistance, unit
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def extract_capacitance_value(value: str) -> Tuple[Optional[float], Optional[str]]:
|
||||
"""Extract capacitance value and unit from component value.
|
||||
|
||||
Args:
|
||||
value: Capacitance value (e.g., "10uF", "4.7nF", "100pF")
|
||||
|
||||
Returns:
|
||||
Tuple of (numeric value, unit) or (None, None) if parsing fails
|
||||
"""
|
||||
# Common capacitance patterns
|
||||
# 10uF, 4.7nF, 100pF, etc.
|
||||
match = re.search(r'(\d+\.?\d*)([pPnNuUμF]+)', value)
|
||||
if match:
|
||||
try:
|
||||
capacitance = float(match.group(1))
|
||||
unit = match.group(2).lower()
|
||||
|
||||
# Normalize unit
|
||||
if "p" in unit or "pf" in unit:
|
||||
unit = "pF"
|
||||
elif "n" in unit or "nf" in unit:
|
||||
unit = "nF"
|
||||
elif "u" in unit or "μ" in unit or "uf" in unit or "μf" in unit:
|
||||
unit = "μF"
|
||||
else:
|
||||
unit = "F"
|
||||
|
||||
return capacitance, unit
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Handle special case like "4n7" (means 4.7nF)
|
||||
match = re.search(r'(\d+)[pPnNuUμ](\d+)', value)
|
||||
if match:
|
||||
try:
|
||||
value1 = int(match.group(1))
|
||||
value2 = int(match.group(2))
|
||||
capacitance = float(f"{value1}.{value2}")
|
||||
|
||||
if "p" in value.lower():
|
||||
unit = "pF"
|
||||
elif "n" in value.lower():
|
||||
unit = "nF"
|
||||
elif "u" in value.lower() or "μ" in value:
|
||||
unit = "μF"
|
||||
else:
|
||||
unit = "F"
|
||||
|
||||
return capacitance, unit
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def extract_inductance_value(value: str) -> Tuple[Optional[float], Optional[str]]:
|
||||
"""Extract inductance value and unit from component value.
|
||||
|
||||
Args:
|
||||
value: Inductance value (e.g., "10uH", "4.7nH", "100mH")
|
||||
|
||||
Returns:
|
||||
Tuple of (numeric value, unit) or (None, None) if parsing fails
|
||||
"""
|
||||
# Common inductance patterns
|
||||
# 10uH, 4.7nH, 100mH, etc.
|
||||
match = re.search(r'(\d+\.?\d*)([pPnNuUμmM][hH])', value)
|
||||
if match:
|
||||
try:
|
||||
inductance = float(match.group(1))
|
||||
unit = match.group(2).lower()
|
||||
|
||||
# Normalize unit
|
||||
if "p" in unit:
|
||||
unit = "pH"
|
||||
elif "n" in unit:
|
||||
unit = "nH"
|
||||
elif "u" in unit or "μ" in unit:
|
||||
unit = "μH"
|
||||
elif "m" in unit:
|
||||
unit = "mH"
|
||||
else:
|
||||
unit = "H"
|
||||
|
||||
return inductance, unit
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Handle special case like "4u7" (means 4.7uH)
|
||||
match = re.search(r'(\d+)[pPnNuUμmM](\d+)[hH]', value)
|
||||
if match:
|
||||
try:
|
||||
value1 = int(match.group(1))
|
||||
value2 = int(match.group(2))
|
||||
inductance = float(f"{value1}.{value2}")
|
||||
|
||||
if "p" in value.lower():
|
||||
unit = "pH"
|
||||
elif "n" in value.lower():
|
||||
unit = "nH"
|
||||
elif "u" in value.lower() or "μ" in value:
|
||||
unit = "μH"
|
||||
elif "m" in value.lower():
|
||||
unit = "mH"
|
||||
else:
|
||||
unit = "H"
|
||||
|
||||
return inductance, unit
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return None, None
|
||||
|
||||
|
||||
def format_resistance(resistance: float, unit: str) -> str:
|
||||
"""Format resistance value with appropriate unit.
|
||||
|
||||
Args:
|
||||
resistance: Resistance value
|
||||
unit: Unit string (Ω, k, M)
|
||||
|
||||
Returns:
|
||||
Formatted resistance string
|
||||
"""
|
||||
if unit == "Ω":
|
||||
return f"{resistance:.0f}Ω" if resistance.is_integer() else f"{resistance}Ω"
|
||||
elif unit == "k":
|
||||
return f"{resistance:.0f}kΩ" if resistance.is_integer() else f"{resistance}kΩ"
|
||||
elif unit == "M":
|
||||
return f"{resistance:.0f}MΩ" if resistance.is_integer() else f"{resistance}MΩ"
|
||||
else:
|
||||
return f"{resistance}{unit}"
|
||||
|
||||
|
||||
def format_capacitance(capacitance: float, unit: str) -> str:
|
||||
"""Format capacitance value with appropriate unit.
|
||||
|
||||
Args:
|
||||
capacitance: Capacitance value
|
||||
unit: Unit string (pF, nF, μF, F)
|
||||
|
||||
Returns:
|
||||
Formatted capacitance string
|
||||
"""
|
||||
if capacitance.is_integer():
|
||||
return f"{int(capacitance)}{unit}"
|
||||
else:
|
||||
return f"{capacitance}{unit}"
|
||||
|
||||
|
||||
def format_inductance(inductance: float, unit: str) -> str:
|
||||
"""Format inductance value with appropriate unit.
|
||||
|
||||
Args:
|
||||
inductance: Inductance value
|
||||
unit: Unit string (pH, nH, μH, mH, H)
|
||||
|
||||
Returns:
|
||||
Formatted inductance string
|
||||
"""
|
||||
if inductance.is_integer():
|
||||
return f"{int(inductance)}{unit}"
|
||||
else:
|
||||
return f"{inductance}{unit}"
|
||||
|
||||
|
||||
def normalize_component_value(value: str, component_type: str) -> str:
|
||||
"""Normalize a component value string based on component type.
|
||||
|
||||
Args:
|
||||
value: Raw component value string
|
||||
component_type: Type of component (R, C, L, etc.)
|
||||
|
||||
Returns:
|
||||
Normalized value string
|
||||
"""
|
||||
if component_type == "R":
|
||||
resistance, unit = extract_resistance_value(value)
|
||||
if resistance is not None and unit is not None:
|
||||
return format_resistance(resistance, unit)
|
||||
elif component_type == "C":
|
||||
capacitance, unit = extract_capacitance_value(value)
|
||||
if capacitance is not None and unit is not None:
|
||||
return format_capacitance(capacitance, unit)
|
||||
elif component_type == "L":
|
||||
inductance, unit = extract_inductance_value(value)
|
||||
if inductance is not None and unit is not None:
|
||||
return format_inductance(inductance, unit)
|
||||
|
||||
# For other component types or if parsing fails, return the original value
|
||||
return value
|
||||
|
||||
|
||||
def get_component_type_from_reference(reference: str) -> str:
|
||||
"""Determine component type from reference designator.
|
||||
|
||||
Args:
|
||||
reference: Component reference (e.g., R1, C2, U3)
|
||||
|
||||
Returns:
|
||||
Component type letter (R, C, L, Q, etc.)
|
||||
"""
|
||||
# Extract the alphabetic prefix (component type)
|
||||
match = re.match(r'^([A-Za-z_]+)', reference)
|
||||
if match:
|
||||
return match.group(1)
|
||||
return ""
|
||||
|
||||
|
||||
def is_power_component(component: Dict[str, Any]) -> bool:
|
||||
"""Check if a component is likely a power-related component.
|
||||
|
||||
Args:
|
||||
component: Component information dictionary
|
||||
|
||||
Returns:
|
||||
True if the component is power-related, False otherwise
|
||||
"""
|
||||
ref = component.get("reference", "")
|
||||
value = component.get("value", "").upper()
|
||||
lib_id = component.get("lib_id", "").upper()
|
||||
|
||||
# Check reference designator
|
||||
if ref.startswith(("VR", "PS", "REG")):
|
||||
return True
|
||||
|
||||
# Check for power-related terms in value or library ID
|
||||
power_terms = ["VCC", "VDD", "GND", "POWER", "PWR", "SUPPLY", "REGULATOR", "LDO"]
|
||||
if any(term in value or term in lib_id for term in power_terms):
|
||||
return True
|
||||
|
||||
# Check for regulator part numbers
|
||||
regulator_patterns = [
|
||||
r"78\d\d", # 7805, 7812, etc.
|
||||
r"79\d\d", # 7905, 7912, etc.
|
||||
r"LM\d{3}", # LM317, LM337, etc.
|
||||
r"LM\d{4}", # LM1117, etc.
|
||||
r"AMS\d{4}", # AMS1117, etc.
|
||||
r"MCP\d{4}", # MCP1700, etc.
|
||||
]
|
||||
|
||||
if any(re.search(pattern, value, re.IGNORECASE) for pattern in regulator_patterns):
|
||||
return True
|
||||
|
||||
# Not identified as a power component
|
||||
return False
|
859
kicad_mcp/utils/pattern_recognition.py
Normal file
859
kicad_mcp/utils/pattern_recognition.py
Normal file
@ -0,0 +1,859 @@
|
||||
"""
|
||||
Circuit pattern recognition functions for KiCad schematics.
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Dict, List, Any
|
||||
from kicad_mcp.utils.component_utils import extract_voltage_from_regulator, extract_frequency_from_value
|
||||
|
||||
def identify_power_supplies(components: Dict[str, Any], nets: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Identify power supply circuits in the schematic.
|
||||
|
||||
Args:
|
||||
components: Dictionary of components from netlist
|
||||
nets: Dictionary of nets from netlist
|
||||
|
||||
Returns:
|
||||
List of identified power supply circuits
|
||||
"""
|
||||
power_supplies = []
|
||||
|
||||
# Look for voltage regulators (Linear)
|
||||
regulator_patterns = {
|
||||
"78xx": r"78\d\d|LM78\d\d|MC78\d\d", # 7805, 7812, etc.
|
||||
"79xx": r"79\d\d|LM79\d\d|MC79\d\d", # 7905, 7912, etc.
|
||||
"LDO": r"LM\d{3}|LD\d{3}|AMS\d{4}|LT\d{4}|TLV\d{3}|AP\d{4}|MIC\d{4}|NCP\d{3}|LP\d{4}|L\d{2}|TPS\d{5}"
|
||||
}
|
||||
|
||||
for ref, component in components.items():
|
||||
# Check for voltage regulators by part value or lib_id
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
for reg_type, pattern in regulator_patterns.items():
|
||||
if re.search(pattern, component_value, re.IGNORECASE) or re.search(pattern, component_lib, re.IGNORECASE):
|
||||
# Found a regulator, look for associated components
|
||||
power_supplies.append({
|
||||
"type": "linear_regulator",
|
||||
"subtype": reg_type,
|
||||
"main_component": ref,
|
||||
"value": component_value,
|
||||
"input_voltage": "unknown", # Would need more analysis to determine
|
||||
"output_voltage": extract_voltage_from_regulator(component_value),
|
||||
"associated_components": [] # Would need connection analysis to find these
|
||||
})
|
||||
|
||||
# Look for switching regulators
|
||||
switching_patterns = {
|
||||
"buck": r"LM\d{4}|TPS\d{4}|MP\d{4}|RT\d{4}|LT\d{4}|MC\d{4}|NCP\d{4}|TL\d{4}|LTC\d{4}",
|
||||
"boost": r"MC\d{4}|LT\d{4}|TPS\d{4}|MAX\d{4}|NCP\d{4}|LTC\d{4}",
|
||||
"buck_boost": r"LTC\d{4}|LM\d{4}|TPS\d{4}|MAX\d{4}"
|
||||
}
|
||||
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
# Check for inductor (key component in switching supplies)
|
||||
if ref.startswith('L') or 'Inductor' in component_lib:
|
||||
# Look for nearby ICs that might be switching controllers
|
||||
for ic_ref, ic_component in components.items():
|
||||
if ic_ref.startswith('U') or ic_ref.startswith('IC'):
|
||||
ic_value = ic_component.get('value', '').upper()
|
||||
ic_lib = ic_component.get('lib_id', '').upper()
|
||||
|
||||
for converter_type, pattern in switching_patterns.items():
|
||||
if re.search(pattern, ic_value, re.IGNORECASE) or re.search(pattern, ic_lib, re.IGNORECASE):
|
||||
power_supplies.append({
|
||||
"type": "switching_regulator",
|
||||
"subtype": converter_type,
|
||||
"main_component": ic_ref,
|
||||
"inductor": ref,
|
||||
"value": ic_value
|
||||
})
|
||||
|
||||
return power_supplies
|
||||
|
||||
|
||||
def identify_amplifiers(components: Dict[str, Any], nets: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Identify amplifier circuits in the schematic.
|
||||
|
||||
Args:
|
||||
components: Dictionary of components from netlist
|
||||
nets: Dictionary of nets from netlist
|
||||
|
||||
Returns:
|
||||
List of identified amplifier circuits
|
||||
"""
|
||||
amplifiers = []
|
||||
|
||||
# Look for op-amps
|
||||
opamp_patterns = [
|
||||
r"LM\d{3}|TL\d{3}|NE\d{3}|LF\d{3}|OP\d{2}|MCP\d{3}|AD\d{3}|LT\d{4}|OPA\d{3}",
|
||||
r"Opamp|Op-Amp|OpAmp|Operational Amplifier"
|
||||
]
|
||||
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
# Check for op-amps
|
||||
for pattern in opamp_patterns:
|
||||
if re.search(pattern, component_value, re.IGNORECASE) or re.search(pattern, component_lib, re.IGNORECASE):
|
||||
# Common op-amps
|
||||
if re.search(r"LM358|LM324|TL072|TL082|NE5532|LF353|MCP6002|AD8620|OPA2134", component_value, re.IGNORECASE):
|
||||
amplifiers.append({
|
||||
"type": "operational_amplifier",
|
||||
"subtype": "general_purpose",
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
# Audio op-amps
|
||||
elif re.search(r"NE5534|OPA134|OPA1612|OPA1652|LM4562|LME49720|LME49860|TL071|TL072", component_value, re.IGNORECASE):
|
||||
amplifiers.append({
|
||||
"type": "operational_amplifier",
|
||||
"subtype": "audio",
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
# Instrumentation amplifiers
|
||||
elif re.search(r"INA\d{3}|AD620|AD8221|AD8429|LT1167", component_value, re.IGNORECASE):
|
||||
amplifiers.append({
|
||||
"type": "operational_amplifier",
|
||||
"subtype": "instrumentation",
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
else:
|
||||
amplifiers.append({
|
||||
"type": "operational_amplifier",
|
||||
"subtype": "unknown",
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
|
||||
# Look for transistor amplifiers
|
||||
transistor_refs = [ref for ref in components.keys() if ref.startswith('Q')]
|
||||
|
||||
for ref in transistor_refs:
|
||||
component = components[ref]
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
# Check if it's a BJT or FET
|
||||
if 'BJT' in component_lib or 'NPN' in component_lib or 'PNP' in component_lib:
|
||||
# Look for resistors connected to transistor (biasing network)
|
||||
has_biasing = False
|
||||
for net_name, pins in nets.items():
|
||||
# Check if this net connects to our transistor
|
||||
if any(pin.get('component') == ref for pin in pins):
|
||||
# Check if the net also connects to resistors
|
||||
if any(pin.get('component', '').startswith('R') for pin in pins):
|
||||
has_biasing = True
|
||||
break
|
||||
|
||||
if has_biasing:
|
||||
amplifiers.append({
|
||||
"type": "transistor_amplifier",
|
||||
"subtype": "BJT",
|
||||
"component": ref,
|
||||
"value": component.get('value', '')
|
||||
})
|
||||
|
||||
elif 'FET' in component_lib or 'MOSFET' in component_lib or 'JFET' in component_lib:
|
||||
# Similar check for FET amplifiers
|
||||
has_biasing = False
|
||||
for net_name, pins in nets.items():
|
||||
if any(pin.get('component') == ref for pin in pins):
|
||||
if any(pin.get('component', '').startswith('R') for pin in pins):
|
||||
has_biasing = True
|
||||
break
|
||||
|
||||
if has_biasing:
|
||||
amplifiers.append({
|
||||
"type": "transistor_amplifier",
|
||||
"subtype": "FET",
|
||||
"component": ref,
|
||||
"value": component.get('value', '')
|
||||
})
|
||||
|
||||
# Look for audio amplifier ICs
|
||||
audio_amp_patterns = [
|
||||
r"LM386|LM383|LM380|LM1875|LM3886|TDA\d{4}|TPA\d{4}|SSM\d{4}|PAM\d{4}|TAS\d{4}"
|
||||
]
|
||||
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
for pattern in audio_amp_patterns:
|
||||
if re.search(pattern, component_value, re.IGNORECASE) or re.search(pattern, component_lib, re.IGNORECASE):
|
||||
amplifiers.append({
|
||||
"type": "audio_amplifier_ic",
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
|
||||
return amplifiers
|
||||
|
||||
|
||||
def identify_filters(components: Dict[str, Any], nets: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Identify filter circuits in the schematic.
|
||||
|
||||
Args:
|
||||
components: Dictionary of components from netlist
|
||||
nets: Dictionary of nets from netlist
|
||||
|
||||
Returns:
|
||||
List of identified filter circuits
|
||||
"""
|
||||
filters = []
|
||||
|
||||
# Look for RC low-pass filters
|
||||
# These typically have a resistor followed by a capacitor to ground
|
||||
resistor_refs = [ref for ref in components.keys() if ref.startswith('R')]
|
||||
capacitor_refs = [ref for ref in components.keys() if ref.startswith('C')]
|
||||
|
||||
for r_ref in resistor_refs:
|
||||
r_nets = []
|
||||
# Find which nets this resistor is connected to
|
||||
for net_name, pins in nets.items():
|
||||
if any(pin.get('component') == r_ref for pin in pins):
|
||||
r_nets.append(net_name)
|
||||
|
||||
# For each net, check if there's a capacitor connected to it
|
||||
for net_name in r_nets:
|
||||
# Find capacitors connected to this net
|
||||
connected_caps = []
|
||||
for pin in nets.get(net_name, []):
|
||||
comp = pin.get('component')
|
||||
if comp and comp.startswith('C'):
|
||||
connected_caps.append(comp)
|
||||
|
||||
if connected_caps:
|
||||
# Check if the other side of the capacitor goes to ground
|
||||
for c_ref in connected_caps:
|
||||
c_is_to_ground = False
|
||||
for gnd_name in ['GND', 'AGND', 'DGND', 'VSS']:
|
||||
for pin in nets.get(gnd_name, []):
|
||||
if pin.get('component') == c_ref:
|
||||
c_is_to_ground = True
|
||||
break
|
||||
if c_is_to_ground:
|
||||
break
|
||||
|
||||
if c_is_to_ground:
|
||||
filters.append({
|
||||
"type": "passive_filter",
|
||||
"subtype": "rc_low_pass",
|
||||
"components": [r_ref, c_ref]
|
||||
})
|
||||
|
||||
# Look for active filters (op-amp with feedback RC components)
|
||||
opamp_refs = []
|
||||
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
if re.search(r"LM\d{3}|TL\d{3}|NE\d{3}|LF\d{3}|OP\d{2}|MCP\d{3}|AD\d{3}|LT\d{4}|OPA\d{3}",
|
||||
component_value, re.IGNORECASE) or "OP_AMP" in component_lib:
|
||||
opamp_refs.append(ref)
|
||||
|
||||
for op_ref in opamp_refs:
|
||||
# Find op-amp output
|
||||
# In a full implementation, we'd know which pin is the output
|
||||
# For simplicity, we'll look for feedback components
|
||||
has_feedback_r = False
|
||||
has_feedback_c = False
|
||||
|
||||
for net_name, pins in nets.items():
|
||||
# If this net connects to our op-amp
|
||||
if any(pin.get('component') == op_ref for pin in pins):
|
||||
# Check if it also connects to resistors and capacitors
|
||||
connects_to_r = any(pin.get('component', '').startswith('R') for pin in pins)
|
||||
connects_to_c = any(pin.get('component', '').startswith('C') for pin in pins)
|
||||
|
||||
if connects_to_r:
|
||||
has_feedback_r = True
|
||||
if connects_to_c:
|
||||
has_feedback_c = True
|
||||
|
||||
if has_feedback_r and has_feedback_c:
|
||||
filters.append({
|
||||
"type": "active_filter",
|
||||
"main_component": op_ref,
|
||||
"value": components[op_ref].get('value', '')
|
||||
})
|
||||
|
||||
# Look for crystal filters or ceramic filters
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
if ref.startswith('Y') or ref.startswith('X') or "CRYSTAL" in component_lib or "XTAL" in component_lib:
|
||||
filters.append({
|
||||
"type": "crystal_filter",
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
|
||||
if "FILTER" in component_lib or "MURATA" in component_lib or "CERAMIC_FILTER" in component_lib:
|
||||
filters.append({
|
||||
"type": "ceramic_filter",
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
|
||||
return filters
|
||||
|
||||
|
||||
def identify_oscillators(components: Dict[str, Any], nets: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Identify oscillator circuits in the schematic.
|
||||
|
||||
Args:
|
||||
components: Dictionary of components from netlist
|
||||
nets: Dictionary of nets from netlist
|
||||
|
||||
Returns:
|
||||
List of identified oscillator circuits
|
||||
"""
|
||||
oscillators = []
|
||||
|
||||
# Look for crystal oscillators
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
# Crystals
|
||||
if ref.startswith('Y') or ref.startswith('X') or "CRYSTAL" in component_lib or "XTAL" in component_lib:
|
||||
# Check if the crystal has load capacitors
|
||||
has_load_caps = False
|
||||
crystal_nets = []
|
||||
|
||||
for net_name, pins in nets.items():
|
||||
if any(pin.get('component') == ref for pin in pins):
|
||||
crystal_nets.append(net_name)
|
||||
|
||||
# Look for capacitors connected to the crystal nets
|
||||
for net_name in crystal_nets:
|
||||
for pin in nets.get(net_name, []):
|
||||
comp = pin.get('component')
|
||||
if comp and comp.startswith('C'):
|
||||
has_load_caps = True
|
||||
break
|
||||
if has_load_caps:
|
||||
break
|
||||
|
||||
oscillators.append({
|
||||
"type": "crystal_oscillator",
|
||||
"component": ref,
|
||||
"value": component_value,
|
||||
"frequency": extract_frequency_from_value(component_value),
|
||||
"has_load_capacitors": has_load_caps
|
||||
})
|
||||
|
||||
# Oscillator ICs
|
||||
if "OSC" in component_lib or "OSCILLATOR" in component_lib or re.search(r"OSC|OSCILLATOR", component_value, re.IGNORECASE):
|
||||
oscillators.append({
|
||||
"type": "oscillator_ic",
|
||||
"component": ref,
|
||||
"value": component_value,
|
||||
"frequency": extract_frequency_from_value(component_value)
|
||||
})
|
||||
|
||||
# RC oscillators (555 timer, etc)
|
||||
if re.search(r"NE555|LM555|ICM7555|TLC555", component_value, re.IGNORECASE) or "555" in component_lib:
|
||||
oscillators.append({
|
||||
"type": "rc_oscillator",
|
||||
"subtype": "555_timer",
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
|
||||
return oscillators
|
||||
|
||||
|
||||
def identify_digital_interfaces(components: Dict[str, Any], nets: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Identify digital interface circuits in the schematic.
|
||||
|
||||
Args:
|
||||
components: Dictionary of components from netlist
|
||||
nets: Dictionary of nets from netlist
|
||||
|
||||
Returns:
|
||||
List of identified digital interface circuits
|
||||
"""
|
||||
interfaces = []
|
||||
|
||||
# I2C interface detection
|
||||
i2c_signals = {"SCL", "SDA", "I2C_SCL", "I2C_SDA"}
|
||||
has_i2c = False
|
||||
|
||||
for net_name in nets.keys():
|
||||
if any(signal in net_name.upper() for signal in i2c_signals):
|
||||
has_i2c = True
|
||||
break
|
||||
|
||||
if has_i2c:
|
||||
interfaces.append({
|
||||
"type": "i2c_interface",
|
||||
"signals_found": [net for net in nets.keys() if any(signal in net.upper() for signal in i2c_signals)]
|
||||
})
|
||||
|
||||
# SPI interface detection
|
||||
spi_signals = {"MOSI", "MISO", "SCK", "SS", "SPI_MOSI", "SPI_MISO", "SPI_SCK", "SPI_CS"}
|
||||
has_spi = False
|
||||
|
||||
for net_name in nets.keys():
|
||||
if any(signal in net_name.upper() for signal in spi_signals):
|
||||
has_spi = True
|
||||
break
|
||||
|
||||
if has_spi:
|
||||
interfaces.append({
|
||||
"type": "spi_interface",
|
||||
"signals_found": [net for net in nets.keys() if any(signal in net.upper() for signal in spi_signals)]
|
||||
})
|
||||
|
||||
# UART interface detection
|
||||
uart_signals = {"TX", "RX", "TXD", "RXD", "UART_TX", "UART_RX"}
|
||||
has_uart = False
|
||||
|
||||
for net_name in nets.keys():
|
||||
if any(signal in net_name.upper() for signal in uart_signals):
|
||||
has_uart = True
|
||||
break
|
||||
|
||||
if has_uart:
|
||||
interfaces.append({
|
||||
"type": "uart_interface",
|
||||
"signals_found": [net for net in nets.keys() if any(signal in net.upper() for signal in uart_signals)]
|
||||
})
|
||||
|
||||
# USB interface detection
|
||||
usb_signals = {"USB_D+", "USB_D-", "USB_DP", "USB_DM", "D+", "D-", "DP", "DM", "VBUS"}
|
||||
has_usb = False
|
||||
|
||||
for net_name in nets.keys():
|
||||
if any(signal in net_name.upper() for signal in usb_signals):
|
||||
has_usb = True
|
||||
break
|
||||
|
||||
# Also check for USB interface ICs
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
if re.search(r"FT232|CH340|CP210|MCP2200|TUSB|FT231|FT201", component_value, re.IGNORECASE):
|
||||
has_usb = True
|
||||
break
|
||||
|
||||
if has_usb:
|
||||
interfaces.append({
|
||||
"type": "usb_interface",
|
||||
"signals_found": [net for net in nets.keys() if any(signal in net.upper() for signal in usb_signals)]
|
||||
})
|
||||
|
||||
# Ethernet interface detection
|
||||
ethernet_signals = {"TX+", "TX-", "RX+", "RX-", "MDI", "MDIO", "ETH"}
|
||||
has_ethernet = False
|
||||
|
||||
for net_name in nets.keys():
|
||||
if any(signal in net_name.upper() for signal in ethernet_signals):
|
||||
has_ethernet = True
|
||||
break
|
||||
|
||||
# Also check for Ethernet PHY ICs
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
if re.search(r"W5500|ENC28J60|LAN87|KSZ80|DP83|RTL8|AX88", component_value, re.IGNORECASE):
|
||||
has_ethernet = True
|
||||
break
|
||||
|
||||
if has_ethernet:
|
||||
interfaces.append({
|
||||
"type": "ethernet_interface",
|
||||
"signals_found": [net for net in nets.keys() if any(signal in net.upper() for signal in ethernet_signals)]
|
||||
})
|
||||
|
||||
return interfaces
|
||||
|
||||
|
||||
def identify_sensor_interfaces(components: Dict[str, Any], nets: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Identify sensor interface circuits in the schematic.
|
||||
|
||||
Args:
|
||||
components: Dictionary of components from netlist
|
||||
nets: Dictionary of nets from netlist
|
||||
|
||||
Returns:
|
||||
List of identified sensor interface circuits
|
||||
"""
|
||||
sensor_interfaces = []
|
||||
|
||||
# Common sensor IC patterns
|
||||
sensor_patterns = {
|
||||
"temperature": r"LM35|DS18B20|DHT11|DHT22|BME280|BMP280|TMP\d+|MCP9808|MAX31855|MAX6675|SI7021|HTU21|SHT[0123]\d|PCT2075",
|
||||
"humidity": r"DHT11|DHT22|BME280|SI7021|HTU21|SHT[0123]\d|HDC1080",
|
||||
"pressure": r"BMP\d+|BME280|LPS\d+|MS5611|DPS310|MPL3115|SPL06",
|
||||
"accelerometer": r"ADXL\d+|LIS3DH|MMA\d+|MPU\d+|LSM\d+|BMI\d+|BMA\d+|KX\d+",
|
||||
"gyroscope": r"L3G\d+|MPU\d+|BMI\d+|LSM\d+|ICM\d+",
|
||||
"magnetometer": r"HMC\d+|QMC\d+|LSM\d+|MMC\d+|RM\d+",
|
||||
"proximity": r"APDS9960|VL53L0X|VL6180|GP2Y|VCNL4040|VCNL4010",
|
||||
"light": r"BH1750|TSL\d+|MAX4\d+|VEML\d+|APDS9960|LTR329|OPT\d+",
|
||||
"air_quality": r"CCS811|BME680|SGP\d+|SEN\d+|MQ\d+|MiCS",
|
||||
"current": r"ACS\d+|INA\d+|MAX\d+|ZXCT\d+",
|
||||
"voltage": r"INA\d+|MCP\d+|ADS\d+",
|
||||
"ADC": r"ADS\d+|MCP33\d+|MCP32\d+|LTC\d+|NAU7802|HX711",
|
||||
"GPS": r"NEO-[67]M|L80|MTK\d+|SIM\d+|SAM-M8Q|MAX-M8"
|
||||
}
|
||||
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
for sensor_type, pattern in sensor_patterns.items():
|
||||
if re.search(pattern, component_value, re.IGNORECASE) or re.search(pattern, component_lib, re.IGNORECASE):
|
||||
# Identify specific sensors
|
||||
|
||||
# Temperature sensors
|
||||
if sensor_type == "temperature":
|
||||
if re.search(r"DS18B20", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "temperature_sensor",
|
||||
"model": "DS18B20",
|
||||
"component": ref,
|
||||
"interface": "1-Wire",
|
||||
"range": "-55°C to +125°C"
|
||||
})
|
||||
elif re.search(r"BME280|BMP280", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "multi_sensor",
|
||||
"model": component_value,
|
||||
"component": ref,
|
||||
"measures": ["temperature", "pressure", "humidity" if "BME" in component_value else "pressure"],
|
||||
"interface": "I2C/SPI"
|
||||
})
|
||||
elif re.search(r"LM35", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "temperature_sensor",
|
||||
"model": "LM35",
|
||||
"component": ref,
|
||||
"interface": "Analog",
|
||||
"range": "0°C to +100°C"
|
||||
})
|
||||
else:
|
||||
sensor_interfaces.append({
|
||||
"type": "temperature_sensor",
|
||||
"model": component_value,
|
||||
"component": ref
|
||||
})
|
||||
|
||||
# Motion sensors (accelerometer, gyroscope, etc.)
|
||||
elif sensor_type in ["accelerometer", "gyroscope"]:
|
||||
if re.search(r"MPU6050", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "motion_sensor",
|
||||
"model": "MPU6050",
|
||||
"component": ref,
|
||||
"measures": ["accelerometer", "gyroscope"],
|
||||
"interface": "I2C"
|
||||
})
|
||||
elif re.search(r"MPU9250", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "motion_sensor",
|
||||
"model": "MPU9250",
|
||||
"component": ref,
|
||||
"measures": ["accelerometer", "gyroscope", "magnetometer"],
|
||||
"interface": "I2C/SPI"
|
||||
})
|
||||
elif re.search(r"LSM6DS3", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "motion_sensor",
|
||||
"model": "LSM6DS3",
|
||||
"component": ref,
|
||||
"measures": ["accelerometer", "gyroscope"],
|
||||
"interface": "I2C/SPI"
|
||||
})
|
||||
else:
|
||||
sensor_interfaces.append({
|
||||
"type": "motion_sensor",
|
||||
"model": component_value,
|
||||
"component": ref,
|
||||
"measures": [sensor_type]
|
||||
})
|
||||
|
||||
# Light and proximity sensors
|
||||
elif sensor_type in ["light", "proximity"]:
|
||||
if re.search(r"APDS9960", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "optical_sensor",
|
||||
"model": "APDS9960",
|
||||
"component": ref,
|
||||
"measures": ["proximity", "light", "gesture", "color"],
|
||||
"interface": "I2C"
|
||||
})
|
||||
elif re.search(r"VL53L0X", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "optical_sensor",
|
||||
"model": "VL53L0X",
|
||||
"component": ref,
|
||||
"measures": ["time-of-flight distance"],
|
||||
"interface": "I2C",
|
||||
"range": "Up to 2m"
|
||||
})
|
||||
elif re.search(r"BH1750", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "optical_sensor",
|
||||
"model": "BH1750",
|
||||
"component": ref,
|
||||
"measures": ["ambient light"],
|
||||
"interface": "I2C"
|
||||
})
|
||||
else:
|
||||
sensor_interfaces.append({
|
||||
"type": "optical_sensor",
|
||||
"model": component_value,
|
||||
"component": ref,
|
||||
"measures": [sensor_type]
|
||||
})
|
||||
|
||||
# ADCs (often used for sensor interfaces)
|
||||
elif sensor_type == "ADC":
|
||||
if re.search(r"ADS1115", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "analog_interface",
|
||||
"model": "ADS1115",
|
||||
"component": ref,
|
||||
"resolution": "16-bit",
|
||||
"channels": 4,
|
||||
"interface": "I2C"
|
||||
})
|
||||
elif re.search(r"HX711", component_value, re.IGNORECASE):
|
||||
sensor_interfaces.append({
|
||||
"type": "analog_interface",
|
||||
"model": "HX711",
|
||||
"component": ref,
|
||||
"resolution": "24-bit",
|
||||
"common_usage": "Load cell/strain gauge",
|
||||
"interface": "Digital"
|
||||
})
|
||||
else:
|
||||
sensor_interfaces.append({
|
||||
"type": "analog_interface",
|
||||
"model": component_value,
|
||||
"component": ref
|
||||
})
|
||||
|
||||
# Other types of sensors
|
||||
else:
|
||||
sensor_interfaces.append({
|
||||
"type": f"{sensor_type}_sensor",
|
||||
"model": component_value,
|
||||
"component": ref
|
||||
})
|
||||
|
||||
# Once identified a component as a specific sensor, no need to check other types
|
||||
break
|
||||
|
||||
# Look for common analog sensors
|
||||
# These often don't have specific ICs but have designators like "RT" for thermistors
|
||||
thermistor_refs = [ref for ref in components.keys() if ref.startswith('RT') or ref.startswith('TH')]
|
||||
for ref in thermistor_refs:
|
||||
component = components[ref]
|
||||
sensor_interfaces.append({
|
||||
"type": "temperature_sensor",
|
||||
"subtype": "thermistor",
|
||||
"component": ref,
|
||||
"value": component.get('value', ''),
|
||||
"interface": "Analog"
|
||||
})
|
||||
|
||||
# Look for photodiodes, photoresistors (LDRs)
|
||||
photosensor_refs = [ref for ref in components.keys() if ref.startswith('PD') or ref.startswith('LDR')]
|
||||
for ref in photosensor_refs:
|
||||
component = components[ref]
|
||||
sensor_interfaces.append({
|
||||
"type": "optical_sensor",
|
||||
"subtype": "photosensor",
|
||||
"component": ref,
|
||||
"value": component.get('value', ''),
|
||||
"interface": "Analog"
|
||||
})
|
||||
|
||||
# Look for potentiometers (often used for manual sensing/control)
|
||||
pot_refs = [ref for ref in components.keys() if ref.startswith('RV') or ref.startswith('POT')]
|
||||
for ref in pot_refs:
|
||||
component = components[ref]
|
||||
sensor_interfaces.append({
|
||||
"type": "position_sensor",
|
||||
"subtype": "potentiometer",
|
||||
"component": ref,
|
||||
"value": component.get('value', ''),
|
||||
"interface": "Analog"
|
||||
})
|
||||
|
||||
return sensor_interfaces
|
||||
|
||||
|
||||
def identify_microcontrollers(components: Dict[str, Any]) -> List[Dict[str, Any]]:
|
||||
"""Identify microcontroller circuits in the schematic.
|
||||
|
||||
Args:
|
||||
components: Dictionary of components from netlist
|
||||
|
||||
Returns:
|
||||
List of identified microcontroller circuits
|
||||
"""
|
||||
microcontrollers = []
|
||||
|
||||
# Common microcontroller families
|
||||
mcu_patterns = {
|
||||
"AVR": r"ATMEGA\d+|ATTINY\d+|AT90\w+",
|
||||
"STM32": r"STM32\w+",
|
||||
"PIC": r"PIC\d+\w+",
|
||||
"ESP": r"ESP32|ESP8266",
|
||||
"Arduino": r"ARDUINO",
|
||||
"MSP430": r"MSP430\w+",
|
||||
"RP2040": r"RP2040|PICO",
|
||||
"NXP": r"LPC\d+|IMXRT\d+|MK\d+",
|
||||
"SAM": r"SAMD\d+|SAM\w+",
|
||||
"ARM Cortex": r"CORTEX|ARM",
|
||||
"8051": r"8051|AT89"
|
||||
}
|
||||
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
for family, pattern in mcu_patterns.items():
|
||||
if re.search(pattern, component_value, re.IGNORECASE) or re.search(pattern, component_lib, re.IGNORECASE):
|
||||
# Identify specific models
|
||||
identified = False
|
||||
|
||||
# ATmega328P (Arduino Uno/Nano)
|
||||
if re.search(r"ATMEGA328P|ATMEGA328", component_value, re.IGNORECASE):
|
||||
microcontrollers.append({
|
||||
"type": "microcontroller",
|
||||
"family": "AVR",
|
||||
"model": "ATmega328P",
|
||||
"component": ref,
|
||||
"common_usage": "Arduino Uno/Nano compatible"
|
||||
})
|
||||
identified = True
|
||||
|
||||
# ATmega32U4 (Arduino Leonardo/Micro)
|
||||
elif re.search(r"ATMEGA32U4", component_value, re.IGNORECASE):
|
||||
microcontrollers.append({
|
||||
"type": "microcontroller",
|
||||
"family": "AVR",
|
||||
"model": "ATmega32U4",
|
||||
"component": ref,
|
||||
"common_usage": "Arduino Leonardo/Micro compatible"
|
||||
})
|
||||
identified = True
|
||||
|
||||
# ESP32
|
||||
elif re.search(r"ESP32", component_value, re.IGNORECASE):
|
||||
microcontrollers.append({
|
||||
"type": "microcontroller",
|
||||
"family": "ESP",
|
||||
"model": "ESP32",
|
||||
"component": ref,
|
||||
"features": "Wi-Fi & Bluetooth"
|
||||
})
|
||||
identified = True
|
||||
|
||||
# ESP8266
|
||||
elif re.search(r"ESP8266", component_value, re.IGNORECASE):
|
||||
microcontrollers.append({
|
||||
"type": "microcontroller",
|
||||
"family": "ESP",
|
||||
"model": "ESP8266",
|
||||
"component": ref,
|
||||
"features": "Wi-Fi"
|
||||
})
|
||||
identified = True
|
||||
|
||||
# STM32 series
|
||||
elif re.search(r"STM32F\d+", component_value, re.IGNORECASE):
|
||||
model = re.search(r"(STM32F\d+)", component_value, re.IGNORECASE).group(1)
|
||||
microcontrollers.append({
|
||||
"type": "microcontroller",
|
||||
"family": "STM32",
|
||||
"model": model.upper(),
|
||||
"component": ref,
|
||||
"features": "ARM Cortex-M"
|
||||
})
|
||||
identified = True
|
||||
|
||||
# Raspberry Pi Pico (RP2040)
|
||||
elif re.search(r"RP2040|PICO", component_value, re.IGNORECASE):
|
||||
microcontrollers.append({
|
||||
"type": "microcontroller",
|
||||
"family": "RP2040",
|
||||
"model": "RP2040",
|
||||
"component": ref,
|
||||
"common_usage": "Raspberry Pi Pico"
|
||||
})
|
||||
identified = True
|
||||
|
||||
# PIC microcontrollers
|
||||
elif re.search(r"PIC\d+", component_value, re.IGNORECASE):
|
||||
model = re.search(r"(PIC\d+\w+)", component_value, re.IGNORECASE)
|
||||
if model:
|
||||
microcontrollers.append({
|
||||
"type": "microcontroller",
|
||||
"family": "PIC",
|
||||
"model": model.group(1).upper(),
|
||||
"component": ref
|
||||
})
|
||||
identified = True
|
||||
|
||||
# MSP430 series
|
||||
elif re.search(r"MSP430\w+", component_value, re.IGNORECASE):
|
||||
model = re.search(r"(MSP430\w+)", component_value, re.IGNORECASE)
|
||||
if model:
|
||||
microcontrollers.append({
|
||||
"type": "microcontroller",
|
||||
"family": "MSP430",
|
||||
"model": model.group(1).upper(),
|
||||
"component": ref,
|
||||
"features": "Ultra-low power"
|
||||
})
|
||||
identified = True
|
||||
|
||||
# If not identified specifically but matches a family
|
||||
if not identified:
|
||||
microcontrollers.append({
|
||||
"type": "microcontroller",
|
||||
"family": family,
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
|
||||
# Once identified a component as a microcontroller, no need to check other families
|
||||
break
|
||||
|
||||
# Look for microcontroller development boards
|
||||
dev_board_patterns = {
|
||||
"Arduino": r"ARDUINO|UNO|NANO|MEGA|LEONARDO|DUE",
|
||||
"ESP32 Dev Board": r"ESP32-DEVKIT|NODEMCU-32S|ESP-WROOM-32",
|
||||
"ESP8266 Dev Board": r"NODEMCU|WEMOS|D1_MINI|ESP-01",
|
||||
"STM32 Dev Board": r"NUCLEO|DISCOVERY|BLUEPILL",
|
||||
"Raspberry Pi": r"RASPBERRY|RPI|RPICO|PICO"
|
||||
}
|
||||
|
||||
for ref, component in components.items():
|
||||
component_value = component.get('value', '').upper()
|
||||
component_lib = component.get('lib_id', '').upper()
|
||||
|
||||
for board_type, pattern in dev_board_patterns.items():
|
||||
if re.search(pattern, component_value, re.IGNORECASE) or re.search(pattern, component_lib, re.IGNORECASE):
|
||||
microcontrollers.append({
|
||||
"type": "development_board",
|
||||
"board_type": board_type,
|
||||
"component": ref,
|
||||
"value": component_value
|
||||
})
|
||||
break
|
||||
|
||||
return microcontrollers
|
Loading…
x
Reference in New Issue
Block a user