Step 1: Product Specification
| Category | Specification | Target |
|---|---|---|
| Product | Smart Environmental Sensor Node | Indoor air quality monitor |
| MCU | ESP32-S3 (dual-core, WiFi + BLE) | 240 MHz, 512 KB SRAM |
| Sensors | BME680 (T/H/P/VOC), SCD41 (CO₂) | ±1°C, ±3% RH, ±40 ppm |
| Display | 1.54″ e-Paper (200 × 200) | Ultra-low power, sunlight readable |
| Power | USB-C + 18650 LiPo (3000 mAh) | 6+ months battery on WiFi interval |
| Connectivity | WiFi 802.11 b/g/n + BLE 5.0 | MQTT over TLS 1.3 |
| Enclosure | Injection-molded ABS, IP42 | 80 × 80 × 30 mm |
| Certifications | FCC Part 15B, CE (RED), RoHS | Unintentional radiator Class B |
| OTA Updates | Dual-partition A/B scheme | Encrypted firmware, rollback |
| Target BOM Cost | 1k units | < $18 per unit |
Step 2: Hardware Design
flowchart TD
A["USB-C
5V Input"] --> B["BQ24075
Li-Ion Charger"]
B --> C["18650 Cell
3000 mAh"]
C --> D["TPS63020
3.3V Buck-Boost"]
D --> E["ESP32-S3
WROOM-1"]
E -->|I2C| F["BME680
T/H/P/VOC"]
E -->|I2C| G["SCD41
CO₂ Sensor"]
E -->|SPI| H["1.54" e-Paper
200×200 px"]
E -->|WiFi| I["Cloud
MQTT Broker"]
E -->|BLE| J["Mobile App
Provisioning"]
D --> K["Fuel Gauge
MAX17048"]
style E fill:#3B9797,color:#fff
# IoT product BOM cost estimator (1k unit pricing)
# All prices from DigiKey/LCSC at 1000-unit breaks
bom = [
("ESP32-S3-WROOM-1 (N16R8)", "MCU Module", 2.80),
("BME680", "Env Sensor", 5.20),
("SCD41", "CO2 Sensor", 4.90),
("1.54\" e-Paper (GDEW0154M09)", "Display", 3.50),
("BQ24075 (Li-Ion Charger)", "PMIC", 0.95),
("TPS63020 (Buck-Boost)", "Regulator", 1.20),
("MAX17048 (Fuel Gauge)", "Battery Monitor", 0.85),
("USB-C Connector", "Connector", 0.18),
("18650 Battery Holder", "Mechanical", 0.12),
("Antenna (PCB trace)", "RF", 0.00),
("Passives (R, C, L, D) ×35", "Passives", 0.45),
("PCB 4-layer (100×60mm)", "PCB", 1.20),
("Assembly (PCBA)", "Manufacturing", 2.50),
("Enclosure (ABS molded)", "Mechanical", 1.80),
("Packaging + label", "Packaging", 0.40),
]
print("IoT Sensor Node — BOM Cost Estimate (1,000 units)")
print("=" * 62)
print(f"{'Component':<35} {'Category':<16} {'Cost':>8}")
print("-" * 62)
total = 0
for name, cat, cost in bom:
total += cost
print(f" {name:<33} {cat:<16} ${cost:>6.2f}")
print("-" * 62)
print(f" {'TOTAL BOM COST':<33} {'':16} ${total:>6.2f}")
margin_target = 0.55 # 55% gross margin
retail_price = total / (1 - margin_target)
print(f"\n Target Retail (55% margin): ${retail_price:.2f}")
print(f" BOM-to-Retail Ratio: {total/retail_price:.1%}")
print(f" Under $18 target: {'✓ YES' if total < 18 else '✗ NO'}")
Step 3: Firmware & OTA Updates
/* IoT firmware architecture — ESP-IDF with dual-partition OTA
Partition table: factory(1MB) + ota_0(1MB) + ota_1(1MB) + nvs(24KB) */
#include <stdio.h>
#include <string.h>
/* OTA update state machine */
typedef enum {
OTA_IDLE,
OTA_CHECKING, /* Polling server for new version */
OTA_DOWNLOADING, /* Streaming firmware to inactive partition */
OTA_VERIFYING, /* SHA-256 + signature check */
OTA_REBOOTING, /* Switch boot partition, reboot */
OTA_ROLLBACK, /* Revert to previous partition on failure */
OTA_ERROR
} ota_state_t;
/* Sensor reading structure — packed for MQTT transmission */
typedef struct __attribute__((packed)) {
uint32_t timestamp; /* Unix epoch seconds */
float temperature; /* °C from BME680 */
float humidity; /* %RH from BME680 */
float pressure; /* hPa from BME680 */
float voc_index; /* IAQ index 0-500 from BME680 */
uint16_t co2_ppm; /* ppm from SCD41 */
uint8_t battery_pct; /* % from MAX17048 */
uint8_t wifi_rssi; /* dBm (signed, stored as uint8) */
} sensor_payload_t; /* 26 bytes total */
/* Sensor task — reads all sensors, publishes via MQTT
*
* Pseudocode flow:
* 1. Wake from deep sleep (RTC timer)
* 2. Initialize I2C bus
* 3. Trigger BME680 forced measurement (wait ~200ms)
* 4. Read SCD41 single-shot measurement (wait ~5s)
* 5. Read MAX17048 battery SOC
* 6. Connect WiFi (use NVS-stored credentials)
* 7. Publish MQTT message (QoS 1, retain)
* 8. Check OTA server (every 24 hours)
* 9. Disconnect WiFi
* 10. Enter deep sleep (configurable: 5-60 min)
*
* Deep sleep current: ~10 µA (ESP32-S3 + all sensors off)
* Active cycle current: ~160 mA for ~8 seconds
* Average current at 15-min interval: ~14 µA
* Battery life (3000 mAh): ~3000/0.014 ≈ 214,000 hours ≈ 24+ years theoretical
* Real-world with self-discharge: ~12-18 months
*/
Step 4: Cloud Connectivity
# MQTT topic structure and message format for IoT sensor fleet
# Compatible with AWS IoT Core, Azure IoT Hub, HiveMQ
import json
# MQTT topic hierarchy
topics = {
"telemetry": "devices/{device_id}/telemetry",
"status": "devices/{device_id}/status",
"command": "devices/{device_id}/cmd",
"ota": "devices/{device_id}/ota",
"config": "devices/{device_id}/config",
}
# Example telemetry payload (published every 15 min)
telemetry_msg = {
"device_id": "sensor-node-001",
"fw_version": "1.2.0",
"timestamp": "2026-04-17T10:30:00Z",
"sensors": {
"temperature": 23.5,
"humidity": 45.2,
"pressure": 1013.25,
"voc_index": 85,
"co2_ppm": 620
},
"battery": {
"percent": 78,
"voltage": 3.82
},
"wifi_rssi": -52
}
print("MQTT Topic Structure:")
print("=" * 55)
for purpose, topic in topics.items():
example = topic.format(device_id="sensor-node-001")
print(f" {purpose:<12} → {example}")
print(f"\nTelemetry Payload ({len(json.dumps(telemetry_msg))} bytes JSON):")
print(json.dumps(telemetry_msg, indent=2))
# TLS 1.3 connection parameters
print("\nSecurity Configuration:")
print(" Protocol: MQTT 3.1.1 over TLS 1.3")
print(" Auth: X.509 client certificate (per-device)")
print(" CA: AmazonRootCA1.pem (or equivalent)")
print(" Cipher: TLS_AES_128_GCM_SHA256")
print(" Keep-alive: 300s (5 min)")
Step 5: Certification & Compliance
| Certification | Standard | Scope | Estimated Cost |
|---|---|---|---|
| FCC Part 15B | 47 CFR 15 Subpart B | Unintentional radiator (Class B) | $3,000 – $5,000 |
| FCC Part 15C | 47 CFR 15 Subpart C | Intentional radiator (WiFi/BLE) | $8,000 – $15,000 |
| CE (RED) | EN 300 328, EN 301 489 | Radio Equipment Directive (EU) | $5,000 – $10,000 |
| RoHS | EU 2011/65/EU | Restricted substances | $500 – $1,500 |
| REACH | EU 1907/2006 | Chemical safety | $500 – $1,000 |
| UL/IEC 62368-1 | Safety (AV/ICT) | Electrical safety (optional) | $10,000 – $20,000 |
Step 6: Production Costing
# Production cost breakdown — from prototype to 10k units
# Includes NRE, tooling, per-unit COGS, and margin analysis
import numpy as np
# Non-Recurring Engineering (NRE) costs
nre_costs = {
"PCB Design (schematic + layout)": 5000,
"Firmware Development": 12000,
"Cloud Backend Setup": 4000,
"Mechanical / Enclosure Design": 3000,
"Injection Mold Tooling": 8000,
"FCC Part 15B Testing": 4000,
"CE (RED) Testing": 7000,
"Prototype Runs (3 iterations)": 3000,
"Safety Testing (UL listing)": 0, # Optional
}
# Per-unit costs at different volumes
volumes = [100, 1000, 5000, 10000]
# Base BOM at 1k units = $26.05 (from BOM table above)
bom_1k = 26.05
# Volume scaling factors (larger orders = lower per-unit)
scaling = {100: 1.35, 1000: 1.00, 5000: 0.88, 10000: 0.82}
print("IoT Product — Production Cost Analysis")
print("=" * 60)
print("\nNon-Recurring Engineering (NRE):")
print("-" * 45)
nre_total = 0
for item, cost in nre_costs.items():
if cost > 0:
nre_total += cost
print(f" {item:<40} ${cost:>8,}")
print(f" {'TOTAL NRE':<40} ${nre_total:>8,}")
print(f"\nPer-Unit Cost at Volume:")
print("-" * 60)
print(f" {'Volume':<10} {'BOM':>8} {'Assembly':>10} {'Test':>8} "
f"{'NRE/unit':>10} {'TOTAL':>8}")
print("-" * 60)
for vol in volumes:
bom = bom_1k * scaling[vol]
assy = 2.50 * (1.5 if vol == 100 else 1.0 if vol == 1000 else 0.9 if vol == 5000 else 0.85)
test = 1.00
nre_per = nre_total / vol
total = bom + assy + test + nre_per
print(f" {vol:<10,} ${bom:>7.2f} ${assy:>9.2f} ${test:>7.2f} "
f"${nre_per:>9.2f} ${total:>7.2f}")
# Break-even analysis at $49.99 retail
retail = 49.99
cogs_10k = bom_1k * scaling[10000] + 2.50 * 0.85 + 1.00 + nre_total / 10000
gross_margin = (retail - cogs_10k) / retail
print(f"\nRetail Price: ${retail}")
print(f"COGS at 10k: ${cogs_10k:.2f}")
print(f"Gross Margin: {gross_margin:.1%}")
print(f"Break-even units: {int(np.ceil(nre_total / (retail - cogs_10k + nre_total/10000)))}")
IoT Product Planner
IoT Product Planner
Plan your IoT product build with cost estimates and certification requirements. Download as Word, Excel, or PDF.
Conclusion
This template covers the complete IoT product journey from concept to production-ready prototype. The key insight: certification strategy (using pre-certified modules) and production costing (NRE amortization) often determine product viability more than the technical design itself. Plan these from day one.
Explore the Full Series
You’ve completed the entire series! Return to the Series Index to revisit any part, capstone project, or template — or start from Part 1: Electronics Foundations for a fresh review.