
Datasheet Interpreter
- 79 installs
- 19 repo stars
- Updated May 26, 2026
- wedsamuel1230/arduino-skills
Helps with ai & agent building tasks.
About
datasheet-interpreter is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- datasheet-interpreter
- AI & Agent Building
- AI-coding skill
Datasheet Interpreter by the numbers
- 79 all-time installs (skills.sh)
- +4 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #5,292 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/wedsamuel1230/arduino-skills --skill datasheet-interpreterAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 79 |
|---|---|
| repo stars | ★ 19 |
| Last updated | May 26, 2026 |
| Repository | wedsamuel1230/arduino-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Datasheet Interpreter
Extracts actionable specifications from component datasheet PDFs.
Resources
Scripts
- scripts/extract_specs.py - Downloads datasheet PDFs from URLs and extracts specs using pattern matching
References
- references/common-parameters.md - Guide to understanding datasheet parameters
Quick Start
# Extract specs from a datasheet URL
uv run --no-project scripts/extract_specs.py --url "https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT22.pdf"
# Extract specs with markdown output
uv run --no-project scripts/extract_specs.py --url "https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf" --format markdown
# Extract from local PDF file
uv run --no-project scripts/extract_specs.py --file "local_datasheet.pdf"
# Interactive mode
uv run --no-project scripts/extract_specs.py --interactive---
When to Use
- "What's the I2C address for this chip?"
- "How do I connect this sensor?"
- "What pins do what on this module?"
- User shares a PDF datasheet URL
- Need timing specs or register info
- Quick reference without full driver generation
Relationship to Other Skills
- datasheet-parser: Full driver library generation (lexus2k-style)
- datasheet-interpreter (this): Quick spec extraction for immediate use
---
Information Extraction Workflow
Step 1: Request Datasheet
Please provide:
1. Component name (e.g., "BME280", "MPU6050")
2. Datasheet PDF file or URL (manufacturer version preferred)
3. What specific information you need:
□ Pin assignments / pinout
□ I2C/SPI address and protocol
□ Operating conditions (voltage, current)
□ Timing requirements
□ Register map
□ Example circuit
□ All of the aboveStep 2: Validate PDF Quality
Check For:
- Searchable text (not scanned image)
- Official manufacturer datasheet (not clone/distributor summary)
- Revision/version number noted
- Complete document (includes register tables if applicable)
If PDF Issues:
⚠️ This appears to be a [scanned image | partial datasheet | third-party summary].
For best results, please provide the official manufacturer datasheet from:
- [Manufacturer website link]
- Or search: "[component] datasheet pdf site:manufacturer.com"---
Extraction Templates
Quick Reference Card
Generate this for any component:
# [Component Name] Quick Reference
## Basic Info
- **Manufacturer:** [name]
- **Part Number:** [full part number with variants]
- **Datasheet Version:** [rev/date]
- **Description:** [one-line description]
## Electrical Characteristics
| Parameter | Min | Typ | Max | Unit |
|-----------|-----|-----|-----|------|
| Supply Voltage (VDD) | | | | V |
| Operating Current | | | | mA |
| Sleep Current | | | | µA |
| Operating Temp | | | | °C |
## Communication Interface
- **Protocol:** [I2C / SPI / UART / GPIO]
- **Address:** [0xNN (7-bit)] or [selectable via pin]
- **Max Clock:** [frequency]
- **Logic Levels:** [3.3V / 5V tolerant]
## Pinout
| Pin | Name | Type | Description |
|-----|------|------|-------------|
| 1 | VDD | Power | Supply voltage |
| 2 | GND | Power | Ground |
| ... | ... | ... | ... |
## Key Registers (if applicable)
| Address | Name | R/W | Description |
|---------|------|-----|-------------|
| 0x00 | WHO_AM_I | R | Device ID (expect 0xNN) |
| ... | ... | ... | ... |
## Arduino Wiring
| Component Pin | Arduino Uno | ESP32 | Pico |
|---------------|-------------|-------|------|
| VCC | 3.3V | 3.3V | 3V3 |
| GND | GND | GND | GND |
| SDA | A4 | GPIO21 | GP4 |
| SCL | A5 | GPIO22 | GP5 |
## Important Notes
- [Critical warnings from datasheet]
- [Application notes]---
Common Component Categories
I2C Sensors
Extract:
□ I2C address (7-bit format, note if configurable)
□ WHO_AM_I register address and expected value
□ Configuration register settings
□ Data registers (where to read measurements)
□ Resolution and range options
□ Conversion time / measurement rateI2C Address Quick Reference:
| Component | Address (7-bit) | Config Pin | Alt Address |
|---|---|---|---|
| BME280 | 0x76 | SDO→GND | 0x77 (SDO→VDD) |
| BME680 | 0x76 | SDO→GND | 0x77 (SDO→VDD) |
| MPU6050 | 0x68 | AD0→GND | 0x69 (AD0→VDD) |
| SHT30 | 0x44 | ADDR→GND | 0x45 (ADDR→VDD) |
| BH1750 | 0x23 | ADDR→GND | 0x5C (ADDR→VDD) |
| VL53L0X | 0x29 | - | Configurable via software |
| INA219 | 0x40 | A0,A1 | 0x40-0x4F |
| ADS1115 | 0x48 | ADDR→GND | 0x49-0x4B |
SPI Devices
Extract:
□ SPI mode (CPOL, CPHA)
□ Max clock frequency
□ Data order (MSB/LSB first)
□ Command format
□ Chip select behavior (active low?)Motor Drivers
Extract:
□ Logic voltage (VCC)
□ Motor voltage range (VM)
□ Current per channel (continuous/peak)
□ Control interface (PWM frequency, step/dir)
□ Protection features (thermal, overcurrent)
□ H-bridge or half-bridgeDisplay Modules
Extract:
□ Resolution (width x height)
□ Controller IC (SSD1306, ILI9341, etc.)
□ Interface (I2C, SPI, parallel)
□ Memory map / pixel format
□ Initialization sequence
□ Refresh rate---
Critical Spec Finder
For Power Planning
Search datasheet for:
- "Electrical Characteristics" table
- "Power Consumption" or "Current Consumption"
- "Typical Operating Conditions"
- Sleep/standby modes and their currentsTemplate Output:
## Power Specs for [Component]
### Operating Modes
| Mode | Typical Current | Conditions |
|------|-----------------|------------|
| Active | XXX mA | [freq, voltage, etc.] |
| Idle | XXX µA | |
| Sleep/Standby | XXX µA | |
| Deep Sleep | XXX nA | |
### Wake-up Time
- From sleep: XXX ms
- From deep sleep: XXX msFor Timing Requirements
Search datasheet for:
- "Timing Characteristics"
- "AC Characteristics"
- Setup/hold times
- Clock specificationsFor Pin Tolerance
Search datasheet for:
- "Absolute Maximum Ratings"
- "ESD protection"
- "5V tolerant" mentions
- Input voltage specifications---
Register Map Extraction
Structured Format
// [Component] Register Definitions
// Extracted from datasheet v[X.Y], p.[N]
// === REGISTER ADDRESSES ===
#define REG_NAME 0x00 // Description
// === BITFIELD DEFINITIONS ===
// REG_NAME (0x00) - [Description]
// Bit 7-6: FIELD_A - [description]
// Bit 5: FIELD_B - [description]
// Bit 4-0: FIELD_C - [description]
#define REG_NAME_FIELD_A_MASK 0xC0
#define REG_NAME_FIELD_A_POS 6
#define REG_NAME_FIELD_B_BIT 5
#define REG_NAME_FIELD_C_MASK 0x1FCommon Register Patterns
Configuration Register:
- Enable/disable features
- Set operating mode
- Configure interrupt behaviorStatus Register:
- Data ready flags
- Error/fault indicators
- Interrupt sourcesData Registers:
- Usually consecutive addresses
- Note byte order (MSB first or LSB first)
- Signed vs unsigned interpretation---
Wiring Diagram Generator
Generate text-based wiring diagrams:
[Component] [Arduino Uno]
┌─────────┐ ┌───────────┐
│ VCC ────┼─────────┤ 3.3V │
│ GND ────┼─────────┤ GND │
│ SDA ────┼─────────┤ A4 (SDA) │
│ SCL ────┼─────────┤ A5 (SCL) │
│ INT ────┼─────────┤ D2 (INT0) │
└─────────┘ └───────────┘
Notes:
- Add 4.7kΩ pull-ups on SDA/SCL if not on module
- INT is open-drain, needs pull-up---
Example Code Generator
Based on extracted specs, generate minimal working example:
/*
* [Component] Basic Example
* Generated from datasheet interpretation
*
* Wiring:
* - VCC → 3.3V
* - GND → GND
* - SDA → A4 (Arduino Uno) / GPIO21 (ESP32)
* - SCL → A5 (Arduino Uno) / GPIO22 (ESP32)
*/
#include <Wire.h>
#define COMPONENT_ADDR 0x00 // From datasheet p.X
#define REG_WHO_AM_I 0x00 // Expected: 0xNN
#define REG_CTRL 0x00 // Configuration
#define REG_DATA 0x00 // Data output
void setup() {
Serial.begin(115200);
Wire.begin();
// Verify communication
Wire.beginTransmission(COMPONENT_ADDR);
if (Wire.endTransmission() != 0) {
Serial.println("Component not found!");
while(1);
}
// Read device ID
uint8_t id = readReg(REG_WHO_AM_I);
Serial.print("Device ID: 0x");
Serial.println(id, HEX);
// Initialize
writeReg(REG_CTRL, 0x00); // [Configuration value from datasheet]
}
void loop() {
// Read data
int16_t data = readReg16(REG_DATA);
Serial.println(data);
delay(100);
}
uint8_t readReg(uint8_t reg) {
Wire.beginTransmission(COMPONENT_ADDR);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(COMPONENT_ADDR, (uint8_t)1);
return Wire.read();
}
void writeReg(uint8_t reg, uint8_t val) {
Wire.beginTransmission(COMPONENT_ADDR);
Wire.write(reg);
Wire.write(val);
Wire.endTransmission();
}
int16_t readReg16(uint8_t reg) {
Wire.beginTransmission(COMPONENT_ADDR);
Wire.write(reg);
Wire.endTransmission(false);
Wire.requestFrom(COMPONENT_ADDR, (uint8_t)2);
int16_t val = Wire.read() << 8; // MSB first (check datasheet!)
val |= Wire.read();
return val;
}---
Quick Lookup Database
Common Questions Answered
"What voltage does [component] need?" → Check "Absolute Maximum Ratings" and "Recommended Operating Conditions"
"Is it 3.3V or 5V?" → Look for "VDD" or "VCC" in electrical characteristics → Check if I/O pins are "5V tolerant"
"How fast can I run the I2C?" → Find "I2C Clock Frequency" in timing specs (usually 100kHz standard, 400kHz fast)
"How do I wake it from sleep?" → Look for "Power Management" or "Operating Modes" section
"Why won't it respond at the address I expected?" → Check address selection pins (AD0, ADDR, SA0) → Some datasheets show 8-bit address (divide by 2 for 7-bit)
---
Datasheet Reading Tips
Where to Find Key Info
| Information | Section Name |
|---|---|
| I2C Address | "Serial Interface" or "Communication" |
| Voltage/Current | "Electrical Characteristics" |
| Pinout | "Pin Configuration" or "Package Information" |
| Registers | "Register Map" or "Memory Map" |
| Timing | "Timing Characteristics" or "AC Characteristics" |
| Example Circuit | "Application Information" or "Typical Application" |
Red Flags in Datasheets
- 🚩 Missing absolute maximum ratings
- 🚩 No version/revision number
- 🚩 Inconsistent register descriptions
- 🚩 Machine-translated text
- 🚩 Missing timing diagrams for protocols
→ If you see these, try to find a better quality datasheet from the original manufacturer
{
"name": "datasheet-interpreter",
"metadata": {
"description": "Interprets datasheets and extracts key specifications for sensors, microcontrollers, and components. Use when user has a datasheet, needs to understand pinouts, wants to find electrical specifications, or needs wiring diagrams. Extracts voltage ranges, pin functions, communication protocols, and integration examples.",
"version": "0.8.0",
"license": "MIT",
"author": "arduino-skills contributors",
"tags": ["datasheet-interpretation", "electronics", "components", "maker", "reference"],
"category": "maker-tools"
},
"plugins": [
{
"name": "datasheet-interpreter",
"description": "Extract specifications, pinouts, and integration examples from component datasheets",
"enabled": true
}
]
}
Common Datasheet Parameters Reference
Quick reference for understanding component datasheets.
Electrical Ratings
Voltage Parameters
| Parameter | Symbol | Meaning |
|---|---|---|
| Operating Voltage | VCC, VDD | Normal supply voltage range |
| Max Voltage | Vmax | Absolute maximum (do not exceed!) |
| Logic High | VIH | Minimum voltage for logic HIGH |
| Logic Low | VIL | Maximum voltage for logic LOW |
| Output High | VOH | Output voltage when HIGH |
| Output Low | VOL | Output voltage when LOW |
Current Parameters
| Parameter | Symbol | Meaning |
|---|---|---|
| Operating Current | ICC, IDD | Normal current draw |
| Standby Current | ISB | Current in sleep/standby mode |
| Max Current | Imax | Maximum safe continuous current |
| Source Current | IOH | Max current pin can source (output HIGH) |
| Sink Current | IOL | Max current pin can sink (output LOW) |
Communication Interface Parameters
I2C
| Parameter | Typical Value | Notes |
|---|---|---|
| Standard Mode | 100 kHz | Basic speed |
| Fast Mode | 400 kHz | Common for sensors |
| Fast Mode+ | 1 MHz | Higher performance |
| High Speed | 3.4 MHz | Requires special setup |
| Pull-up | 2.2kΩ-10kΩ | Required on SDA/SCL |
SPI
| Parameter | Range | Notes |
|---|---|---|
| Clock Speed | 1-20+ MHz | Device dependent |
| Modes | 0, 1, 2, 3 | CPOL/CPHA settings |
| Bit Order | MSB/LSB first | Check datasheet |
UART
| Parameter | Common Values | Notes |
|---|---|---|
| Baud Rate | 9600, 115200 | Must match both ends |
| Data Bits | 8 | Standard |
| Stop Bits | 1 | Standard |
| Parity | None | Unless specified |
Timing Parameters
| Parameter | Symbol | Meaning |
|---|---|---|
| Startup Time | tPU | Time from power-on to ready |
| Conversion Time | tCONV | ADC/sensor measurement time |
| Response Time | tRESP | Time to respond to input |
| Refresh Rate | - | How often output updates |
Temperature Ratings
| Range | Min | Max | Notes |
|---|---|---|---|
| Commercial | 0°C | 70°C | Consumer devices |
| Industrial | -40°C | 85°C | Robust applications |
| Extended | -40°C | 125°C | Automotive/harsh |
Common Unit Conversions
| From | To | Multiply by |
|---|---|---|
| mA | µA | 1000 |
| µA | mA | 0.001 |
| mW | W | 0.001 |
| MHz | kHz | 1000 |
| ms | µs | 1000 |
| µs | ms | 0.001 |
Absolute Maximum Ratings ⚠️
CRITICAL: These are DAMAGE LIMITS, not operating conditions!
- Never exceed these values even momentarily
- Recommended operating conditions are LOWER
- No safety margin included
Interpreting Accuracy Specs
±X% vs ±X Units
- ±5%: Error is percentage of reading
- ±2°C: Error is fixed amount regardless of reading
Full Scale (FS) vs Reading
- ±1% FS: Error relative to maximum range
- ±1% rdg: Error relative to actual reading
Common Gotchas
1. 5V vs 3.3V Logic
- Many modern sensors are 3.3V ONLY
- Check "Absolute Maximum" voltage
- Use level shifters if needed
2. I2C Addresses
- May be shown as 7-bit or 8-bit
- 7-bit: 0x3C, 8-bit: 0x78 (same device)
- Some addresses are configurable
3. Current Draw
- Datasheet shows typical, actual may vary
- Add 20% margin for design
- Check peak vs average current
4. Pin Names
- VCC/VDD = Power positive
- VSS/GND = Ground
- NC = Not Connected
- DNC = Do Not Connect
Quick Lookup: Popular Components
| Component | Voltage | Interface | Address |
|---|---|---|---|
| DHT22 | 3.3-5V | 1-Wire | N/A |
| BME280 | 3.3V | I2C/SPI | 0x76/0x77 |
| SSD1306 | 3.3V | I2C/SPI | 0x3C/0x3D |
| MPU6050 | 3.3V | I2C | 0x68/0x69 |
| DS3231 | 3.3-5V | I2C | 0x68 |
| nRF24L01 | 3.3V | SPI | N/A |
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "httpx",
# "pdfplumber",
# ]
# ///
"""
Datasheet Spec Extractor - Extracts key specifications from component datasheet PDFs
Downloads datasheet PDFs from URLs and extracts:
- Voltage/current ratings
- Pin configurations
- I2C/SPI addresses
- Operating temperature
- Timing parameters
Usage:
uv run --no-project scripts/extract_specs.py --url "https://example.com/datasheet.pdf"
uv run --no-project scripts/extract_specs.py --url "https://example.com/datasheet.pdf" --format markdown
uv run --no-project scripts/extract_specs.py --file "local_datasheet.pdf"
uv run --no-project scripts/extract_specs.py --interactive
"""
import argparse
import re
import sys
import tempfile
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional
import httpx
import pdfplumber
# =============================================================================
# Extraction Patterns - Regex for common datasheet parameters
# =============================================================================
PATTERNS = {
# Voltage patterns
"supply_voltage": [
r"(?:VCC|VDD|Supply|Operating)\s*(?:Voltage)?[\s:=]+(\d+\.?\d*)\s*(?:to|[-–])\s*(\d+\.?\d*)\s*V",
r"(\d+\.?\d*)\s*V\s*(?:to|[-–])\s*(\d+\.?\d*)\s*V\s*(?:supply|operating|input)",
r"(?:Power Supply|Input Voltage)[\s:]+(\d+\.?\d*)\s*[-–]\s*(\d+\.?\d*)\s*V",
],
# Current patterns
"supply_current": [
r"(?:Supply|Operating|Active)\s*Current[\s:=]+(\d+\.?\d*)\s*(mA|µA|uA|μA)",
r"(?:ICC|IDD)[\s:=]+(\d+\.?\d*)\s*(mA|µA|uA|μA)",
r"Current\s*Consumption[\s:=]+(\d+\.?\d*)\s*(mA|µA|uA|μA)",
],
# Standby/Sleep current
"standby_current": [
r"(?:Standby|Sleep|Power.?Down)\s*(?:Current|Mode)?[\s:=]+(\d+\.?\d*)\s*(mA|µA|uA|μA|nA)",
r"(?:ISB|ISBY)[\s:=]+(\d+\.?\d*)\s*(µA|uA|μA|nA)",
],
# I2C Address
"i2c_address": [
r"(?:I2C|I²C|IIC)\s*(?:Address|Addr)[\s:=]*(0x[0-9A-Fa-f]{2})",
r"(?:Slave|Device)\s*Address[\s:=]*(0x[0-9A-Fa-f]{2})",
r"Address[\s:=]*(0x[0-9A-Fa-f]{2})\s*(?:\(7.?bit\))?",
r"(0x[0-9A-Fa-f]{2})\s*(?:\(W\)|write|read)",
],
# SPI Clock
"spi_clock": [
r"(?:SPI|SCLK|SCK)\s*(?:Clock|Frequency)?[\s:=]+(?:up to\s*)?(\d+\.?\d*)\s*(MHz|kHz)",
r"(?:Serial Clock|fSCL)[\s:=]+(\d+\.?\d*)\s*(MHz|kHz)",
],
# Operating Temperature
"temperature_range": [
r"(?:Operating|Ambient)\s*Temperature[\s:=]*([-]?\d+)\s*°?C?\s*(?:to|[-–])\s*([-]?\d+)\s*°?C",
r"(?:TA|TOPR)[\s:=]*([-]?\d+)\s*(?:to|[-–])\s*([-]?\d+)\s*°?C",
r"([-]?\d+)\s*°C\s*to\s*([-]?\d+)\s*°C\s*(?:operating|ambient)",
],
# Resolution (for ADCs, sensors)
"resolution": [
r"(?:Resolution|ADC)[\s:=]+(\d+)\s*(?:-)?bit",
r"(\d+)\s*(?:-)?bit\s*(?:resolution|ADC|DAC)",
],
# Package/Pins
"package": [
r"(?:Package|Pkg)[\s:=]+(\w+[-]?\d+)",
r"(SO(?:IC)?[-]?\d+|DIP[-]?\d+|QFN[-]?\d+|TQFP[-]?\d+|BGA[-]?\d+)",
],
# Accuracy (for sensors)
"accuracy": [
r"(?:Accuracy|Typical Accuracy)[\s:=]+[±]?(\d+\.?\d*)\s*(%|°C|°|LSB)",
r"[±](\d+\.?\d*)\s*(%|°C)\s*(?:accuracy|typical)",
],
}
@dataclass
class ExtractedSpecs:
"""Container for extracted specifications"""
source: str
supply_voltage: Optional[str] = None
supply_current: Optional[str] = None
standby_current: Optional[str] = None
i2c_addresses: list = field(default_factory=list)
spi_clock: Optional[str] = None
temperature_range: Optional[str] = None
resolution: Optional[str] = None
package: Optional[str] = None
accuracy: Optional[str] = None
raw_text_sample: str = ""
tables: list = field(default_factory=list)
def download_pdf(url: str, timeout: float = 30.0) -> bytes:
"""Download PDF from URL with progress indication"""
print(f"Downloading: {url}")
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
}
with httpx.Client(follow_redirects=True, timeout=timeout) as client:
response = client.get(url, headers=headers)
response.raise_for_status()
content_type = response.headers.get("content-type", "")
if "pdf" not in content_type.lower() and not url.lower().endswith(".pdf"):
print(f"Warning: Content-Type is '{content_type}', may not be a PDF")
print(f"Downloaded: {len(response.content):,} bytes")
return response.content
def extract_text_from_pdf(pdf_path: Path, max_pages: int = 20) -> tuple[str, list]:
"""Extract text and tables from PDF"""
all_text = []
all_tables = []
with pdfplumber.open(pdf_path) as pdf:
page_count = min(len(pdf.pages), max_pages)
print(f"Processing {page_count} pages...")
for i, page in enumerate(pdf.pages[:max_pages]):
# Extract text
text = page.extract_text() or ""
all_text.append(text)
# Extract tables (first 5 pages only - usually contain specs)
if i < 5:
tables = page.extract_tables()
for table in tables:
if table and len(table) > 1: # Has header + data
all_tables.append(table)
return "\n".join(all_text), all_tables
def extract_specs(text: str, tables: list, source: str) -> ExtractedSpecs:
"""Extract specifications using regex patterns"""
specs = ExtractedSpecs(source=source)
# Normalize text for better matching
text_normalized = text.replace('\n', ' ').replace(' ', ' ')
# Extract voltage
for pattern in PATTERNS["supply_voltage"]:
match = re.search(pattern, text_normalized, re.IGNORECASE)
if match:
specs.supply_voltage = f"{match.group(1)}-{match.group(2)}V"
break
# Extract supply current
for pattern in PATTERNS["supply_current"]:
match = re.search(pattern, text_normalized, re.IGNORECASE)
if match:
specs.supply_current = f"{match.group(1)} {match.group(2)}"
break
# Extract standby current
for pattern in PATTERNS["standby_current"]:
match = re.search(pattern, text_normalized, re.IGNORECASE)
if match:
specs.standby_current = f"{match.group(1)} {match.group(2)}"
break
# Extract I2C addresses (can have multiple)
for pattern in PATTERNS["i2c_address"]:
matches = re.findall(pattern, text_normalized, re.IGNORECASE)
for addr in matches:
if addr not in specs.i2c_addresses:
specs.i2c_addresses.append(addr)
# Extract SPI clock
for pattern in PATTERNS["spi_clock"]:
match = re.search(pattern, text_normalized, re.IGNORECASE)
if match:
specs.spi_clock = f"{match.group(1)} {match.group(2)}"
break
# Extract temperature range
for pattern in PATTERNS["temperature_range"]:
match = re.search(pattern, text_normalized, re.IGNORECASE)
if match:
specs.temperature_range = f"{match.group(1)}°C to {match.group(2)}°C"
break
# Extract resolution
for pattern in PATTERNS["resolution"]:
match = re.search(pattern, text_normalized, re.IGNORECASE)
if match:
specs.resolution = f"{match.group(1)}-bit"
break
# Extract package
for pattern in PATTERNS["package"]:
match = re.search(pattern, text_normalized, re.IGNORECASE)
if match:
specs.package = match.group(1)
break
# Extract accuracy
for pattern in PATTERNS["accuracy"]:
match = re.search(pattern, text_normalized, re.IGNORECASE)
if match:
specs.accuracy = f"±{match.group(1)}{match.group(2)}"
break
# Store raw text sample (first 500 chars for context)
specs.raw_text_sample = text[:500].strip()
# Store relevant tables
specs.tables = tables[:3] # First 3 tables
return specs
def format_text(specs: ExtractedSpecs) -> str:
"""Format extracted specs as plain text"""
lines = [
"=" * 60,
" DATASHEET SPEC EXTRACTION",
"=" * 60,
"",
f"Source: {specs.source}",
"",
"EXTRACTED SPECIFICATIONS:",
"-" * 40,
]
if specs.supply_voltage:
lines.append(f" Supply Voltage: {specs.supply_voltage}")
if specs.supply_current:
lines.append(f" Supply Current: {specs.supply_current}")
if specs.standby_current:
lines.append(f" Standby Current: {specs.standby_current}")
if specs.i2c_addresses:
lines.append(f" I2C Address(es): {', '.join(specs.i2c_addresses)}")
if specs.spi_clock:
lines.append(f" SPI Clock: {specs.spi_clock}")
if specs.temperature_range:
lines.append(f" Temperature: {specs.temperature_range}")
if specs.resolution:
lines.append(f" Resolution: {specs.resolution}")
if specs.package:
lines.append(f" Package: {specs.package}")
if specs.accuracy:
lines.append(f" Accuracy: {specs.accuracy}")
# Check if nothing was extracted
has_specs = any([
specs.supply_voltage, specs.supply_current, specs.standby_current,
specs.i2c_addresses, specs.spi_clock, specs.temperature_range,
specs.resolution, specs.package, specs.accuracy
])
if not has_specs:
lines.append(" (No specifications auto-extracted)")
lines.append(" Try reading the datasheet manually for specs")
lines.append("")
# Show tables if found
if specs.tables:
lines.append("SPECIFICATION TABLES FOUND:")
lines.append("-" * 40)
for i, table in enumerate(specs.tables[:2], 1):
lines.append(f"\nTable {i}:")
for row in table[:5]: # First 5 rows
if row:
lines.append(" " + " | ".join(str(cell or "")[:20] for cell in row))
if len(table) > 5:
lines.append(f" ... ({len(table) - 5} more rows)")
lines.append("")
lines.append("RAW TEXT SAMPLE (first 300 chars):")
lines.append("-" * 40)
lines.append(specs.raw_text_sample[:300])
return "\n".join(lines)
def format_markdown(specs: ExtractedSpecs) -> str:
"""Format extracted specs as markdown"""
lines = [
"# Datasheet Specification Extract",
"",
f"**Source:** `{specs.source}`",
"",
"## Extracted Specifications",
"",
"| Parameter | Value |",
"|-----------|-------|",
]
if specs.supply_voltage:
lines.append(f"| Supply Voltage | {specs.supply_voltage} |")
if specs.supply_current:
lines.append(f"| Supply Current | {specs.supply_current} |")
if specs.standby_current:
lines.append(f"| Standby Current | {specs.standby_current} |")
if specs.i2c_addresses:
lines.append(f"| I2C Address | {', '.join(specs.i2c_addresses)} |")
if specs.spi_clock:
lines.append(f"| SPI Clock | {specs.spi_clock} |")
if specs.temperature_range:
lines.append(f"| Operating Temp | {specs.temperature_range} |")
if specs.resolution:
lines.append(f"| Resolution | {specs.resolution} |")
if specs.package:
lines.append(f"| Package | {specs.package} |")
if specs.accuracy:
lines.append(f"| Accuracy | {specs.accuracy} |")
lines.append("")
# Tables
if specs.tables:
lines.append("## Specification Tables")
lines.append("")
for i, table in enumerate(specs.tables[:2], 1):
lines.append(f"### Table {i}")
lines.append("")
# Convert to markdown table
if table and len(table) > 0:
header = table[0]
lines.append("| " + " | ".join(str(h or "")[:20] for h in header) + " |")
lines.append("| " + " | ".join(["---"] * len(header)) + " |")
for row in table[1:6]:
lines.append("| " + " | ".join(str(c or "")[:20] for c in row) + " |")
if len(table) > 6:
lines.append(f"*... {len(table) - 6} more rows*")
lines.append("")
return "\n".join(lines)
def process_url(url: str, output_format: str = "text") -> str:
"""Main processing function for URL input"""
try:
# Download PDF
pdf_bytes = download_pdf(url)
# Save to temp file
with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) as tmp:
tmp.write(pdf_bytes)
tmp_path = Path(tmp.name)
try:
# Extract text and tables
text, tables = extract_text_from_pdf(tmp_path)
# Extract specs
specs = extract_specs(text, tables, url)
# Format output
if output_format == "markdown":
return format_markdown(specs)
else:
return format_text(specs)
finally:
# Cleanup temp file
tmp_path.unlink(missing_ok=True)
except httpx.HTTPError as e:
return f"Error downloading PDF: {e}"
except Exception as e:
return f"Error processing PDF: {e}"
def process_file(file_path: str, output_format: str = "text") -> str:
"""Process local PDF file"""
path = Path(file_path)
if not path.exists():
return f"Error: File not found: {file_path}"
try:
text, tables = extract_text_from_pdf(path)
specs = extract_specs(text, tables, str(path))
if output_format == "markdown":
return format_markdown(specs)
else:
return format_text(specs)
except Exception as e:
return f"Error processing PDF: {e}"
def interactive_mode():
"""Interactive mode for spec extraction"""
print("=" * 60)
print("Datasheet Spec Extractor - Interactive Mode")
print("=" * 60)
print()
print("Enter a datasheet PDF URL or local file path")
print("Type 'quit' or 'q' to exit")
print()
while True:
source = input("PDF URL or path> ").strip()
if not source:
continue
if source.lower() in ['quit', 'exit', 'q']:
break
print()
if source.startswith(('http://', 'https://')):
result = process_url(source)
else:
result = process_file(source)
print(result)
print()
def main():
parser = argparse.ArgumentParser(
description="Extract specifications from component datasheet PDFs",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
uv run --no-project scripts/extract_specs.py --url "https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT22.pdf"
uv run --no-project scripts/extract_specs.py --url "https://cdn-shop.adafruit.com/datasheets/SSD1306.pdf" --format markdown
uv run --no-project scripts/extract_specs.py --file "local_datasheet.pdf"
uv run --no-project scripts/extract_specs.py --interactive
"""
)
parser.add_argument("--url", "-u", type=str, help="URL of datasheet PDF to download and extract")
parser.add_argument("--file", "-f", type=str, help="Local PDF file path")
parser.add_argument("--format", type=str, default="text",
choices=["text", "markdown"], help="Output format")
parser.add_argument("--interactive", "-i", action="store_true", help="Interactive mode")
args = parser.parse_args()
if args.interactive:
interactive_mode()
return
if args.url:
result = process_url(args.url, args.format)
print(result)
return
if args.file:
result = process_file(args.file, args.format)
print(result)
return
parser.print_help()
if __name__ == "__main__":
main()