
Power Budget Calculator
- 78 installs
- 19 repo stars
- Updated May 26, 2026
- wedsamuel1230/arduino-skills
Helps with ai & agent building tasks.
About
power-budget-calculator is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- power-budget-calculator
- AI & Agent Building
- AI-coding skill
Power Budget Calculator by the numbers
- 78 all-time installs (skills.sh)
- +5 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #5,313 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 power-budget-calculatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 78 |
|---|---|
| repo stars | ★ 19 |
| Last updated | May 26, 2026 |
| Repository | wedsamuel1230/arduino-skills ↗ |
What it does
Helps with ai & agent building tasks.
Files
Power Budget Calculator
Estimates power consumption and battery life for embedded projects.
Resources
- scripts/calculate_power.py - Python calculator with component database, duty cycle support, battery life estimation
- references/component-database.md - Comprehensive current draw reference from datasheets
- assets/example-project.json - Sample project configuration
Quick Start
Run the calculator interactively:
uv run --no-project scripts/calculate_power.py --interactiveOr calculate for a single component:
uv run --no-project scripts/calculate_power.py --component ESP32 --mode deep_sleep --duty 95Load a project configuration:
uv run --no-project scripts/calculate_power.py --json assets/example-project.json --output report.mdList all components in database:
uv run --no-project scripts/calculate_power.py --listWhen to Use
- "How long will my battery last?"
- "What size battery do I need?"
- "How much current does my project draw?"
- Planning battery-powered projects
- Optimizing for low power
Workflow
Step 1: Gather Component List
Ask user for: 1. Microcontroller (model and operating mode) 2. All sensors and modules 3. Displays 4. Actuators (motors, LEDs, relays) 5. Communication (WiFi, Bluetooth, LoRa) 6. Operating profile (always on vs. duty cycle)
Step 2: Calculate Power Budget
Run scripts/calculate_power.py --interactive for guided calculation, or use this template:
# Power Budget: [Project Name]
## Operating Conditions
- Supply Voltage: [V]
- Operating Modes: [Active/Sleep cycles]
- Duty Cycle: [X% active, Y% sleep]
## Component Current Draw
### Always-On Components
| Component | Voltage | Current | Power | Notes |
|-----------|---------|---------|-------|-------|
| [name] | [V] | [mA] | [mW] | [conditions] |
| ... | ... | ... | ... | ... |
| **Subtotal** | - | **[mA]** | **[mW]** | - |
### Active Mode Only
| Component | Voltage | Current | Power | Duty % |
|-----------|---------|---------|-------|--------|
| [name] | [V] | [mA] | [mW] | [%] |
| ... | ... | ... | ... | ... |
| **Subtotal** | - | **[mA]** | **[mW]** | - |
### Peak Current Events
| Event | Current | Duration | Frequency |
|-------|---------|----------|-----------|
| [WiFi TX] | [mA] | [ms] | [per hour] |
| ... | ... | ... | ... |
## Power Calculations
### Average CurrentI_avg = (I_active × t_active + I_sleep × t_sleep) / t_total I_avg = ([X]mA × [Y]s + [Z]mA × [W]s) / [T]s I_avg = [result] mA
### Battery Life EstimateCapacity: [X] mAh Efficiency factor: 0.8 (80% usable) Effective capacity: [X × 0.8] mAh
Runtime = Effective capacity / I_avg Runtime = [X] mAh / [Y] mA Runtime = [Z] hours ([days] days)
## Recommendations
- [Power optimization suggestions]
- [Battery type recommendation]
- [Sleep mode configuration]Current Draw Database
Microcontrollers
Arduino (ATmega328P @ 5V)
| Mode | Current | Notes |
|---|---|---|
| Active (16MHz) | 15-20mA | No peripherals |
| Idle | 6mA | CPU stopped, peripherals on |
| Power-down | 0.1µA | Only WDT/external interrupt |
| With USB-Serial | +50mA | CH340/FTDI chip always on |
ESP32 (WROOM-32)
| Mode | Current | Notes |
|---|---|---|
| Active (WiFi TX) | 160-260mA | Peak during transmission |
| Active (WiFi RX) | 95-100mA | Listening |
| Active (no WiFi) | 20-68mA | CPU only |
| Modem-sleep | 20mA | WiFi off, CPU on |
| Light-sleep | 0.8mA | CPU paused |
| Deep-sleep | 10µA | RTC only |
| Hibernation | 5µA | RTC off |
ESP32-C3
| Mode | Current | Notes |
|---|---|---|
| Active (WiFi TX) | 320mA peak | Brief spikes |
| Active (BLE) | 50-100mA | During advertising |
| Active (no radio) | 25-35mA | CPU at 160MHz |
| Light-sleep | 130µA | Auto wake |
| Deep-sleep | 5µA | RTC on |
RP2040 (Pico)
| Mode | Current | Notes |
|---|---|---|
| Active (both cores) | 25-50mA | Depends on clock |
| Single core | 15-25mA | One core dormant |
| Dormant | 0.18mA | Wake on GPIO/RTC |
| Sleep | 1.3mA |
RP2040 (Pico W)
| Mode | Current | Notes |
|---|---|---|
| WiFi active | 50-150mA | During TX |
| WiFi idle | 30-40mA | Connected, listening |
| WiFi off | 25-50mA | Radio disabled |
Sensors
| Sensor | Active | Sleep | Notes |
|---|---|---|---|
| DHT22 | 1.5mA | 50µA | During read |
| BME280 | 0.35mA | 0.1µA | Forced mode |
| BMP280 | 0.27mA | 0.1µA | |
| MPU6050 | 3.8mA | 5µA | All axes |
| DS18B20 | 1.5mA | 1µA | During conversion |
| HC-SR04 | 15mA | 2mA | No true sleep |
| VL53L0X | 19mA | 5µA | During ranging |
| MAX30102 | 600µA | 0.7µA | Low power mode |
| BH1750 | 120µA | 1µA | Power down mode |
| GPS (NEO-6M) | 45mA | 11mA | Backup mode |
Displays
| Display | Active | Off/Sleep | Notes |
|---|---|---|---|
| 16x2 LCD (backlight) | 80-120mA | 1mA | Backlight dominates |
| 16x2 LCD (no backlight) | 1-2mA | 1mA | |
| SSD1306 OLED 128x64 | 8-20mA | 10µA | Depends on content |
| SSD1306 OLED 128x32 | 5-15mA | 10µA | |
| ST7735 TFT | 20-40mA | 20mA | Backlight always on |
| ILI9341 TFT | 50-80mA | 50mA | |
| E-Paper 2.9" | 8mA | 0µA | Only during refresh |
Communication Modules
| Module | TX Peak | RX/Idle | Sleep | Notes |
|---|---|---|---|---|
| ESP8266 (module) | 170mA | 70mA | 20µA | Deep sleep |
| NRF24L01 | 11.3mA | 13.5mA | 0.9µA | |
| HC-05 Bluetooth | 40mA | 8mA | 2mA | |
| RFM95 LoRa | 120mA | 10mA | 0.2µA | |
| SIM800L GSM | 2A peak | 15mA | 1mA | Needs big cap! |
Actuators
| Component | Current | Notes |
|---|---|---|
| LED (typical) | 10-20mA | Through resistor |
| RGB LED (each color) | 20mA | Per channel |
| WS2812B NeoPixel | 60mA max | Full white |
| SG90 Servo (idle) | 10mA | No load |
| SG90 Servo (moving) | 200-500mA | Under load |
| MG996R Servo | 500-900mA | Under load |
| Small DC motor | 100-500mA | Varies with load |
| 5V Relay module | 70-100mA | Coil energized |
| Buzzer (passive) | 30mA |
Sleep Mode Configurations
ESP32 Deep Sleep Template
#include <esp_sleep.h>
#define uS_TO_S_FACTOR 1000000ULL
#define TIME_TO_SLEEP 60 // seconds
void setup() {
// Do measurements here
// Configure wake-up source
esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
// Enter deep sleep
esp_deep_sleep_start();
}
void loop() {
// Never reached
}Arduino Power-Down Template
#include <avr/sleep.h>
#include <avr/wdt.h>
void enterSleep() {
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sleep_mode(); // Sleep here
sleep_disable();
}
// Wake on watchdog or external interrupt
ISR(WDT_vect) {
// Wake up
}Power Optimization Checklist
Hardware
- [ ] Remove power LED (saves 5-10mA)
- [ ] Remove USB-Serial chip for battery operation
- [ ] Use LDO with low quiescent current (<10µA)
- [ ] Add MOSFET to power-gate high-current peripherals
- [ ] Use pull-downs on unused pins
- [ ] Disable ADC when not reading
Software
- [ ] Use sleep modes between readings
- [ ] Reduce clock speed if possible
- [ ] Batch data and transmit less frequently
- [ ] Turn off WiFi/BLE when not needed
- [ ] Disable brown-out detector in deep sleep
- [ ] Use interrupts instead of polling
Design Patterns
Power Gating:
VCC
│
[P-MOSFET] ← GPIO (LOW = ON)
│
[Sensor/Module]
│
GNDDuty Cycling Formula:
I_average = (I_active × T_active + I_sleep × T_sleep) / T_total
Example: Sensor reads every 5 minutes
- Active: 50mA for 2 seconds
- Sleep: 10µA for 298 seconds
I_avg = (50mA × 2s + 0.01mA × 298s) / 300s
I_avg = (100 + 2.98) / 300
I_avg = 0.34mA
With 2000mAh battery:
Runtime = 2000mAh × 0.8 / 0.34mA = 4706 hours = 196 daysBattery Reference
Common Battery Types
| Type | Voltage | Typical Capacity | Notes |
|---|---|---|---|
| CR2032 | 3V | 220mAh | Very low current only |
| 2x AA Alkaline | 3V | 2500mAh | Good for low power |
| 3x AA Alkaline | 4.5V | 2500mAh | Direct to 5V systems |
| 4x AA Alkaline | 6V | 2500mAh | With LDO to 5V |
| 18650 Li-ion | 3.7V | 2000-3500mAh | Rechargeable, popular |
| LiPo 1S | 3.7V | Various | Flat, light |
| 9V Alkaline | 9V | 500mAh | Poor energy density |
Voltage Over Discharge
18650/LiPo Discharge Curve:
Capacity% Voltage
─────────────────────
100% 4.2V
80% 3.9V
50% 3.7V
20% 3.5V
0% 3.0V (cut-off!)
Never discharge below 3.0V - damages cell!Alkaline AA Discharge:
Capacity% Voltage (per cell)
─────────────────────────────
100% 1.5V
50% 1.2V
0% 0.9V
Many devices stop working below 1.1V/cellQuick Calculator
Enter your values:
Always-on current: ______ mA
Active current: ______ mA
Active time: ______ seconds
Sleep current: ______ mA
Sleep time: ______ seconds
Battery capacity: ______ mAh
Average current = (Active_mA × Active_s + Sleep_mA × Sleep_s) / Total_s
= (_____ × _____ + _____ × _____) / _____
= _____ mA
Runtime = Capacity × 0.8 / Average_current
= _____ × 0.8 / _____
= _____ hours
= _____ days{
"name": "power-budget-calculator",
"metadata": {
"description": "Calculates total power consumption and battery life for Arduino/ESP32/RP2040 projects. Use when user asks about battery life, power requirements, current draw, or needs to estimate runtime. Includes sleep mode analysis, power optimization tips, and battery sizing recommendations.",
"version": "0.8.0",
"license": "MIT",
"author": "arduino-skills contributors",
"tags": ["power-management", "battery-life", "energy-efficiency", "maker", "electronics"],
"category": "maker-tools"
},
"plugins": [
{
"name": "power-budget-calculator",
"description": "Calculate power consumption, battery life, and optimization strategies for embedded projects",
"enabled": true
}
]
}
{
"project_name": "Weather Station Example",
"system_voltage": 3.3,
"safety_margin_percent": 20,
"components": [
{
"name": "ESP32",
"mode": "modem_sleep",
"duty_cycle_percent": 90,
"quantity": 1,
"notes": "WiFi transmission 10% of time"
},
{
"name": "ESP32",
"mode": "active_wifi_tx",
"duty_cycle_percent": 10,
"quantity": 1,
"notes": "Sending data every 10 minutes"
},
{
"name": "BME280",
"mode": "measuring",
"duty_cycle_percent": 1,
"quantity": 1,
"notes": "Quick measurement"
},
{
"name": "OLED_128x64",
"mode": "half_pixels",
"duty_cycle_percent": 50,
"quantity": 1,
"notes": "Display on half the time"
}
]
}
Component Current Draw Database
Comprehensive reference for common embedded system components. Data sourced from manufacturer datasheets - always verify with actual datasheet for your specific component.
Table of Contents
---
Microcontrollers
ESP32 (3.3V)
| Mode | Current (mA) | Notes |
|---|---|---|
| Active (WiFi TX) | 260 | Max during transmission |
| Active (WiFi RX) | 95 | Receiving data |
| Active (BT) | 130 | Bluetooth active |
| Active (CPU only) | 80 | 240MHz, no radio |
| Modem Sleep | 20 | CPU active, modem off |
| Light Sleep | 0.8 | RTC + ULP running |
| Deep Sleep | 0.01 | RTC timer only |
| Hibernation | 0.005 | RTC off |
Datasheet Reference: ESP32 Technical Reference Manual, Section 8
ESP8266 (3.3V)
| Mode | Current (mA) | Notes |
|---|---|---|
| Active (TX 802.11b) | 170 | Peak during TX |
| Active (RX) | 56 | Receiving |
| Modem Sleep | 15 | CPU active |
| Light Sleep | 0.5 | |
| Deep Sleep | 0.02 |
Arduino Uno (ATmega328P @ 5V)
| Mode | Current (mA) | Notes |
|---|---|---|
| Active | 45 | Including regulator |
| Idle | 35 | CPU halted |
| Power Down | 0.1 | External interrupt wake |
Note: Raw ATmega328P @ 3.3V/8MHz uses ~5mA active
Arduino Nano (5V)
| Mode | Current (mA) | Notes |
|---|---|---|
| Active | 20 | With CH340 USB chip |
| Idle | 15 | |
| Power Down | 0.006 | CH340 draws power when USB connected |
Raspberry Pi Pico (RP2040 @ 3.3V)
| Mode | Current (mA) | Notes |
|---|---|---|
| Active (Full) | 93 | Both cores @ 133MHz |
| Active (Normal) | 24 | Single core, lower clock |
| Sleep | 1.3 | |
| Dormant | 0.18 |
ATtiny85 (3.3V)
| Mode | Current (mA) | Notes |
|---|---|---|
| Active @ 8MHz | 5 | |
| Active @ 1MHz | 1.5 | |
| Idle | 0.5 | |
| Power Down | 0.0001 | WDT disabled |
---
Sensors
Temperature/Humidity
| Sensor | Voltage | Measuring (mA) | Sleep (µA) | Notes |
|---|---|---|---|---|
| BME280 | 3.3 | 0.714 | 0.1 | Temp/Hum/Pressure |
| BME680 | 3.3 | 3.1 | 0.15 | + Gas sensor |
| DHT22 | 3.3-5 | 1.5 | 50 | 2s between reads |
| DHT11 | 3.3-5 | 0.5 | 60 | Less accurate |
| SHT31 | 3.3 | 0.8 | 0.2 | High precision |
| DS18B20 | 3.3-5 | 1.5 | 750 (nA) | 1-Wire |
Motion/Position
| Sensor | Voltage | Active (mA) | Sleep (µA) | Notes |
|---|---|---|---|---|
| MPU6050 | 3.3 | 3.8 | 5 | 6-axis IMU |
| MPU9250 | 3.3 | 3.7 | 8 | 9-axis IMU |
| ADXL345 | 3.3 | 0.14 | 0.1 | 3-axis accel |
| BNO055 | 3.3 | 12.3 | 40 | 9-axis + fusion |
Distance
| Sensor | Voltage | Active (mA) | Standby (mA) | Notes |
|---|---|---|---|---|
| HC-SR04 | 5 | 15 | 2 | Ultrasonic 2-400cm |
| VL53L0X | 3.3 | 19 | 0.005 | ToF laser 2m |
| VL53L1X | 3.3 | 20 | 0.005 | ToF laser 4m |
Light
| Sensor | Voltage | Active (mA) | Sleep (µA) | Notes |
|---|---|---|---|---|
| BH1750 | 3.3 | 0.12 | 0.01 | Lux meter |
| TSL2591 | 3.3 | 0.4 | 0.5 | High dynamic range |
| VEML7700 | 3.3 | 0.045 | 0.5 | Ambient light |
Pressure/Altitude
| Sensor | Voltage | Active (mA) | Sleep (µA) | Notes |
|---|---|---|---|---|
| BMP180 | 3.3 | 0.65 | 3 | Basic pressure |
| BMP280 | 3.3 | 0.71 | 0.1 | Weather-grade |
| MS5611 | 3.3 | 1.4 | 0.14 | High resolution |
---
Displays
| Display | Voltage | Active (mA) | Sleep (mA) | Notes |
|---|---|---|---|---|
| OLED 0.96" 128x64 | 3.3 | 10-20 | 0.01 | Depends on pixels lit |
| OLED 1.3" 128x64 | 3.3 | 15-30 | 0.01 | |
| LCD 16x2 (I2C) | 5 | 25 (backlight on) | 2 | |
| LCD 20x4 (I2C) | 5 | 30 | 3 | |
| TFT 1.8" ST7735 | 3.3 | 40 | 0.5 | With backlight |
| TFT 2.4" ILI9341 | 3.3 | 80 | 1 | Touch adds 5mA |
| E-Paper 2.9" | 3.3 | 40 (refresh) | 0.001 | Static display |
E-Paper Note: Only draws power during refresh (~2 seconds). Static display essentially zero power.
---
Communication Modules
Wireless
| Module | Voltage | TX (mA) | RX (mA) | Sleep (µA) | Notes |
|---|---|---|---|---|---|
| NRF24L01 | 3.3 | 11.3 | 13.5 | 0.9 | 2.4GHz |
| NRF24L01+PA+LNA | 3.3 | 115 | 45 | 0.9 | Long range |
| LoRa SX1276 | 3.3 | 90-120 | 12 | 0.2 | 433/868/915MHz |
| HC-05 | 3.3 | 40 | 8 | - | Bluetooth Classic |
| HM-10 | 3.3 | 8.5 | 8.5 | 0.4 | BLE |
| RFM69 | 3.3 | 45 | 16 | 0.1 | 433/868/915MHz |
GPS
| Module | Voltage | Acquisition (mA) | Tracking (mA) | Sleep (mA) | Notes |
|---|---|---|---|---|---|
| NEO-6M | 3.3-5 | 45 | 35 | 11 | u-blox |
| NEO-7M | 3.3 | 40 | 30 | 7 | |
| NEO-M8N | 3.3 | 35 | 25 | 5 | |
| BN-220 | 3.3-5 | 50 | 25 | - | GPS + GLONASS |
Wired
| Interface | Typical Current | Notes |
|---|---|---|
| MAX485 (RS485) | 0.5mA idle, 1.2mA TX | |
| MCP2515 (CAN) | 5mA | |
| W5500 (Ethernet) | 132mA |
---
Actuators
Servos
| Servo | Voltage | Moving (mA) | Holding (mA) | Idle (mA) |
|---|---|---|---|---|
| SG90 (micro) | 5 | 250-500 | 10 | 5 |
| MG996R | 5 | 500-900 | 50 | 10 |
| MG90S | 5 | 350-650 | 20 | 5 |
Motors
| Type | Typical Range | Notes |
|---|---|---|
| Small DC motor | 100-500mA | No load to load |
| 28BYJ-48 stepper | 240mA | Per phase |
| NEMA17 stepper | 400-1700mA | Per phase |
Other
| Actuator | Current | Notes |
|---|---|---|
| 5V Relay | 70-90mA | Coil energized |
| Piezo buzzer | 5-30mA | Depends on frequency |
| LED (standard) | 5-20mA | Per LED |
| WS2812B LED | 60mA max | Full white, per LED |
---
Power Regulators
Regulator quiescent current (overhead):
| Regulator | Quiescent Current | Notes |
|---|---|---|
| AMS1117-3.3 | 5mA | Linear, common |
| HT7333 | 4µA | Ultra-low dropout |
| MCP1700 | 1.6µA | Low power |
| RT9013 | 25µA | Low dropout |
| AP2112 | 55µA |
Note: Linear regulators waste power as heat: P_waste = (V_in - V_out) × I_load
---
Measurement Tips
1. Always measure actual current - Datasheet values are typical, your component may vary 2. Consider startup current - Many devices draw 2-10x more during initialization 3. Decoupling capacitors - Add 100µF near power-hungry modules 4. Separate power rails - Keep noisy loads (motors) separate from sensitive circuits 5. Use a current meter - USB power meters or inline multimeters
#!/usr/bin/env python3
"""
Power Budget Calculator for Embedded Systems
Calculates total power consumption, duty cycle impact, and battery runtime
for Arduino/ESP32/RP2040 projects.
Usage:
uv run --no-project scripts/calculate_power.py --interactive
uv run --no-project scripts/calculate_power.py --json input.json
uv run --no-project scripts/calculate_power.py --component ESP32 --mode active --duty 10
Output: JSON report with power analysis and battery recommendations
"""
import argparse
import json
import sys
from dataclasses import dataclass, field, asdict
from typing import Optional
from pathlib import Path
# =============================================================================
# Component Database - Current draw in mA at typical voltages
# =============================================================================
COMPONENT_DATABASE = {
# Microcontrollers
"ESP32": {
"voltage": 3.3,
"modes": {
"active_wifi_tx": 260,
"active_wifi_rx": 95,
"active_bt": 130,
"active_cpu": 80,
"modem_sleep": 20,
"light_sleep": 0.8,
"deep_sleep": 0.01,
"hibernation": 0.005
},
"typical_mode": "active_wifi_tx"
},
"ESP8266": {
"voltage": 3.3,
"modes": {
"active_wifi_tx": 170,
"active_wifi_rx": 56,
"modem_sleep": 15,
"light_sleep": 0.5,
"deep_sleep": 0.02
},
"typical_mode": "active_wifi_tx"
},
"Arduino_Uno": {
"voltage": 5.0,
"modes": {
"active": 45,
"idle": 35,
"power_down": 0.1
},
"typical_mode": "active"
},
"Arduino_Nano": {
"voltage": 5.0,
"modes": {
"active": 20,
"idle": 15,
"power_down": 0.006
},
"typical_mode": "active"
},
"RP2040": {
"voltage": 3.3,
"modes": {
"active_full": 93,
"active_normal": 24,
"dormant": 0.18,
"sleep": 1.3
},
"typical_mode": "active_normal"
},
"ATtiny85": {
"voltage": 3.3,
"modes": {
"active_8mhz": 5,
"active_1mhz": 1.5,
"idle": 0.5,
"power_down": 0.0001
},
"typical_mode": "active_1mhz"
},
# Sensors
"BME280": {
"voltage": 3.3,
"modes": {
"measuring": 0.714,
"sleep": 0.0001
},
"typical_mode": "measuring"
},
"DHT22": {
"voltage": 3.3,
"modes": {
"measuring": 1.5,
"standby": 0.05
},
"typical_mode": "measuring"
},
"MPU6050": {
"voltage": 3.3,
"modes": {
"gyro_accel": 3.8,
"accel_only": 0.5,
"sleep": 0.005
},
"typical_mode": "gyro_accel"
},
"BMP180": {
"voltage": 3.3,
"modes": {
"measuring": 0.65,
"standby": 0.003
},
"typical_mode": "measuring"
},
"HC_SR04": {
"voltage": 5.0,
"modes": {
"active": 15,
"standby": 2
},
"typical_mode": "active"
},
"VL53L0X": {
"voltage": 3.3,
"modes": {
"ranging": 19,
"standby": 0.005
},
"typical_mode": "ranging"
},
# Displays
"OLED_128x64": {
"voltage": 3.3,
"modes": {
"full_on": 20,
"half_pixels": 10,
"off": 0.01
},
"typical_mode": "half_pixels"
},
"LCD_16x2_I2C": {
"voltage": 5.0,
"modes": {
"backlight_on": 25,
"backlight_off": 2
},
"typical_mode": "backlight_on"
},
"TFT_1_8": {
"voltage": 3.3,
"modes": {
"active": 40,
"sleep": 0.5
},
"typical_mode": "active"
},
"E_Paper_2_9": {
"voltage": 3.3,
"modes": {
"refresh": 40,
"static": 0.001
},
"typical_mode": "static"
},
# Communication
"NRF24L01": {
"voltage": 3.3,
"modes": {
"tx": 11.3,
"rx": 13.5,
"standby": 0.022,
"power_down": 0.0009
},
"typical_mode": "rx"
},
"LoRa_SX1276": {
"voltage": 3.3,
"modes": {
"tx_20dbm": 120,
"tx_17dbm": 90,
"rx": 12,
"sleep": 0.0002
},
"typical_mode": "rx"
},
"GPS_NEO6M": {
"voltage": 3.3,
"modes": {
"acquisition": 45,
"tracking": 35,
"power_save": 11
},
"typical_mode": "tracking"
},
# Actuators
"Servo_SG90": {
"voltage": 5.0,
"modes": {
"moving": 250,
"holding": 10,
"idle": 5
},
"typical_mode": "holding"
},
"DC_Motor_Small": {
"voltage": 5.0,
"modes": {
"full_load": 500,
"no_load": 100,
"stall": 1000
},
"typical_mode": "no_load"
},
"Relay_5V": {
"voltage": 5.0,
"modes": {
"energized": 80,
"off": 0
},
"typical_mode": "energized"
},
"LED": {
"voltage": 3.3,
"modes": {
"on_20ma": 20,
"on_5ma": 5,
"off": 0
},
"typical_mode": "on_5ma"
},
"WS2812B_LED": {
"voltage": 5.0,
"modes": {
"white_full": 60,
"color_avg": 20,
"off": 1
},
"typical_mode": "color_avg"
}
}
# Battery capacities in mAh
BATTERY_DATABASE = {
"CR2032": {"capacity": 220, "voltage": 3.0, "chemistry": "Lithium", "rechargeable": False},
"AA_Alkaline": {"capacity": 2500, "voltage": 1.5, "chemistry": "Alkaline", "rechargeable": False},
"AA_NiMH": {"capacity": 2000, "voltage": 1.2, "chemistry": "NiMH", "rechargeable": True},
"AAA_Alkaline": {"capacity": 1000, "voltage": 1.5, "chemistry": "Alkaline", "rechargeable": False},
"18650_LiPo": {"capacity": 3000, "voltage": 3.7, "chemistry": "Li-ion", "rechargeable": True},
"LiPo_1000": {"capacity": 1000, "voltage": 3.7, "chemistry": "LiPo", "rechargeable": True},
"LiPo_2000": {"capacity": 2000, "voltage": 3.7, "chemistry": "LiPo", "rechargeable": True},
"LiPo_5000": {"capacity": 5000, "voltage": 3.7, "chemistry": "LiPo", "rechargeable": True},
"9V_Alkaline": {"capacity": 550, "voltage": 9.0, "chemistry": "Alkaline", "rechargeable": False},
"LiFePO4_1500": {"capacity": 1500, "voltage": 3.2, "chemistry": "LiFePO4", "rechargeable": True}
}
@dataclass
class ComponentEntry:
"""Single component in the power budget"""
name: str
mode: str
current_ma: float
voltage: float
duty_cycle_percent: float = 100.0
quantity: int = 1
notes: str = ""
@property
def effective_current_ma(self) -> float:
"""Current adjusted for duty cycle"""
return self.current_ma * (self.duty_cycle_percent / 100.0) * self.quantity
@property
def power_mw(self) -> float:
"""Power in milliwatts"""
return self.effective_current_ma * self.voltage
@dataclass
class PowerBudget:
"""Complete power budget analysis"""
project_name: str = "Untitled Project"
system_voltage: float = 3.3
components: list = field(default_factory=list)
safety_margin_percent: float = 20.0
@property
def total_current_ma(self) -> float:
"""Total current draw in mA"""
return sum(c.effective_current_ma for c in self.components)
@property
def total_current_with_margin_ma(self) -> float:
"""Total current with safety margin"""
return self.total_current_ma * (1 + self.safety_margin_percent / 100.0)
@property
def total_power_mw(self) -> float:
"""Total power in milliwatts"""
return sum(c.power_mw for c in self.components)
def calculate_battery_life(self, battery_name: str) -> dict:
"""Calculate runtime for a specific battery"""
if battery_name not in BATTERY_DATABASE:
return {"error": f"Unknown battery: {battery_name}"}
battery = BATTERY_DATABASE[battery_name]
capacity = battery["capacity"]
# Peukert effect approximation (capacity reduces at higher currents)
current = self.total_current_with_margin_ma
if current > capacity * 0.2: # High drain
effective_capacity = capacity * 0.7
elif current > capacity * 0.1: # Medium drain
effective_capacity = capacity * 0.85
else: # Low drain
effective_capacity = capacity * 0.95
hours = effective_capacity / current if current > 0 else float('inf')
return {
"battery": battery_name,
"capacity_mah": capacity,
"effective_capacity_mah": round(effective_capacity, 1),
"runtime_hours": round(hours, 2),
"runtime_days": round(hours / 24, 2),
"suitable": hours > 1, # At least 1 hour runtime
"peukert_note": "High drain reduces effective capacity" if current > capacity * 0.1 else None
}
def recommend_batteries(self, min_hours: float = 24) -> list:
"""Recommend batteries that meet minimum runtime"""
recommendations = []
for battery_name in BATTERY_DATABASE:
result = self.calculate_battery_life(battery_name)
if result.get("runtime_hours", 0) >= min_hours:
recommendations.append(result)
# Sort by runtime descending
recommendations.sort(key=lambda x: x["runtime_hours"], reverse=True)
return recommendations
def to_dict(self) -> dict:
"""Export to dictionary"""
return {
"project_name": self.project_name,
"system_voltage": self.system_voltage,
"safety_margin_percent": self.safety_margin_percent,
"components": [
{
"name": c.name,
"mode": c.mode,
"current_ma": c.current_ma,
"voltage": c.voltage,
"duty_cycle_percent": c.duty_cycle_percent,
"quantity": c.quantity,
"effective_current_ma": round(c.effective_current_ma, 3),
"power_mw": round(c.power_mw, 3),
"notes": c.notes
}
for c in self.components
],
"totals": {
"total_current_ma": round(self.total_current_ma, 3),
"total_current_with_margin_ma": round(self.total_current_with_margin_ma, 3),
"total_power_mw": round(self.total_power_mw, 3)
}
}
def to_json(self) -> str:
"""Export to JSON string"""
return json.dumps(self.to_dict(), indent=2)
def to_markdown_report(self) -> str:
"""Generate markdown report"""
lines = [
f"# Power Budget Report: {self.project_name}",
"",
"## System Configuration",
f"- **System Voltage:** {self.system_voltage}V",
f"- **Safety Margin:** {self.safety_margin_percent}%",
"",
"## Component Breakdown",
"",
"| Component | Mode | Current (mA) | Duty % | Eff. Current (mA) | Power (mW) |",
"|-----------|------|--------------|--------|-------------------|------------|"
]
for c in self.components:
lines.append(
f"| {c.name} | {c.mode} | {c.current_ma:.2f} | {c.duty_cycle_percent:.1f} | "
f"{c.effective_current_ma:.3f} | {c.power_mw:.3f} |"
)
lines.extend([
"",
"## Totals",
f"- **Total Current:** {self.total_current_ma:.2f} mA",
f"- **With Safety Margin:** {self.total_current_with_margin_ma:.2f} mA",
f"- **Total Power:** {self.total_power_mw:.2f} mW",
"",
"## Battery Life Estimates",
"",
"| Battery | Capacity (mAh) | Runtime (hours) | Runtime (days) |",
"|---------|----------------|-----------------|----------------|"
])
for battery_name in BATTERY_DATABASE:
result = self.calculate_battery_life(battery_name)
lines.append(
f"| {battery_name} | {result['capacity_mah']} | "
f"{result['runtime_hours']:.1f} | {result['runtime_days']:.2f} |"
)
return "\n".join(lines)
def add_component_from_database(budget: PowerBudget, component_name: str,
mode: Optional[str] = None,
duty_cycle: float = 100.0,
quantity: int = 1) -> bool:
"""Add a component from the database to the budget"""
if component_name not in COMPONENT_DATABASE:
print(f"Warning: Unknown component '{component_name}'", file=sys.stderr)
return False
comp_data = COMPONENT_DATABASE[component_name]
# Use specified mode or typical mode
if mode is None:
mode = comp_data.get("typical_mode", list(comp_data["modes"].keys())[0])
if mode not in comp_data["modes"]:
print(f"Warning: Unknown mode '{mode}' for {component_name}", file=sys.stderr)
return False
current = comp_data["modes"][mode]
voltage = comp_data["voltage"]
entry = ComponentEntry(
name=component_name,
mode=mode,
current_ma=current,
voltage=voltage,
duty_cycle_percent=duty_cycle,
quantity=quantity
)
budget.components.append(entry)
return True
def interactive_mode():
"""Run calculator in interactive mode"""
print("=" * 60)
print("Power Budget Calculator - Interactive Mode")
print("=" * 60)
print()
project_name = input("Project name [My Project]: ").strip() or "My Project"
voltage_str = input("System voltage (3.3/5.0) [3.3]: ").strip() or "3.3"
try:
voltage = float(voltage_str)
except ValueError:
voltage = 3.3
budget = PowerBudget(project_name=project_name, system_voltage=voltage)
print("\nAvailable components:")
for i, name in enumerate(COMPONENT_DATABASE.keys(), 1):
print(f" {i:2}. {name}")
print("\nEnter components (empty line to finish):")
while True:
comp_input = input("\nComponent name (or number): ").strip()
if not comp_input:
break
# 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 comp_input not in COMPONENT_DATABASE:
print(f"Unknown component: {comp_input}")
continue
# Show available modes
modes = COMPONENT_DATABASE[comp_input]["modes"]
typical = COMPONENT_DATABASE[comp_input].get("typical_mode", list(modes.keys())[0])
print(f" Available modes: {', '.join(modes.keys())}")
mode = input(f" Mode [{typical}]: ").strip() or typical
duty_str = input(" Duty cycle % [100]: ").strip() or "100"
qty_str = input(" Quantity [1]: ").strip() or "1"
try:
duty = float(duty_str)
qty = int(qty_str)
except ValueError:
duty = 100.0
qty = 1
add_component_from_database(budget, comp_input, mode, duty, qty)
print(f" Added: {comp_input} ({mode}) @ {duty}% duty x{qty}")
# Generate report
print("\n" + "=" * 60)
print(budget.to_markdown_report())
# Save option
save = input("\nSave report to file? (y/n) [n]: ").strip().lower()
if save == 'y':
filename = f"{project_name.replace(' ', '_')}_power_budget.md"
with open(filename, 'w') as f:
f.write(budget.to_markdown_report())
print(f"Saved to: {filename}")
# Also save JSON
json_filename = f"{project_name.replace(' ', '_')}_power_budget.json"
with open(json_filename, 'w') as f:
f.write(budget.to_json())
print(f"Saved to: {json_filename}")
def main():
parser = argparse.ArgumentParser(description="Power Budget Calculator for Embedded Systems")
parser.add_argument("--interactive", "-i", action="store_true", help="Run in interactive mode")
parser.add_argument("--json", "-j", type=str, help="Load configuration from JSON file")
parser.add_argument("--component", "-c", type=str, help="Single component to calculate")
parser.add_argument("--mode", "-m", type=str, help="Operating mode for component")
parser.add_argument("--duty", "-d", type=float, default=100.0, help="Duty cycle percentage")
parser.add_argument("--list", "-l", action="store_true", help="List all components")
parser.add_argument("--output", "-o", type=str, help="Output file (json or md)")
args = parser.parse_args()
if args.list:
print("Available Components:")
print("-" * 60)
for name, data in COMPONENT_DATABASE.items():
modes = ", ".join(data["modes"].keys())
print(f"{name} ({data['voltage']}V): {modes}")
print("\nAvailable Batteries:")
print("-" * 60)
for name, data in BATTERY_DATABASE.items():
print(f"{name}: {data['capacity']}mAh @ {data['voltage']}V ({data['chemistry']})")
return
if args.interactive:
interactive_mode()
return
if args.json:
# Load from JSON
with open(args.json, 'r') as f:
config = json.load(f)
budget = PowerBudget(
project_name=config.get("project_name", "Loaded Project"),
system_voltage=config.get("system_voltage", 3.3),
safety_margin_percent=config.get("safety_margin_percent", 20.0)
)
for comp in config.get("components", []):
if comp.get("name") in COMPONENT_DATABASE:
add_component_from_database(
budget,
comp["name"],
comp.get("mode"),
comp.get("duty_cycle_percent", 100.0),
comp.get("quantity", 1)
)
else:
# Custom component
entry = ComponentEntry(
name=comp["name"],
mode=comp.get("mode", "custom"),
current_ma=comp.get("current_ma", 0),
voltage=comp.get("voltage", budget.system_voltage),
duty_cycle_percent=comp.get("duty_cycle_percent", 100.0),
quantity=comp.get("quantity", 1)
)
budget.components.append(entry)
if args.output:
if args.output.endswith('.json'):
with open(args.output, 'w') as f:
f.write(budget.to_json())
else:
with open(args.output, 'w') as f:
f.write(budget.to_markdown_report())
print(f"Output saved to: {args.output}")
else:
print(budget.to_markdown_report())
return
if args.component:
budget = PowerBudget(project_name="Quick Calculation")
add_component_from_database(budget, args.component, args.mode, args.duty)
print(budget.to_json())
return
# Default: show help
parser.print_help()
if __name__ == "__main__":
main()