
Bom Generator
- 76 installs
- 19 repo stars
- Updated May 26, 2026
- wedsamuel1230/arduino-skills
Helps with ai & agent building tasks.
About
bom-generator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- bom-generator
- AI & Agent Building
- AI-coding skill
Bom Generator by the numbers
- 76 all-time installs (skills.sh)
- +4 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #5,442 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 bom-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 76 |
|---|---|
| repo stars | ★ 19 |
| Last updated | May 26, 2026 |
| Repository | wedsamuel1230/arduino-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
BOM Generator
Creates comprehensive Bill of Materials for maker projects with supplier links and compatibility checks.
Resources
- scripts/generate_bom.py - Python script for xlsx/csv/markdown BOM generation (requires openpyxl)
- references/example-bom.md - Complete example BOM
- assets/example-project.json - Sample project configuration for script
Quick Start
Generate BOM interactively:
uv run --no-project scripts/generate_bom.py --interactiveGenerate from JSON configuration:
uv run --no-project scripts/generate_bom.py --json assets/example-project.json --output bom.xlsxList component database:
uv run --no-project scripts/generate_bom.py --listExport formats: xlsx (default), csv, md, json
When to Use
- User describes a project and needs parts list
- User asks "what components do I need for X"
- User wants to order parts for a design
- User needs cost estimates
Workflow
Step 1: Gather Project Requirements
Ask user for:
1. Project description (what does it do?)
2. Target microcontroller (Arduino UNO/Nano/Mega, ESP32, RP2040)
3. Power source (USB, batteries, wall adapter)
4. Quantity (how many units to build?)
5. Budget constraints (optional)
6. Supplier preference (optional)Step 2: Generate BOM
Run scripts/generate_bom.py --interactive for guided generation, or use this template format:
# Bill of Materials: [Project Name]
**Generated:** [Date]
**Target Board:** [MCU]
**Quantity:** [N] unit(s)
**Estimated Total:** $[X.XX] - $[Y.YY] (per unit)
## Core Components
| Qty | Component | Specifications | Est. Price | Supplier Links |
|-----|-----------|----------------|------------|----------------|
| 1 | [MCU Board] | [specs] | $X.XX | [DigiKey](#) / [Amazon](#) |
| ... | ... | ... | ... | ... |
## Sensors & Input
| Qty | Component | Specifications | Est. Price | Supplier Links |
|-----|-----------|----------------|------------|----------------|
| ... | ... | ... | ... | ... |
## Output Devices
| Qty | Component | Specifications | Est. Price | Supplier Links |
|-----|-----------|----------------|------------|----------------|
| ... | ... | ... | ... | ... |
## Power Components
| Qty | Component | Specifications | Est. Price | Supplier Links |
|-----|-----------|----------------|------------|----------------|
| ... | ... | ... | ... | ... |
## Passive Components
| Qty | Component | Specifications | Est. Price | Supplier Links |
|-----|-----------|----------------|------------|----------------|
| ... | ... | ... | ... | ... |
## Mechanical & Connectors
| Qty | Component | Specifications | Est. Price | Supplier Links |
|-----|-----------|----------------|------------|----------------|
| ... | ... | ... | ... | ... |
## Tools Required (if not owned)
- [ ] Tool 1
- [ ] Tool 2
## Compatibility Warnings
⚠️ [Any voltage/current/timing concerns]
## Substitution Notes
💡 [Alternative components if primary unavailable]Component Database
Microcontrollers
| Board | Voltage | GPIO | Flash | Price Range | Best For |
|---|---|---|---|---|---|
| Arduino UNO R3 | 5V | 14 | 32KB | $12-25 | Beginners, most tutorials |
| Arduino Nano | 5V | 14 | 32KB | $3-20 | Compact projects |
| Arduino Mega | 5V | 54 | 256KB | $15-40 | Many I/O, large programs |
| ESP32 DevKit | 3.3V | 34 | 4MB | $5-15 | WiFi, Bluetooth, IoT |
| ESP32-C3 | 3.3V | 22 | 4MB | $4-10 | Low-cost WiFi |
| RP2040 Pico | 3.3V | 26 | 2MB | $4-6 | Dual-core, PIO |
| RP2040 Pico W | 3.3V | 26 | 2MB | $6-8 | Pico + WiFi |
Common Sensors
| Sensor | Interface | Voltage | Price | Use Case |
|---|---|---|---|---|
| DHT22/AM2302 | 1-Wire | 3.3-5V | $3-8 | Temperature + humidity |
| BME280 | I2C/SPI | 3.3V | $5-15 | Temp + humidity + pressure |
| BMP280 | I2C/SPI | 3.3V | $2-8 | Temp + pressure (no humidity) |
| DS18B20 | 1-Wire | 3-5.5V | $2-5 | Waterproof temperature |
| HC-SR04 | GPIO | 5V | $1-3 | Ultrasonic distance |
| VL53L0X | I2C | 3.3V | $5-12 | Laser distance (accurate) |
| MPU6050 | I2C | 3.3V | $2-8 | Accelerometer + gyroscope |
| BNO055 | I2C | 3.3V | $25-35 | 9-DOF IMU (best accuracy) |
| VEML7700 | I2C | 3.3V | $4-8 | Ambient light (lux) |
| MAX30102 | I2C | 3.3V | $5-12 | Heart rate + SpO2 |
Displays
| Display | Interface | Resolution | Price | Notes |
|---|---|---|---|---|
| 16x2 LCD | I2C | 16 chars | $3-8 | Need I2C backpack |
| 20x4 LCD | I2C | 20 chars | $5-12 | Larger text display |
| SSD1306 OLED | I2C | 128x64 | $3-8 | Sharp, no backlight needed |
| SSD1306 OLED | I2C | 128x32 | $3-6 | Compact OLED |
| ST7735 TFT | SPI | 128x160 | $5-10 | Color, fast refresh |
| ILI9341 TFT | SPI | 240x320 | $8-15 | Larger color display |
| E-Paper | SPI | Various | $15-40 | Low power, no refresh |
Motors & Actuators
| Type | Driver Needed | Price | Notes |
|---|---|---|---|
| SG90 Servo | None (PWM) | $2-5 | 180°, weak torque |
| MG996R Servo | None (PWM) | $5-10 | Strong, metal gears |
| 28BYJ-48 Stepper | ULN2003 | $3-6 | Cheap, slow, weak |
| NEMA17 Stepper | A4988/DRV8825 | $8-15 | Strong, precise |
| DC Motor + Gearbox | L298N/TB6612 | $5-15 | High speed available |
| Linear Actuator | L298N | $15-40 | Push/pull motion |
Motor Drivers
| Driver | Channels | Max Current | Voltage | Price |
|---|---|---|---|---|
| L298N | 2 | 2A/ch | 5-35V | $3-8 |
| TB6612FNG | 2 | 1.2A/ch | 4.5-13.5V | $3-8 |
| A4988 | 1 stepper | 2A | 8-35V | $2-5 |
| DRV8825 | 1 stepper | 2.5A | 8.2-45V | $3-6 |
| TMC2209 | 1 stepper | 2A | 4.75-28V | $8-15 |
Power Components
| Component | Specs | Price | Use Case |
|---|---|---|---|
| LM7805 | 5V 1A linear | $0.50 | Simple 5V reg |
| AMS1117-3.3 | 3.3V 1A linear | $0.30 | 3.3V from 5V |
| LM2596 Module | Adj. 3A buck | $2-4 | Efficient step-down |
| MT3608 Module | Adj. 2A boost | $1-3 | Step-up voltage |
| TP4056 Module | LiPo charger | $1-2 | Battery charging |
| 18650 Holder | 1-4 cells | $1-5 | Battery mounting |
| JST Connectors | 2-pin | $2-5/10pk | Battery connections |
Passive Components (Buy Kits!)
| Kit Type | Typical Contents | Price | Recommendation |
|---|---|---|---|
| Resistor Kit | 600+ pcs, 1/4W | $8-15 | Get once, use forever |
| Capacitor Kit | Ceramic + electrolytic | $10-20 | Essential |
| LED Kit | 5mm various colors | $5-10 | Common needs |
| Button Kit | Tactile switches | $5-8 | Various sizes |
| Diode Kit | 1N4148, 1N4007, etc | $5-8 | Protection circuits |
Supplier Guide
Speed vs Cost Trade-offs
| Supplier | Shipping | Price | Best For |
|---|---|---|---|
| DigiKey | 1-3 days | $$$ | Precise specs, datasheets, urgent |
| Mouser | 1-3 days | $$$ | Wide selection, quality |
| Amazon | 1-2 days | $$ | Quick delivery, returns easy |
| Adafruit | 3-5 days | $$$ | Quality, tutorials, support |
| SparkFun | 3-5 days | $$$ | Breakout boards, learning |
| AliExpress | 2-6 weeks | $ | Bulk, budget, clones |
| LCSC | 1-2 weeks | $$ | Chinese components, good quality |
Part Number Patterns
DigiKey: Descriptive codes
- 1N4007-TP → 1N4007 diode
- SER0006 → Servo motor
Mouser: Manufacturer part numbers
- Search by exact MPN
Amazon: ASIN codes
- Search by product name + specs
AliExpress: Store + product ID
- Check reviews, sold countCompatibility Checks
Voltage Level Matrix
Can Connect To:
From: 3.3V Logic 5V Logic
─────────────────────────────────────
3.3V MCU ✅ Direct ⚠️ Level shifter
5V MCU ⚠️ Divider ✅ Direct
3.3V Sensor ✅ Direct ⚠️ May work*
5V Sensor ❌ Damage! ✅ Direct
* Some 3.3V sensors are 5V tolerant - check datasheetCurrent Budget Check
Source Limits:
- Arduino 5V pin: 500mA max (from USB)
- Arduino GPIO: 40mA max per pin
- ESP32 3.3V: 500mA max
- ESP32 GPIO: 40mA max
- RP2040 GPIO: 16mA max
Always calculate:
Total current = Σ(component currents)
If total > source limit → external power neededI2C Address Conflicts
Common I2C addresses to watch:
0x3C - SSD1306 OLED
0x27 - PCF8574 LCD backpack
0x3F - PCF8574A LCD backpack
0x68 - MPU6050, DS3231 RTC
0x76 - BME280 (default)
0x77 - BME280 (alternate), BMP280
0x48 - ADS1115 ADC
0x50 - AT24C32 EEPROMOutput Format Options
Markdown Table (Default)
Best for documentation, GitHub READMEs.
CSV Export
Qty,Component,Specifications,Unit Price,Total,Supplier,Link
1,Arduino UNO R3,ATmega328P,15.00,15.00,Amazon,https://...Shopping Cart Links
Provide direct "Add to Cart" links where possible.
Example BOM Output
See references/example-bom.md for complete example.
{
"name": "bom-generator",
"metadata": {
"description": "Generates Bill of Materials (BOM) from project descriptions for Arduino/ESP32/RP2040 projects. Use when user needs component lists, parts shopping lists, cost estimates, or asks \"what parts do I need\". Outputs formatted BOMs with part numbers, quantities, suppliers (DigiKey, Mouser, Amazon, AliExpress), and compatibility warnings.",
"version": "0.8.0",
"license": "MIT",
"author": "arduino-skills contributors",
"tags": ["bom-generation", "project-planning", "electronics", "maker", "procurement"],
"category": "maker-tools"
},
"plugins": [
{
"name": "bom-generator",
"description": "Generate Bill of Materials with part numbers, suppliers, and cost estimates",
"enabled": true
}
]
}
{
"project_name": "Weather Station",
"version": "1.0",
"author": "Maker Student",
"date": "2026-01-04",
"items": [
{
"name": "ESP32-DevKit",
"quantity": 1,
"reference": "U1",
"notes": "Main controller"
},
{
"name": "BME280",
"quantity": 1,
"reference": "U2",
"notes": "Environmental sensor"
},
{
"name": "OLED-128x64",
"quantity": 1,
"reference": "DISP1",
"notes": "Local display"
},
{
"name": "TP4056",
"quantity": 1,
"reference": "U3",
"notes": "Battery charger"
},
{
"name": "LiPo-1000mAh",
"quantity": 1,
"reference": "BAT1",
"notes": "Power source"
},
{
"name": "Breadboard-830",
"quantity": 1,
"reference": "-",
"notes": "Prototyping"
},
{
"name": "Jumper-Wires-Kit",
"quantity": 1,
"reference": "-",
"notes": "Connections"
}
]
}
Example BOM: Weather Station Project
Generated: 2026-01-04 Target Board: ESP32 DevKit Quantity: 1 unit Estimated Total: $35-55 (per unit)
Project Description
Battery-powered outdoor weather station with temperature, humidity, pressure sensors. Displays on OLED, logs to SD card, sends data via WiFi every 15 minutes.
---
Core Components
| Qty | Component | Specifications | Est. Price | Supplier Links |
|---|---|---|---|---|
| 1 | ESP32 DevKit V1 | ESP32-WROOM-32, 38 pins | $6-12 | Amazon / AliExpress |
| 1 | MicroSD Card Module | SPI interface, 3.3V | $1-3 | Amazon |
| 1 | MicroSD Card | 8-32GB, Class 10 | $5-10 | Amazon |
Sensors
| Qty | Component | Specifications | Est. Price | Supplier Links |
|---|---|---|---|---|
| 1 | BME280 | I2C, Temp/Humidity/Pressure, 3.3V | $5-12 | Amazon / Adafruit |
| 1 | BH1750 | I2C, Light sensor (lux), 3.3V | $2-5 | Amazon |
Display
| Qty | Component | Specifications | Est. Price | Supplier Links |
|---|---|---|---|---|
| 1 | SSD1306 OLED | I2C, 128x64, 0.96", White | $4-8 | Amazon |
Power Components
| Qty | Component | Specifications | Est. Price | Supplier Links |
|---|---|---|---|---|
| 1 | TP4056 Module | USB LiPo charger with protection | $1-3 | Amazon |
| 1 | 18650 Battery | 3.7V 2600mAh+, protected | $5-10 | Amazon |
| 1 | 18650 Holder | Single cell, wire leads | $1-2 | Amazon |
| 1 | MT3608 Boost | 3.7V → 5V step-up | $1-3 | Amazon |
| 1 | Slide Switch | SPDT, panel mount | $0.50 | Amazon |
Passive Components
| Qty | Component | Specifications | Est. Price | Supplier Links |
|---|---|---|---|---|
| 2 | 4.7kΩ Resistor | 1/4W, for I2C pull-ups | $0.10 | From resistor kit |
| 2 | 100nF Capacitor | Ceramic, decoupling | $0.10 | From capacitor kit |
| 1 | 100µF Capacitor | Electrolytic, 16V | $0.20 | From capacitor kit |
Mechanical & Connectors
| Qty | Component | Specifications | Est. Price | Supplier Links |
|---|---|---|---|---|
| 1 | Project Enclosure | Weatherproof, ~100x68x50mm | $5-10 | Amazon |
| 1 | Cable Glands | PG7, for wire entry | $2-5 | Amazon |
| 1 | Breadboard | 400 tie points (prototyping) | $2-4 | Amazon |
| 20 | Jumper Wires | M-M and M-F assortment | $3-6 | Amazon |
---
Cost Summary
| Category | Low Est. | High Est. |
|---|---|---|
| Core Components | $12 | $25 |
| Sensors | $7 | $17 |
| Display | $4 | $8 |
| Power | $9 | $18 |
| Passives | $0.50 | $1 |
| Mechanical | $10 | $20 |
| TOTAL | $43 | $89 |
Note: Prices vary by supplier and region. AliExpress is cheapest but slowest.
---
Compatibility Warnings
⚠️ Voltage: All sensors are 3.3V - ESP32 is 3.3V logic ✅ Compatible
⚠️ I2C Addresses:
- BME280: 0x76 (default) or 0x77
- BH1750: 0x23 (default) or 0x5C
- SSD1306: 0x3C (default) or 0x3D
- No conflicts with default addresses ✅
⚠️ Current Budget:
Component Typical Current
─────────────────────────────────────
ESP32 (active) 80-240mA
ESP32 (deep sleep) 10µA
BME280 0.1mA
BH1750 0.2mA
SSD1306 OLED 20mA
SD Card (write) 100mA peak
─────────────────────────────────────
Total Active: ~400mA max
Deep Sleep: ~0.2mA18650 @ 2600mAh → 6.5 hours continuous, or months with proper sleep cycles
---
Substitution Notes
💡 BME280 → DHT22: Cheaper ($3-5), but less accurate and no pressure. Wire is simpler (single GPIO).
💡 SSD1306 → 16x2 LCD: Cheaper ($3), but needs more pins and I2C adapter. Less info display.
💡 ESP32 → ESP8266: Cheaper ($3), fewer GPIO, no Bluetooth. Works for this project.
💡 18650 → 2x AA: Simpler, no special charger needed. Shorter battery life, voltage less stable.
---
Tools Required
If not already owned:
- [ ] Soldering iron + solder (for permanent build)
- [ ] Wire strippers
- [ ] Multimeter (essential for debugging)
- [ ] Hot glue gun (for securing components in enclosure)
- [ ] Drill + bits (for enclosure mounting holes)
---
Wiring Summary
ESP32 Component
────── ─────────
GPIO21 (SDA) → BME280 SDA, BH1750 SDA, OLED SDA
GPIO22 (SCL) → BME280 SCL, BH1750 SCL, OLED SCL
GPIO5 (CS) → SD Card CS
GPIO18 (SCK) → SD Card SCK
GPIO19 (MISO)→ SD Card MISO
GPIO23 (MOSI)→ SD Card MOSI
3.3V → All VCC pins
GND → All GND pins---
Order Checklist
□ ESP32 DevKit
□ BME280 sensor module
□ BH1750 light sensor
□ SSD1306 OLED display
□ MicroSD card module
□ MicroSD card (8GB+)
□ TP4056 charger module
□ 18650 battery (protected!)
□ 18650 holder
□ MT3608 boost converter
□ Enclosure
□ Cable glands
□ Jumper wires
□ Breadboard (if prototyping)#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = [
# "openpyxl",
# ]
# ///
"""
BOM Generator - Creates Bill of Materials in multiple formats
Generates professional BOM spreadsheets (xlsx, csv) with:
- Component details and quantities
- Supplier links and pricing
- Category totals and grand total
- Formatted columns with formulas
Usage:
uv run --no-project scripts/generate_bom.py --interactive
uv run --no-project scripts/generate_bom.py --json input.json --output bom.xlsx
uv run --no-project scripts/generate_bom.py --json input.json --format csv
"""
import argparse
import json
import csv
import sys
from dataclasses import dataclass, field
from typing import Optional, List
from pathlib import Path
from datetime import datetime
# Check for openpyxl
try:
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, Border, Side, PatternFill
from openpyxl.utils import get_column_letter
OPENPYXL_AVAILABLE = True
except ImportError:
OPENPYXL_AVAILABLE = False
print("Warning: openpyxl not installed. xlsx output disabled.", file=sys.stderr)
print("Install with: pip install openpyxl", file=sys.stderr)
# =============================================================================
# Component Database
# =============================================================================
COMPONENT_DATABASE = {
# Microcontrollers
"ESP32-DevKit": {
"description": "ESP32 Development Board with WiFi/BLE",
"category": "Microcontroller",
"unit_price_usd": 8.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B08D5ZD528",
"AliExpress": "https://aliexpress.com/item/32902307791.html"
}
},
"Arduino-Uno-R3": {
"description": "Arduino Uno R3 ATmega328P",
"category": "Microcontroller",
"unit_price_usd": 25.00,
"package": "Module",
"suppliers": {
"Arduino.cc": "https://store.arduino.cc/products/arduino-uno-rev3",
"Amazon": "https://amazon.com/dp/B008GRTSV6"
}
},
"Arduino-Nano": {
"description": "Arduino Nano V3 ATmega328P",
"category": "Microcontroller",
"unit_price_usd": 5.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B0713XK923"
}
},
"Raspberry-Pi-Pico": {
"description": "RP2040 Microcontroller Board",
"category": "Microcontroller",
"unit_price_usd": 4.00,
"package": "Module",
"suppliers": {
"Raspberry Pi": "https://www.raspberrypi.com/products/raspberry-pi-pico/",
"Adafruit": "https://www.adafruit.com/product/4864"
}
},
# Sensors
"BME280": {
"description": "Temperature/Humidity/Pressure Sensor (I2C)",
"category": "Sensor",
"unit_price_usd": 3.50,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07KR24P6P",
"AliExpress": "https://aliexpress.com/item/32849462236.html"
}
},
"DHT22": {
"description": "Temperature/Humidity Sensor",
"category": "Sensor",
"unit_price_usd": 4.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B073F472JL"
}
},
"MPU6050": {
"description": "6-Axis Accelerometer/Gyroscope (I2C)",
"category": "Sensor",
"unit_price_usd": 2.50,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B008BOPN40"
}
},
"HC-SR04": {
"description": "Ultrasonic Distance Sensor",
"category": "Sensor",
"unit_price_usd": 2.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07RGB4W8V"
}
},
"PIR-HC-SR501": {
"description": "PIR Motion Sensor",
"category": "Sensor",
"unit_price_usd": 1.50,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07KBWVJMP"
}
},
# Displays
"OLED-128x64": {
"description": "0.96\" OLED Display 128x64 (I2C)",
"category": "Display",
"unit_price_usd": 5.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B08ZY4YBHL",
"AliExpress": "https://aliexpress.com/item/32896971385.html"
}
},
"LCD-16x2-I2C": {
"description": "16x2 LCD Display with I2C Backpack",
"category": "Display",
"unit_price_usd": 6.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07S7PJYM6"
}
},
"TFT-1.8-ST7735": {
"description": "1.8\" TFT Display 128x160 (SPI)",
"category": "Display",
"unit_price_usd": 7.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B08FD643VZ"
}
},
# Communication
"NRF24L01": {
"description": "2.4GHz Wireless Transceiver",
"category": "Communication",
"unit_price_usd": 2.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B00O9O868G"
}
},
"LoRa-SX1276": {
"description": "LoRa Radio Module 868/915MHz",
"category": "Communication",
"unit_price_usd": 12.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07V51M4VC"
}
},
"GPS-NEO6M": {
"description": "GPS Module with Antenna",
"category": "Communication",
"unit_price_usd": 10.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07P8YMVNT"
}
},
# Power
"TP4056": {
"description": "LiPo Charging Module (USB-C)",
"category": "Power",
"unit_price_usd": 1.50,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07PZT3ZW2"
}
},
"LM2596-DC-DC": {
"description": "Buck Converter Adjustable DC-DC",
"category": "Power",
"unit_price_usd": 2.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B01GJ0SC2C"
}
},
"18650-Battery": {
"description": "18650 Li-ion Battery 3000mAh",
"category": "Power",
"unit_price_usd": 5.00,
"package": "Cell",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07SXRXVF3"
}
},
"LiPo-1000mAh": {
"description": "LiPo Battery 3.7V 1000mAh",
"category": "Power",
"unit_price_usd": 6.00,
"package": "Cell",
"suppliers": {
"Adafruit": "https://www.adafruit.com/product/258"
}
},
# Actuators
"Servo-SG90": {
"description": "Micro Servo Motor SG90",
"category": "Actuator",
"unit_price_usd": 3.00,
"package": "Unit",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07MLR1498"
}
},
"Relay-5V": {
"description": "5V Relay Module (1-Channel)",
"category": "Actuator",
"unit_price_usd": 2.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07BVXT1ZK"
}
},
"Motor-Driver-L298N": {
"description": "L298N Dual H-Bridge Motor Driver",
"category": "Actuator",
"unit_price_usd": 4.00,
"package": "Module",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07BK1QL5T"
}
},
# Passive Components
"Resistor-Kit": {
"description": "Resistor Assortment Kit (600pcs)",
"category": "Passive",
"unit_price_usd": 10.00,
"package": "Kit",
"suppliers": {
"Amazon": "https://amazon.com/dp/B08FD1XVL6"
}
},
"Capacitor-Kit": {
"description": "Ceramic Capacitor Kit (300pcs)",
"category": "Passive",
"unit_price_usd": 12.00,
"package": "Kit",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07PK78Y3H"
}
},
"LED-Kit": {
"description": "LED Assortment Kit (500pcs)",
"category": "Passive",
"unit_price_usd": 8.00,
"package": "Kit",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07RBHSM6X"
}
},
"Breadboard-830": {
"description": "830 Point Solderless Breadboard",
"category": "Passive",
"unit_price_usd": 5.00,
"package": "Unit",
"suppliers": {
"Amazon": "https://amazon.com/dp/B0135IQ0ZC"
}
},
"Jumper-Wires-Kit": {
"description": "Jumper Wire Kit M-M, M-F, F-F",
"category": "Passive",
"unit_price_usd": 7.00,
"package": "Kit",
"suppliers": {
"Amazon": "https://amazon.com/dp/B07GD2BWPY"
}
}
}
@dataclass
class BOMItem:
"""Single item in the BOM"""
reference: str # e.g., "U1", "R1"
name: str
description: str
quantity: int
category: str
unit_price_usd: float
package: str = ""
supplier: str = ""
supplier_link: str = ""
notes: str = ""
@property
def total_price(self) -> float:
return self.quantity * self.unit_price_usd
@dataclass
class BOM:
"""Complete Bill of Materials"""
project_name: str = "Untitled Project"
version: str = "1.0"
author: str = ""
date: str = field(default_factory=lambda: datetime.now().strftime("%Y-%m-%d"))
items: List[BOMItem] = field(default_factory=list)
@property
def total_cost(self) -> float:
return sum(item.total_price for item in self.items)
@property
def item_count(self) -> int:
return sum(item.quantity for item in self.items)
def add_from_database(self, name: str, quantity: int = 1,
reference: str = "", notes: str = "",
supplier: str = "") -> bool:
"""Add an item from the component database"""
if name not in COMPONENT_DATABASE:
return False
comp = COMPONENT_DATABASE[name]
# Select supplier
suppliers = comp.get("suppliers", {})
if supplier and supplier in suppliers:
selected_supplier = supplier
supplier_link = suppliers[supplier]
elif suppliers:
selected_supplier = list(suppliers.keys())[0]
supplier_link = suppliers[selected_supplier]
else:
selected_supplier = ""
supplier_link = ""
item = BOMItem(
reference=reference or f"X{len(self.items)+1}",
name=name,
description=comp["description"],
quantity=quantity,
category=comp["category"],
unit_price_usd=comp["unit_price_usd"],
package=comp.get("package", ""),
supplier=selected_supplier,
supplier_link=supplier_link,
notes=notes
)
self.items.append(item)
return True
def add_custom(self, name: str, description: str, quantity: int,
category: str, unit_price: float, **kwargs) -> None:
"""Add a custom item not in the database"""
item = BOMItem(
reference=kwargs.get("reference", f"X{len(self.items)+1}"),
name=name,
description=description,
quantity=quantity,
category=category,
unit_price_usd=unit_price,
package=kwargs.get("package", ""),
supplier=kwargs.get("supplier", ""),
supplier_link=kwargs.get("supplier_link", ""),
notes=kwargs.get("notes", "")
)
self.items.append(item)
def to_csv(self, filename: str) -> None:
"""Export to CSV format"""
with open(filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
# Header
writer.writerow([
"Reference", "Name", "Description", "Quantity",
"Category", "Unit Price ($)", "Total ($)",
"Package", "Supplier", "Supplier Link", "Notes"
])
# Items
for item in self.items:
writer.writerow([
item.reference, item.name, item.description, item.quantity,
item.category, f"{item.unit_price_usd:.2f}", f"{item.total_price:.2f}",
item.package, item.supplier, item.supplier_link, item.notes
])
# Total row
writer.writerow([])
writer.writerow(["", "", "", self.item_count, "", "", f"{self.total_cost:.2f}", "", "", "", ""])
def to_xlsx(self, filename: str) -> bool:
"""Export to Excel xlsx format with formatting"""
if not OPENPYXL_AVAILABLE:
print("Error: openpyxl not installed", file=sys.stderr)
return False
wb = Workbook()
ws = wb.active
ws.title = "BOM"
# Styles
header_font = Font(bold=True, color="FFFFFF")
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
currency_format = '$#,##0.00'
# Project info
ws['A1'] = "Project:"
ws['B1'] = self.project_name
ws['A2'] = "Version:"
ws['B2'] = self.version
ws['A3'] = "Author:"
ws['B3'] = self.author
ws['A4'] = "Date:"
ws['B4'] = self.date
ws['A1'].font = Font(bold=True)
ws['A2'].font = Font(bold=True)
ws['A3'].font = Font(bold=True)
ws['A4'].font = Font(bold=True)
# Header row
headers = ["Ref", "Name", "Description", "Qty", "Category",
"Unit Price", "Total", "Package", "Supplier", "Notes"]
header_row = 6
for col, header in enumerate(headers, 1):
cell = ws.cell(row=header_row, column=col, value=header)
cell.font = header_font
cell.fill = header_fill
cell.border = border
cell.alignment = Alignment(horizontal='center')
# Data rows
for row_num, item in enumerate(self.items, header_row + 1):
data = [
item.reference, item.name, item.description, item.quantity,
item.category, item.unit_price_usd, item.total_price,
item.package, item.supplier, item.notes
]
for col, value in enumerate(data, 1):
cell = ws.cell(row=row_num, column=col, value=value)
cell.border = border
# Format currency columns
if col in [6, 7]:
cell.number_format = currency_format
# Total row
total_row = header_row + len(self.items) + 2
ws.cell(row=total_row, column=3, value="TOTAL:").font = Font(bold=True)
ws.cell(row=total_row, column=4, value=self.item_count).font = Font(bold=True)
total_cell = ws.cell(row=total_row, column=7, value=self.total_cost)
total_cell.font = Font(bold=True)
total_cell.number_format = currency_format
# Add formula for dynamic total
formula_row = header_row + 1
end_row = header_row + len(self.items)
ws.cell(row=total_row, column=7,
value=f"=SUM(G{formula_row}:G{end_row})")
# Adjust column widths
column_widths = [8, 25, 40, 6, 15, 12, 12, 10, 15, 30]
for i, width in enumerate(column_widths, 1):
ws.column_dimensions[get_column_letter(i)].width = width
# Category summary sheet
ws2 = wb.create_sheet(title="By Category")
ws2['A1'] = "Category"
ws2['B1'] = "Items"
ws2['C1'] = "Total Cost"
ws2['A1'].font = Font(bold=True)
ws2['B1'].font = Font(bold=True)
ws2['C1'].font = Font(bold=True)
categories = {}
for item in self.items:
if item.category not in categories:
categories[item.category] = {"count": 0, "cost": 0}
categories[item.category]["count"] += item.quantity
categories[item.category]["cost"] += item.total_price
for row, (cat, data) in enumerate(sorted(categories.items()), 2):
ws2.cell(row=row, column=1, value=cat)
ws2.cell(row=row, column=2, value=data["count"])
cost_cell = ws2.cell(row=row, column=3, value=data["cost"])
cost_cell.number_format = currency_format
# Save
wb.save(filename)
return True
def to_markdown(self) -> str:
"""Generate markdown BOM table"""
lines = [
f"# Bill of Materials: {self.project_name}",
"",
f"**Version:** {self.version}",
f"**Author:** {self.author}",
f"**Date:** {self.date}",
"",
"## Components",
"",
"| Ref | Component | Description | Qty | Unit $ | Total $ | Supplier |",
"|-----|-----------|-------------|-----|--------|---------|----------|"
]
for item in self.items:
supplier_cell = f"[{item.supplier}]({item.supplier_link})" if item.supplier_link else item.supplier
lines.append(
f"| {item.reference} | {item.name} | {item.description} | "
f"{item.quantity} | ${item.unit_price_usd:.2f} | ${item.total_price:.2f} | {supplier_cell} |"
)
lines.extend([
"",
f"**Total Components:** {self.item_count}",
f"**Estimated Cost:** ${self.total_cost:.2f}",
"",
"## By Category",
""
])
categories = {}
for item in self.items:
if item.category not in categories:
categories[item.category] = {"count": 0, "cost": 0}
categories[item.category]["count"] += item.quantity
categories[item.category]["cost"] += item.total_price
for cat, data in sorted(categories.items()):
lines.append(f"- **{cat}:** {data['count']} items, ${data['cost']:.2f}")
return "\n".join(lines)
def to_dict(self) -> dict:
"""Export to dictionary/JSON"""
return {
"project_name": self.project_name,
"version": self.version,
"author": self.author,
"date": self.date,
"items": [
{
"reference": item.reference,
"name": item.name,
"description": item.description,
"quantity": item.quantity,
"category": item.category,
"unit_price_usd": item.unit_price_usd,
"total_price_usd": item.total_price,
"package": item.package,
"supplier": item.supplier,
"supplier_link": item.supplier_link,
"notes": item.notes
}
for item in self.items
],
"summary": {
"total_items": self.item_count,
"total_cost_usd": round(self.total_cost, 2)
}
}
@classmethod
def from_json(cls, filepath: str) -> 'BOM':
"""Load BOM from JSON file"""
with open(filepath, 'r') as f:
data = json.load(f)
bom = cls(
project_name=data.get("project_name", "Untitled"),
version=data.get("version", "1.0"),
author=data.get("author", ""),
date=data.get("date", datetime.now().strftime("%Y-%m-%d"))
)
for item in data.get("items", []):
if item.get("name") in COMPONENT_DATABASE:
bom.add_from_database(
name=item["name"],
quantity=item.get("quantity", 1),
reference=item.get("reference", ""),
notes=item.get("notes", ""),
supplier=item.get("supplier", "")
)
else:
bom.add_custom(
name=item.get("name", "Unknown"),
description=item.get("description", ""),
quantity=item.get("quantity", 1),
category=item.get("category", "Other"),
unit_price=item.get("unit_price_usd", 0),
reference=item.get("reference", ""),
supplier=item.get("supplier", ""),
supplier_link=item.get("supplier_link", ""),
notes=item.get("notes", "")
)
return bom
def interactive_mode():
"""Run BOM generator in interactive mode"""
print("=" * 60)
print("BOM Generator - Interactive Mode")
print("=" * 60)
print()
project_name = input("Project name [My Project]: ").strip() or "My Project"
version = input("Version [1.0]: ").strip() or "1.0"
author = input("Author: ").strip()
bom = BOM(project_name=project_name, version=version, author=author)
print("\nAvailable components in database:")
for i, name in enumerate(COMPONENT_DATABASE.keys(), 1):
print(f" {i:2}. {name}")
print("\nEnter components (empty line to finish):")
print("Format: <name or number> [quantity] [reference]")
while True:
line = input("\nComponent: ").strip()
if not line:
break
parts = line.split()
comp_input = parts[0]
quantity = int(parts[1]) if len(parts) > 1 else 1
reference = parts[2] if len(parts) > 2 else ""
# Handle numeric input
if comp_input.isdigit():
idx = int(comp_input) - 1
names = list(COMPONENT_DATABASE.keys())
if 0 <= idx < len(names):
comp_input = names[idx]
else:
print("Invalid number")
continue
if bom.add_from_database(comp_input, quantity, reference):
comp = COMPONENT_DATABASE[comp_input]
print(f" Added: {quantity}x {comp_input} (${comp['unit_price_usd']:.2f} each)")
else:
# Custom component
print(f" '{comp_input}' not in database. Enter details:")
desc = input(" Description: ").strip()
category = input(" Category: ").strip() or "Other"
price = float(input(" Unit price ($): ").strip() or "0")
bom.add_custom(comp_input, desc, quantity, category, price, reference=reference)
print(f" Added custom: {quantity}x {comp_input}")
# Show summary
print("\n" + "=" * 60)
print(bom.to_markdown())
# Export
print("\nExport options:")
print("1. Excel (.xlsx)")
print("2. CSV (.csv)")
print("3. Markdown (.md)")
print("4. JSON (.json)")
print("5. All formats")
choice = input("\nExport format [1]: ").strip() or "1"
base_name = project_name.replace(" ", "_")
if choice in ["1", "5"]:
if bom.to_xlsx(f"{base_name}_BOM.xlsx"):
print(f"Saved: {base_name}_BOM.xlsx")
if choice in ["2", "5"]:
bom.to_csv(f"{base_name}_BOM.csv")
print(f"Saved: {base_name}_BOM.csv")
if choice in ["3", "5"]:
with open(f"{base_name}_BOM.md", 'w') as f:
f.write(bom.to_markdown())
print(f"Saved: {base_name}_BOM.md")
if choice in ["4", "5"]:
with open(f"{base_name}_BOM.json", 'w') as f:
json.dump(bom.to_dict(), f, indent=2)
print(f"Saved: {base_name}_BOM.json")
def main():
parser = argparse.ArgumentParser(description="BOM Generator for Embedded Projects")
parser.add_argument("--interactive", "-i", action="store_true", help="Interactive mode")
parser.add_argument("--json", "-j", type=str, help="Load from JSON file")
parser.add_argument("--output", "-o", type=str, help="Output filename")
parser.add_argument("--format", "-f", type=str, choices=["xlsx", "csv", "md", "json"],
default="xlsx", help="Output format")
parser.add_argument("--list", "-l", action="store_true", help="List component database")
args = parser.parse_args()
if args.list:
print("Component Database:")
print("-" * 80)
for name, data in COMPONENT_DATABASE.items():
print(f"{name:25} ${data['unit_price_usd']:6.2f} {data['description']}")
return
if args.interactive:
interactive_mode()
return
if args.json:
bom = BOM.from_json(args.json)
output = args.output or f"{bom.project_name.replace(' ', '_')}_BOM.{args.format}"
if args.format == "xlsx":
bom.to_xlsx(output)
elif args.format == "csv":
bom.to_csv(output)
elif args.format == "md":
with open(output, 'w') as f:
f.write(bom.to_markdown())
elif args.format == "json":
with open(output, 'w') as f:
json.dump(bom.to_dict(), f, indent=2)
print(f"Generated: {output}")
return
parser.print_help()
if __name__ == "__main__":
main()