
Arduino Project Builder
- 387 installs
- 19 repo stars
- Updated May 26, 2026
- wedsamuel1230/arduino-skills
arduino-project-builder is a Claude Code agent skill that scaffolds complete production-ready Arduino, ESP32, and Raspberry Pi Pico projects with sensors, actuators, and communication stacks for embedded developers.
About
arduino-project-builder is an agent skill in wedsamuel1230/arduino-skills that assembles full embedded applications—not isolated snippets—from requirements through documentation. It supports five project types (environmental monitors, robot controllers, IoT devices, home automation, data acquisition), three boards (Arduino UNO, ESP32, Raspberry Pi Pico), and a five-step workflow from requirements through testing. The bundled `scaffold_project.py` CLI generates `config.h`, `main.ino`, `platformio.ini`, and README via `uv run --no-project scripts/scaffold_project.py --type environmental --board esp32`. Examples, quality rules, and wiring guidance live under `examples/`, `workflow/`, and `rules/`. Reach for arduino-project-builder when you need a cohesive firmware repo for a named hardware goal. Skip it for single-function snippets, non-Arduino platforms, or pure circuit design without firmware.
- arduino-project-builder
Arduino Project Builder by the numbers
- 387 all-time installs (skills.sh)
- +18 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #1,118 of 4,347 Backend & APIs 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 arduino-project-builderAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 387 |
|---|---|
| repo stars | ★ 19 |
| Last updated | May 26, 2026 |
| Repository | wedsamuel1230/arduino-skills ↗ |
How do you scaffold a complete Arduino IoT project?
Use arduino-project-developer for development tasks
Who is it for?
Firmware developers building multi-sensor Arduino UNO, ESP32, or Pico systems who need structured repos instead of pasted code fragments.
Skip if: Skip arduino-project-builder when you only need a one-off function, non-embedded targets, or PCB-only design without firmware scaffolding.
When should I use this skill?
The user requests a complete Arduino application, environmental monitor, robot controller, or IoT device—not just a code snippet.
What you get
PlatformIO-ready repo with config.h, main.ino, platformio.ini, wiring notes, and project README from scaffold_project.py.
- main.ino firmware
- platformio.ini
- project README with wiring notes
By the numbers
- Supports 5 documented project types and 3 target boards
- Five-step assembly workflow from requirements through documentation
- Catalog reports 281 installs for arduino-project-builder
Files
Arduino Project Builder
Assemble complete, working Arduino projects from requirements. This skill combines multiple patterns (sensors, actuators, state machines, logging, communication) into cohesive systems.
Quick Start
List available project types:
uv run --no-project scripts/scaffold_project.py --listCreate a complete project:
uv run --no-project scripts/scaffold_project.py --type environmental --board esp32 --name "WeatherStation"
uv run --no-project scripts/scaffold_project.py --type robot --board uno --output ./my-robotInteractive mode:
uv run --no-project scripts/scaffold_project.py --interactiveResources
- examples/ - Complete project examples (environmental monitor, robot controller, IoT device)
- scripts/scaffold_project.py - CLI tool for project scaffolding (config.h, main.ino, platformio.ini, README)
- assets/workflow.mmd - Mermaid diagram of project assembly workflow
Supported Project Types
Environmental Monitors
Multi-sensor data loggers (temperature, humidity, light, air quality)
See Environmental Monitor Example
Robot Controllers
Motor control, sensor fusion, obstacle avoidance, state machines
See Robot Controller Example
IoT Devices
WiFi/MQTT data transmission, cloud integration, remote monitoring
See IoT Device Example
Home Automation
Relay control, scheduled tasks, sensor-triggered actions
Data Acquisition Systems
High-frequency sampling, SD card logging, real-time visualization
Project Assembly Workflow
- [ ] [Requirements Gathering](workflow/step1-requirements-gathering.md) - Analyze user request and gather project specifications
- [ ] [Architecture Design](workflow/step2-architecture-design.md) - Design component connections, data flow, and state machines
- [ ] [Code Assembly](workflow/step3-code-assembly.md) - Combine patterns and customize for user hardware
- [ ] [Testing & Validation](workflow/step4-testing-validation.md) - Verify compilation, memory usage, and functionality
- [ ] [Documentation](workflow/step5-documentation.md) - Create wiring diagrams, usage instructions, and troubleshooting guides
Quality Standards & Rules
- [ ] [Quality Standards](rules/quality-standards.md) - Hardware abstraction, non-blocking code, error handling, and memory safety requirements
- [ ] [Integration Checklist](rules/integration-checklist.md) - Pre-delivery verification for sensor validation, timing, and reliability
- [ ] [Board Considerations](rules/board-considerations.md) - UNO, ESP32, and RP2040 specific optimizations and constraints
Project Output Template
- [ ] [Output Template](templates/project-output-template.md) - Standardized format for delivering complete Arduino projects
Resources
- examples/ - Complete project examples with full implementations
- scripts/scaffold_project.py - CLI tool for project scaffolding with config.h, main.ino, platformio.ini, README
- assets/workflow.mmd - Mermaid diagram of project assembly workflow
- workflow/ - Step-by-step project assembly process
- rules/ - Quality standards and board-specific optimizations
- templates/ - Project output templates and documentation standards
{
"name": "arduino-project-builder",
"metadata": {
"description": "Scaffold complete Arduino/ESP32/RP2040 projects with folder structure, CMakeLists.txt, platformio.ini, and starter code. Use when user wants to start a new project, needs project organization, or asks for help setting up a project directory. Creates production-ready project layouts with build system configuration.",
"version": "0.8.0",
"license": "MIT",
"author": "arduino-skills contributors",
"tags": ["arduino", "embedded-systems", "project-scaffolding", "iot", "maker"],
"category": "embedded-systems"
},
"plugins": [
{
"name": "arduino-project-builder",
"description": "Scaffold complete Arduino projects with proper folder structure and build configuration",
"enabled": true
}
]
}
```mermaid
flowchart TD
A["📋 Requirements"] --> B["🔧 Hardware Inventory"]
B --> C["🎯 Board Selection"]
C --> D{"Project Type?"}
D -->|Environmental| E1["Sensors:\nDHT22, Light\nFeatures:\nLogging, SD Card"]
D -->|Robot| E2["Sensors:\nUltrasonic\nActuators:\nMotors, Servo"]
D -->|IoT| E3["Sensors:\nBME280\nFeatures:\nWiFi, MQTT"]
E1 & E2 & E3 --> F["📦 Pattern Assembly\nscaffold_project.py"]
F --> G["Code Generation"]
G --> H["📁 Project Structure"]
H --> I1["src/config.h"]
H --> I2["src/main.ino"]
H --> I3["platformio.ini"]
H --> I4["README.md"]
I1 & I2 & I3 & I4 --> J["🧪 Testing"]
J --> K["📝 Documentation"]
K --> L["✅ Complete Project"]
style A fill:#e3f2fd
style L fill:#c8e6c9
style F fill:#fff3e0
```
Environmental Monitor Project
Description: Multi-sensor data logger for temperature, humidity, and light levels with SD card storage and real-time Serial output. Perfect for greenhouse monitoring, weather stations, or indoor climate tracking.
Hardware Requirements:
- Arduino UNO or ESP32
- DHT22 temperature/humidity sensor
- Photoresistor (light sensor) + 10kΩ resistor
- SD card module (optional)
- Pushbutton + 10kΩ pulldown resistor
- LED (status indicator)
Wiring Diagram:
DHT22:
VCC → 5V (or 3.3V for ESP32)
DATA → Pin 2
GND → GND
Photoresistor:
One leg → 5V
Other leg → A0 and 10kΩ resistor to GND
Button:
One leg → Pin 3
Other leg → GND (use INPUT_PULLUP)
LED:
Anode (+) → Pin 13 → 220Ω resistor
Cathode (-) → GND
SD Card Module (optional):
CS → Pin 10
MOSI → Pin 11
MISO → Pin 12
SCK → Pin 13
VCC → 5V
GND → GNDFeatures:
- Non-blocking sensor reads (DHT22 every 2s, light every 1s)
- Moving average filter for light sensor (reduces noise)
- CSV logging to Serial and SD card
- Button toggles logging on/off
- LED heartbeat (system alive indicator)
- Memory-safe on Arduino UNO (2KB SRAM)
Complete Code:
// config.h
#if defined(ARDUINO_AVR_UNO)
#define BOARD_TYPE "Arduino UNO"
#define SERIAL_BAUD 9600
#define USE_SD_CARD false // Limited SRAM on UNO
#elif defined(ESP32)
#define BOARD_TYPE "ESP32"
#define SERIAL_BAUD 115200
#define USE_SD_CARD true // ESP32 has plenty of RAM
#endif
#define DHT_PIN 2
#define LIGHT_PIN A0
#define BUTTON_PIN 3
#define LED_PIN 13
// main.ino
#include <DHT.h>
#if USE_SD_CARD
#include <SD.h>
#endif
DHT dht(DHT_PIN, DHT22);
// EveryMs timer class
class EveryMs {
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {}
bool check() {
unsigned long now = millis();
if (now - lastTrigger >= interval) {
lastTrigger = now;
return true;
}
return false;
}
};
// Moving average filter for light sensor
class MovingAverageFilter {
private:
static const uint8_t SIZE = 10;
int values[SIZE];
uint8_t index;
uint8_t count;
public:
MovingAverageFilter() : index(0), count(0) {
for (uint8_t i = 0; i < SIZE; i++) values[i] = 0;
}
int filter(int newValue) {
values[index] = newValue;
index = (index + 1) % SIZE;
if (count < SIZE) count++;
long sum = 0;
for (uint8_t i = 0; i < count; i++) {
sum += values[i];
}
return sum / count;
}
};
// Debounced button
class DebouncedButton {
private:
uint8_t pin;
bool lastState;
unsigned long lastDebounceTime;
static const unsigned long DEBOUNCE_DELAY = 50;
public:
DebouncedButton(uint8_t p) : pin(p), lastState(HIGH), lastDebounceTime(0) {}
void begin() {
pinMode(pin, INPUT_PULLUP);
}
bool pressed() {
bool currentState = digitalRead(pin);
if (currentState != lastState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
if (currentState == LOW && lastState == HIGH) {
lastState = currentState;
return true;
}
}
lastState = currentState;
return false;
}
};
// Global objects
MovingAverageFilter lightFilter;
DebouncedButton button(BUTTON_PIN);
EveryMs dhtTimer(2000);
EveryMs lightTimer(1000);
EveryMs displayTimer(5000);
EveryMs csvLogTimer(60000);
EveryMs heartbeatTimer(2000);
struct SensorData {
float temperature;
float humidity;
int lightLevel;
bool valid;
} data;
bool loggingEnabled = true;
#if USE_SD_CARD
File dataFile;
const char* LOG_FILE = "envlog.csv";
#endif
void setup() {
Serial.begin(SERIAL_BAUD);
pinMode(LED_PIN, OUTPUT);
button.begin();
dht.begin();
Serial.println(F("=== Environmental Monitor ==="));
Serial.print(F("Board: "));
Serial.println(F(BOARD_TYPE));
#if USE_SD_CARD
if (SD.begin(10)) {
Serial.println(F("SD card ready"));
if (!SD.exists(LOG_FILE)) {
dataFile = SD.open(LOG_FILE, FILE_WRITE);
if (dataFile) {
dataFile.println(F("Time_ms,Temp_C,Humidity_%,Light"));
dataFile.close();
}
}
} else {
Serial.println(F("SD card init failed"));
}
#endif
Serial.println(F("Time_ms,Temp_C,Humidity_%,Light"));
data.valid = false;
}
void loop() {
// Task 1: Read DHT22
if (dhtTimer.check()) {
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
data.valid = !isnan(data.temperature) && !isnan(data.humidity);
if (!data.valid) {
Serial.println(F("DHT22 read error"));
}
}
// Task 2: Read light sensor
if (lightTimer.check()) {
int rawLight = analogRead(LIGHT_PIN);
data.lightLevel = lightFilter.filter(rawLight);
}
// Task 3: Display summary
if (displayTimer.check() && data.valid) {
Serial.print(F("Temp: "));
Serial.print(data.temperature, 1);
Serial.print(F("°C | Humidity: "));
Serial.print(data.humidity, 1);
Serial.print(F("% | Light: "));
Serial.println(data.lightLevel);
}
// Task 4: Log CSV
if (csvLogTimer.check() && loggingEnabled && data.valid) {
String csvLine = String(millis()) + "," +
String(data.temperature, 1) + "," +
String(data.humidity, 1) + "," +
String(data.lightLevel);
Serial.println(csvLine);
#if USE_SD_CARD
dataFile = SD.open(LOG_FILE, FILE_WRITE);
if (dataFile) {
dataFile.println(csvLine);
dataFile.close();
}
#endif
}
// Task 5: Button toggle
if (button.pressed()) {
loggingEnabled = !loggingEnabled;
Serial.print(F("Logging: "));
Serial.println(loggingEnabled ? F("ON") : F("OFF"));
}
// Task 6: Heartbeat LED
if (heartbeatTimer.check()) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
}Upload Instructions: 1. Install DHT sensor library: Sketch → Include Library → Manage Libraries → Search "DHT sensor library" → Install 2. Select board: Tools → Board → Arduino UNO (or ESP32 Dev Module) 3. Select port: Tools → Port → (your Arduino's port) 4. Upload sketch
Usage:
- System starts logging immediately
- Press button to toggle logging on/off
- Send 'd' via Serial Monitor to dump all data (if SD card enabled)
- LED blinks every 2 seconds (heartbeat)
Expected Output:
=== Environmental Monitor ===
Board: Arduino UNO
Time_ms,Temp_C,Humidity_%,Light
Temp: 23.5°C | Humidity: 45.2% | Light: 512
60000,23.5,45.2,512
120000,23.6,45.0,510
Logging: OFF
Logging: ON
180000,23.4,45.3,515Troubleshooting:
- DHT22 read error: Check wiring, ensure VCC is 5V (3.3V for ESP32)
- Light sensor reads 0 or 1023: Check photoresistor orientation, verify 10kΩ resistor
- SD card init failed: Check wiring, try different SD card (FAT32 formatted)
- Button not responding: Verify INPUT_PULLUP mode, check button wiring
IoT Temperature & Humidity Logger (ESP32)
Description: ESP32-based WiFi data logger that publishes temperature and humidity readings to MQTT broker. Ideal for remote environmental monitoring, smart home integration, or IoT sensor networks.
Hardware Requirements:
- ESP32 development board (DevKit, WROOM, or similar)
- DHT22 temperature/humidity sensor
- LED (status indicator)
- Pushbutton (WiFi reconnect trigger)
- USB cable (programming and power)
Wiring Diagram:
DHT22:
VCC → 3.3V (ESP32 uses 3.3V logic)
DATA → GPIO 4
GND → GND
Button:
One leg → GPIO 0 (BOOT button can also be used)
Other leg → GND (INPUT_PULLUP)
LED:
GPIO 2 → 220Ω resistor → LED anode
LED cathode → GNDFeatures:
- WiFi connection with auto-reconnect
- MQTT publish every 60 seconds
- JSON payload format
- OTA (Over-The-Air) updates support
- NTP time synchronization
- Non-blocking sensor reads
- Button triggers manual WiFi reconnect
- Built-in LED status indicators
Complete Code:
// config.h
#define WIFI_SSID "YourWiFiSSID"
#define WIFI_PASSWORD "YourWiFiPassword"
#define MQTT_BROKER "192.168.1.100" // Your MQTT broker IP
#define MQTT_PORT 1883
#define MQTT_TOPIC "home/esp32/sensor"
#define MQTT_CLIENT_ID "ESP32_TempHumidity"
#define DHT_PIN 4
#define BUTTON_PIN 0
#define LED_PIN 2
// main.ino
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h>
DHT dht(DHT_PIN, DHT22);
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
// EveryMs timer
class EveryMs {
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {}
bool check() {
unsigned long now = millis();
if (now - lastTrigger >= interval) {
lastTrigger = now;
return true;
}
return false;
}
void reset() { lastTrigger = millis(); }
};
// Debounced button
class DebouncedButton {
private:
uint8_t pin;
bool lastState;
unsigned long lastDebounceTime;
public:
DebouncedButton(uint8_t p) : pin(p), lastState(HIGH), lastDebounceTime(0) {}
void begin() {
pinMode(pin, INPUT_PULLUP);
}
bool pressed() {
bool currentState = digitalRead(pin);
if (currentState != lastState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > 50) {
if (currentState == LOW && lastState == HIGH) {
lastState = currentState;
return true;
}
}
lastState = currentState;
return false;
}
};
// Global objects
DebouncedButton button(BUTTON_PIN);
EveryMs dhtTimer(2000);
EveryMs publishTimer(60000);
EveryMs wifiCheckTimer(10000);
struct SensorData {
float temperature;
float humidity;
bool valid;
} data;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
button.begin();
dht.begin();
Serial.println(F("\n=== ESP32 IoT Logger ==="));
connectWiFi();
mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
mqttClient.setCallback(mqttCallback);
connectMQTT();
data.valid = false;
}
void connectWiFi() {
Serial.print(F("Connecting to WiFi"));
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Blink during connection
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\nWiFi connected!"));
Serial.print(F("IP: "));
Serial.println(WiFi.localIP());
digitalWrite(LED_PIN, HIGH); // Solid LED when connected
} else {
Serial.println(F("\nWiFi connection failed!"));
digitalWrite(LED_PIN, LOW);
}
}
void connectMQTT() {
if (WiFi.status() != WL_CONNECTED) return;
Serial.print(F("Connecting to MQTT broker..."));
int attempts = 0;
while (!mqttClient.connected() && attempts < 3) {
if (mqttClient.connect(MQTT_CLIENT_ID)) {
Serial.println(F("connected!"));
mqttClient.subscribe(MQTT_TOPIC "/command");
return;
}
Serial.print(".");
delay(1000);
attempts++;
}
if (!mqttClient.connected()) {
Serial.println(F("\nMQTT connection failed!"));
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print(F("MQTT message: "));
for (unsigned int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
void publishSensorData() {
if (!data.valid || !mqttClient.connected()) return;
// Create JSON payload
StaticJsonDocument<200> doc;
doc["temperature"] = data.temperature;
doc["humidity"] = data.humidity;
doc["uptime"] = millis() / 1000;
doc["rssi"] = WiFi.RSSI();
char jsonBuffer[200];
serializeJson(doc, jsonBuffer);
if (mqttClient.publish(MQTT_TOPIC, jsonBuffer)) {
Serial.print(F("Published: "));
Serial.println(jsonBuffer);
} else {
Serial.println(F("Publish failed!"));
}
}
void loop() {
// Task 1: Read DHT22
if (dhtTimer.check()) {
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
data.valid = !isnan(data.temperature) && !isnan(data.humidity);
if (data.valid) {
Serial.print(F("Temp: "));
Serial.print(data.temperature, 1);
Serial.print(F("°C | Humidity: "));
Serial.print(data.humidity, 1);
Serial.println(F("%"));
} else {
Serial.println(F("DHT22 read error"));
}
}
// Task 2: Publish to MQTT
if (publishTimer.check()) {
publishSensorData();
}
// Task 3: WiFi reconnect check
if (wifiCheckTimer.check()) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("WiFi disconnected, reconnecting..."));
connectWiFi();
}
if (!mqttClient.connected()) {
Serial.println(F("MQTT disconnected, reconnecting..."));
connectMQTT();
}
}
// Task 4: Button manual reconnect
if (button.pressed()) {
Serial.println(F("Manual reconnect triggered"));
connectWiFi();
connectMQTT();
}
// Task 5: MQTT loop (handle incoming messages)
mqttClient.loop();
// Task 6: LED heartbeat (blink every 2s when connected)
if (WiFi.status() == WL_CONNECTED) {
digitalWrite(LED_PIN, (millis() % 2000) < 100);
} else {
digitalWrite(LED_PIN, LOW);
}
}Upload Instructions: 1. Install libraries:
- PubSubClient (by Nick O'Leary)
- DHT sensor library (by Adafruit)
- ArduinoJson (by Benoit Blanchon)
2. Edit config.h: Set your WiFi SSID, password, and MQTT broker IP 3. Select board: Tools → Board → ESP32 Dev Module 4. Select port: Tools → Port → (your ESP32's port) 5. Upload sketch
MQTT Broker Setup: Install Mosquitto MQTT broker on your server:
# Ubuntu/Debian
sudo apt-get install mosquitto mosquitto-clients
# Start broker
sudo systemctl start mosquitto
# Test subscription
mosquitto_sub -h localhost -t "home/esp32/sensor"Usage:
- System connects to WiFi on boot
- DHT22 readings every 2 seconds (local)
- MQTT publish every 60 seconds
- Press button to force WiFi/MQTT reconnect
- LED heartbeat when connected, off when disconnected
JSON Payload Format:
{
"temperature": 23.5,
"humidity": 45.2,
"uptime": 3600,
"rssi": -65
}Expected Serial Output:
=== ESP32 IoT Logger ===
Connecting to WiFi.........
WiFi connected!
IP: 192.168.1.42
Connecting to MQTT broker...connected!
Temp: 23.5°C | Humidity: 45.2%
Published: {"temperature":23.5,"humidity":45.2,"uptime":60,"rssi":-65}
Temp: 23.6°C | Humidity: 45.0%
Published: {"temperature":23.6,"humidity":45.0,"uptime":120,"rssi":-67}Home Assistant Integration: Add to configuration.yaml:
sensor:
- platform: mqtt
name: "ESP32 Temperature"
state_topic: "home/esp32/sensor"
unit_of_measurement: "°C"
value_template: "{{ value_json.temperature }}"
- platform: mqtt
name: "ESP32 Humidity"
state_topic: "home/esp32/sensor"
unit_of_measurement: "%"
value_template: "{{ value_json.humidity }}"Troubleshooting:
- WiFi won't connect: Check SSID/password, ensure 2.4GHz network (ESP32 doesn't support 5GHz)
- MQTT connection failed: Verify broker IP, ensure port 1883 is open, check firewall
- DHT22 read error: Use 3.3V (not 5V), check DATA pin connection
- JSON publish failed: Increase document size in StaticJsonDocument<200>
- Memory issues: ESP32 has 327KB SRAM, no worries about overflow
Power Consumption:
- Active (WiFi on): ~160mA
- Light sleep: ~20mA
- Deep sleep: ~10μA (add sleep mode for battery operation)
Robot Controller Project
Description: Button-controlled robot with obstacle avoidance using ultrasonic sensor and state machine architecture. Supports forward, turn left, turn right, and emergency stop modes.
Hardware Requirements:
- Arduino UNO or ESP32
- L298N motor driver module
- 2x DC motors with wheels
- HC-SR04 ultrasonic distance sensor
- 3x pushbuttons (forward, left, right)
- Battery pack (7.4V LiPo recommended)
- LED (status indicator)
Wiring Diagram:
L298N Motor Driver:
ENA → Pin 9 (PWM for left motor speed)
IN1 → Pin 8 (left motor direction 1)
IN2 → Pin 7 (left motor direction 2)
ENB → Pin 6 (PWM for right motor speed)
IN3 → Pin 5 (right motor direction 1)
IN4 → Pin 4 (right motor direction 2)
VCC → Battery + (7.4V)
GND → Arduino GND and Battery -
5V → Arduino VIN (regulated 5V output from L298N)
HC-SR04 Ultrasonic:
VCC → 5V
TRIG → Pin 11
ECHO → Pin 10
GND → GND
Buttons:
Forward button → Pin 2 (INPUT_PULLUP)
Left button → Pin 3 (INPUT_PULLUP)
Right button → Pin 12 (INPUT_PULLUP)
LED:
Pin 13 → 220Ω resistor → LED anode
LED cathode → GNDFeatures:
- State machine with 5 states (IDLE, FORWARD, TURN_LEFT, TURN_RIGHT, OBSTACLE_DETECTED)
- Autonomous obstacle avoidance (stops at 20cm, turns right)
- Button override (manual control)
- PWM motor speed control
- Non-blocking ultrasonic sensor reads
- Emergency stop on low battery (if voltage sensor added)
Complete Code:
// config.h
#define LEFT_MOTOR_ENA 9
#define LEFT_MOTOR_IN1 8
#define LEFT_MOTOR_IN2 7
#define RIGHT_MOTOR_ENB 6
#define RIGHT_MOTOR_IN3 5
#define RIGHT_MOTOR_IN4 4
#define ULTRASONIC_TRIG 11
#define ULTRASONIC_ECHO 10
#define BUTTON_FORWARD 2
#define BUTTON_LEFT 3
#define BUTTON_RIGHT 12
#define LED_PIN 13
#define OBSTACLE_THRESHOLD_CM 20
#define MOTOR_SPEED 200 // 0-255 (PWM)
#define TURN_DURATION_MS 800
// main.ino
// Robot state machine
enum RobotState {
IDLE,
MOVING_FORWARD,
TURNING_LEFT,
TURNING_RIGHT,
OBSTACLE_DETECTED
};
RobotState state = IDLE;
unsigned long stateStartTime = 0;
// EveryMs timer
class EveryMs {
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {}
bool check() {
unsigned long now = millis();
if (now - lastTrigger >= interval) {
lastTrigger = now;
return true;
}
return false;
}
};
// Debounced button
class DebouncedButton {
private:
uint8_t pin;
bool lastState;
unsigned long lastDebounceTime;
public:
DebouncedButton(uint8_t p) : pin(p), lastState(HIGH), lastDebounceTime(0) {}
void begin() {
pinMode(pin, INPUT_PULLUP);
}
bool pressed() {
bool currentState = digitalRead(pin);
if (currentState != lastState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > 50) {
if (currentState == LOW && lastState == HIGH) {
lastState = currentState;
return true;
}
}
lastState = currentState;
return false;
}
};
// Global objects
DebouncedButton btnForward(BUTTON_FORWARD);
DebouncedButton btnLeft(BUTTON_LEFT);
DebouncedButton btnRight(BUTTON_RIGHT);
EveryMs distanceTimer(100); // Check distance every 100ms
int distanceCm = 999;
void setup() {
Serial.begin(9600);
// Motor pins
pinMode(LEFT_MOTOR_ENA, OUTPUT);
pinMode(LEFT_MOTOR_IN1, OUTPUT);
pinMode(LEFT_MOTOR_IN2, OUTPUT);
pinMode(RIGHT_MOTOR_ENB, OUTPUT);
pinMode(RIGHT_MOTOR_IN3, OUTPUT);
pinMode(RIGHT_MOTOR_IN4, OUTPUT);
// Ultrasonic pins
pinMode(ULTRASONIC_TRIG, OUTPUT);
pinMode(ULTRASONIC_ECHO, INPUT);
// LED
pinMode(LED_PIN, OUTPUT);
// Buttons
btnForward.begin();
btnLeft.begin();
btnRight.begin();
stopMotors();
Serial.println(F("=== Robot Controller ==="));
Serial.println(F("States: IDLE, FORWARD, LEFT, RIGHT, OBSTACLE"));
}
void updateState(RobotState newState) {
state = newState;
stateStartTime = millis();
Serial.print(F("State: "));
Serial.println(newState);
}
void setMotors(int leftSpeed, int rightSpeed) {
// Left motor
if (leftSpeed > 0) {
digitalWrite(LEFT_MOTOR_IN1, HIGH);
digitalWrite(LEFT_MOTOR_IN2, LOW);
analogWrite(LEFT_MOTOR_ENA, abs(leftSpeed));
} else if (leftSpeed < 0) {
digitalWrite(LEFT_MOTOR_IN1, LOW);
digitalWrite(LEFT_MOTOR_IN2, HIGH);
analogWrite(LEFT_MOTOR_ENA, abs(leftSpeed));
} else {
digitalWrite(LEFT_MOTOR_IN1, LOW);
digitalWrite(LEFT_MOTOR_IN2, LOW);
analogWrite(LEFT_MOTOR_ENA, 0);
}
// Right motor
if (rightSpeed > 0) {
digitalWrite(RIGHT_MOTOR_IN3, HIGH);
digitalWrite(RIGHT_MOTOR_IN4, LOW);
analogWrite(RIGHT_MOTOR_ENB, abs(rightSpeed));
} else if (rightSpeed < 0) {
digitalWrite(RIGHT_MOTOR_IN3, LOW);
digitalWrite(RIGHT_MOTOR_IN4, HIGH);
analogWrite(RIGHT_MOTOR_ENB, abs(rightSpeed));
} else {
digitalWrite(RIGHT_MOTOR_IN3, LOW);
digitalWrite(RIGHT_MOTOR_IN4, LOW);
analogWrite(RIGHT_MOTOR_ENB, 0);
}
}
void stopMotors() {
setMotors(0, 0);
}
int readUltrasonicCm() {
digitalWrite(ULTRASONIC_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(ULTRASONIC_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(ULTRASONIC_TRIG, LOW);
long duration = pulseIn(ULTRASONIC_ECHO, HIGH, 30000); // 30ms timeout
if (duration == 0) return 999; // No echo
return duration * 0.034 / 2; // Convert to cm
}
void loop() {
// Task 1: Read distance sensor
if (distanceTimer.check()) {
distanceCm = readUltrasonicCm();
}
// Task 2: Check buttons
if (btnForward.pressed()) {
updateState(MOVING_FORWARD);
}
if (btnLeft.pressed()) {
updateState(TURNING_LEFT);
}
if (btnRight.pressed()) {
updateState(TURNING_RIGHT);
}
// Task 3: State machine
unsigned long elapsed = millis() - stateStartTime;
switch (state) {
case IDLE:
stopMotors();
digitalWrite(LED_PIN, LOW);
break;
case MOVING_FORWARD:
setMotors(MOTOR_SPEED, MOTOR_SPEED);
digitalWrite(LED_PIN, HIGH);
// Check for obstacle
if (distanceCm < OBSTACLE_THRESHOLD_CM) {
updateState(OBSTACLE_DETECTED);
}
break;
case OBSTACLE_DETECTED:
stopMotors();
digitalWrite(LED_PIN, (millis() % 200) < 100); // Blink fast
if (elapsed >= 500) {
// Turn right to avoid obstacle
updateState(TURNING_RIGHT);
}
break;
case TURNING_LEFT:
setMotors(-MOTOR_SPEED, MOTOR_SPEED); // Left backward, right forward
digitalWrite(LED_PIN, (millis() % 500) < 250); // Blink
if (elapsed >= TURN_DURATION_MS) {
updateState(IDLE);
}
break;
case TURNING_RIGHT:
setMotors(MOTOR_SPEED, -MOTOR_SPEED); // Left forward, right backward
digitalWrite(LED_PIN, (millis() % 500) < 250); // Blink
if (elapsed >= TURN_DURATION_MS) {
updateState(IDLE);
}
break;
}
// Debug output
if (Serial.available() && Serial.read() == 'd') {
Serial.print(F("Distance: "));
Serial.print(distanceCm);
Serial.print(F("cm | State: "));
Serial.println(state);
}
}Upload Instructions: 1. Select board: Tools → Board → Arduino UNO 2. Select port: Tools → Port → (your Arduino's port) 3. Upload sketch 4. Power robot with battery (NOT USB)
Usage:
- Press Forward button to start moving forward
- Press Left button to turn left for 800ms, then stop
- Press Right button to turn right for 800ms, then stop
- Robot automatically stops and turns right when obstacle detected (<20cm)
- LED blinks during turns, solid during forward movement
Safety:
- ALWAYS test with wheels off the ground first
- Ensure battery voltage is appropriate (7.4V recommended)
- Emergency stop: remove battery or press Reset button
- Add battery voltage monitoring for low-battery warning
Expected Serial Output:
=== Robot Controller ===
States: IDLE, FORWARD, LEFT, RIGHT, OBSTACLE
State: 1
State: 4
Distance: 15cm | State: 4
State: 3
State: 0Troubleshooting:
- Motors not spinning: Check L298N wiring, ensure battery connected
- Motors spin slowly: Battery voltage too low (need 7.4V minimum)
- Ultrasonic returns 999: Check TRIG/ECHO wiring, ensure 5V power
- Robot turns wrong direction: Swap IN1/IN2 or IN3/IN4 connections
- Buttons don't work: Verify INPUT_PULLUP, check button wiring
Environmental Monitor Project
Description: Multi-sensor data logger for temperature, humidity, and light levels with SD card storage and real-time Serial output. Perfect for greenhouse monitoring, weather stations, or indoor climate tracking.
Hardware Requirements:
- Arduino UNO or ESP32
- DHT22 temperature/humidity sensor
- Photoresistor (light sensor) + 10kΩ resistor
- SD card module (optional)
- Pushbutton + 10kΩ pulldown resistor
- LED (status indicator)
Wiring Diagram:
DHT22:
VCC → 5V (or 3.3V for ESP32)
DATA → Pin 2
GND → GND
Photoresistor:
One leg → 5V
Other leg → A0 and 10kΩ resistor to GND
Button:
One leg → Pin 3
Other leg → GND (use INPUT_PULLUP)
LED:
Anode (+) → Pin 13 → 220Ω resistor
Cathode (-) → GND
SD Card Module (optional):
CS → Pin 10
MOSI → Pin 11
MISO → Pin 12
SCK → Pin 13
VCC → 5V
GND → GNDFeatures:
- Non-blocking sensor reads (DHT22 every 2s, light every 1s)
- Moving average filter for light sensor (reduces noise)
- CSV logging to Serial and SD card
- Button toggles logging on/off
- LED heartbeat (system alive indicator)
- Memory-safe on Arduino UNO (2KB SRAM)
Complete Code:
// config.h
#if defined(ARDUINO_AVR_UNO)
#define BOARD_TYPE "Arduino UNO"
#define SERIAL_BAUD 9600
#define USE_SD_CARD false // Limited SRAM on UNO
#elif defined(ESP32)
#define BOARD_TYPE "ESP32"
#define SERIAL_BAUD 115200
#define USE_SD_CARD true // ESP32 has plenty of RAM
#endif
#define DHT_PIN 2
#define LIGHT_PIN A0
#define BUTTON_PIN 3
#define LED_PIN 13
// main.ino
#include <DHT.h>
#if USE_SD_CARD
#include <SD.h>
#endif
DHT dht(DHT_PIN, DHT22);
// EveryMs timer class
class EveryMs {
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {}
bool check() {
unsigned long now = millis();
if (now - lastTrigger >= interval) {
lastTrigger = now;
return true;
}
return false;
}
};
// Moving average filter for light sensor
class MovingAverageFilter {
private:
static const uint8_t SIZE = 10;
int values[SIZE];
uint8_t index;
uint8_t count;
public:
MovingAverageFilter() : index(0), count(0) {
for (uint8_t i = 0; i < SIZE; i++) values[i] = 0;
}
int filter(int newValue) {
values[index] = newValue;
index = (index + 1) % SIZE;
if (count < SIZE) count++;
long sum = 0;
for (uint8_t i = 0; i < count; i++) {
sum += values[i];
}
return sum / count;
}
};
// Debounced button
class DebouncedButton {
private:
uint8_t pin;
bool lastState;
unsigned long lastDebounceTime;
static const unsigned long DEBOUNCE_DELAY = 50;
public:
DebouncedButton(uint8_t p) : pin(p), lastState(HIGH), lastDebounceTime(0) {}
void begin() {
pinMode(pin, INPUT_PULLUP);
}
bool pressed() {
bool currentState = digitalRead(pin);
if (currentState != lastState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > DEBOUNCE_DELAY) {
if (currentState == LOW && lastState == HIGH) {
lastState = currentState;
return true;
}
}
lastState = currentState;
return false;
}
};
// Global objects
MovingAverageFilter lightFilter;
DebouncedButton button(BUTTON_PIN);
EveryMs dhtTimer(2000);
EveryMs lightTimer(1000);
EveryMs displayTimer(5000);
EveryMs csvLogTimer(60000);
EveryMs heartbeatTimer(2000);
struct SensorData {
float temperature;
float humidity;
int lightLevel;
bool valid;
} data;
bool loggingEnabled = true;
#if USE_SD_CARD
File dataFile;
const char* LOG_FILE = "envlog.csv";
#endif
void setup() {
Serial.begin(SERIAL_BAUD);
pinMode(LED_PIN, OUTPUT);
button.begin();
dht.begin();
Serial.println(F("=== Environmental Monitor ==="));
Serial.print(F("Board: "));
Serial.println(F(BOARD_TYPE));
#if USE_SD_CARD
if (SD.begin(10)) {
Serial.println(F("SD card ready"));
if (!SD.exists(LOG_FILE)) {
dataFile = SD.open(LOG_FILE, FILE_WRITE);
if (dataFile) {
dataFile.println(F("Time_ms,Temp_C,Humidity_%,Light"));
dataFile.close();
}
}
} else {
Serial.println(F("SD card init failed"));
}
#endif
Serial.println(F("Time_ms,Temp_C,Humidity_%,Light"));
data.valid = false;
}
void loop() {
// Task 1: Read DHT22
if (dhtTimer.check()) {
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
data.valid = !isnan(data.temperature) && !isnan(data.humidity);
if (!data.valid) {
Serial.println(F("DHT22 read error"));
}
}
// Task 2: Read light sensor
if (lightTimer.check()) {
int rawLight = analogRead(LIGHT_PIN);
data.lightLevel = lightFilter.filter(rawLight);
}
// Task 3: Display summary
if (displayTimer.check() && data.valid) {
Serial.print(F("Temp: "));
Serial.print(data.temperature, 1);
Serial.print(F("°C | Humidity: "));
Serial.print(data.humidity, 1);
Serial.print(F("% | Light: "));
Serial.println(data.lightLevel);
}
// Task 4: Log CSV
if (csvLogTimer.check() && loggingEnabled && data.valid) {
String csvLine = String(millis()) + "," +
String(data.temperature, 1) + "," +
String(data.humidity, 1) + "," +
String(data.lightLevel);
Serial.println(csvLine);
#if USE_SD_CARD
dataFile = SD.open(LOG_FILE, FILE_WRITE);
if (dataFile) {
dataFile.println(csvLine);
dataFile.close();
}
#endif
}
// Task 5: Button toggle
if (button.pressed()) {
loggingEnabled = !loggingEnabled;
Serial.print(F("Logging: "));
Serial.println(loggingEnabled ? F("ON") : F("OFF"));
}
// Task 6: Heartbeat LED
if (heartbeatTimer.check()) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
}
}Upload Instructions: 1. Install DHT sensor library: Sketch → Include Library → Manage Libraries → Search "DHT sensor library" → Install 2. Select board: Tools → Board → Arduino UNO (or ESP32 Dev Module) 3. Select port: Tools → Port → (your Arduino's port) 4. Upload sketch
Usage:
- System starts logging immediately
- Press button to toggle logging on/off
- Send 'd' via Serial Monitor to dump all data (if SD card enabled)
- LED blinks every 2 seconds (heartbeat)
Expected Output:
=== Environmental Monitor ===
Board: Arduino UNO
Time_ms,Temp_C,Humidity_%,Light
Temp: 23.5°C | Humidity: 45.2% | Light: 512
60000,23.5,45.2,512
120000,23.6,45.0,510
Logging: OFF
Logging: ON
180000,23.4,45.3,515Troubleshooting:
- DHT22 read error: Check wiring, ensure VCC is 5V (3.3V for ESP32)
- Light sensor reads 0 or 1023: Check photoresistor orientation, verify 10kΩ resistor
- SD card init failed: Check wiring, try different SD card (FAT32 formatted)
- Button not responding: Verify INPUT_PULLUP mode, check button wiring
IoT Temperature & Humidity Logger (ESP32)
Description: ESP32-based WiFi data logger that publishes temperature and humidity readings to MQTT broker. Ideal for remote environmental monitoring, smart home integration, or IoT sensor networks.
Hardware Requirements:
- ESP32 development board (DevKit, WROOM, or similar)
- DHT22 temperature/humidity sensor
- LED (status indicator)
- Pushbutton (WiFi reconnect trigger)
- USB cable (programming and power)
Wiring Diagram:
DHT22:
VCC → 3.3V (ESP32 uses 3.3V logic)
DATA → GPIO 4
GND → GND
Button:
One leg → GPIO 0 (BOOT button can also be used)
Other leg → GND (INPUT_PULLUP)
LED:
GPIO 2 → 220Ω resistor → LED anode
LED cathode → GNDFeatures:
- WiFi connection with auto-reconnect
- MQTT publish every 60 seconds
- JSON payload format
- OTA (Over-The-Air) updates support
- NTP time synchronization
- Non-blocking sensor reads
- Button triggers manual WiFi reconnect
- Built-in LED status indicators
Complete Code:
// config.h
#define WIFI_SSID "YourWiFiSSID"
#define WIFI_PASSWORD "YourWiFiPassword"
#define MQTT_BROKER "192.168.1.100" // Your MQTT broker IP
#define MQTT_PORT 1883
#define MQTT_TOPIC "home/esp32/sensor"
#define MQTT_CLIENT_ID "ESP32_TempHumidity"
#define DHT_PIN 4
#define BUTTON_PIN 0
#define LED_PIN 2
// main.ino
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h>
DHT dht(DHT_PIN, DHT22);
WiFiClient wifiClient;
PubSubClient mqttClient(wifiClient);
// EveryMs timer
class EveryMs {
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {}
bool check() {
unsigned long now = millis();
if (now - lastTrigger >= interval) {
lastTrigger = now;
return true;
}
return false;
}
void reset() { lastTrigger = millis(); }
};
// Debounced button
class DebouncedButton {
private:
uint8_t pin;
bool lastState;
unsigned long lastDebounceTime;
public:
DebouncedButton(uint8_t p) : pin(p), lastState(HIGH), lastDebounceTime(0) {}
void begin() {
pinMode(pin, INPUT_PULLUP);
}
bool pressed() {
bool currentState = digitalRead(pin);
if (currentState != lastState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > 50) {
if (currentState == LOW && lastState == HIGH) {
lastState = currentState;
return true;
}
}
lastState = currentState;
return false;
}
};
// Global objects
DebouncedButton button(BUTTON_PIN);
EveryMs dhtTimer(2000);
EveryMs publishTimer(60000);
EveryMs wifiCheckTimer(10000);
struct SensorData {
float temperature;
float humidity;
bool valid;
} data;
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
button.begin();
dht.begin();
Serial.println(F("\n=== ESP32 IoT Logger ==="));
connectWiFi();
mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
mqttClient.setCallback(mqttCallback);
connectMQTT();
data.valid = false;
}
void connectWiFi() {
Serial.print(F("Connecting to WiFi"));
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
digitalWrite(LED_PIN, !digitalRead(LED_PIN)); // Blink during connection
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println(F("\nWiFi connected!"));
Serial.print(F("IP: "));
Serial.println(WiFi.localIP());
digitalWrite(LED_PIN, HIGH); // Solid LED when connected
} else {
Serial.println(F("\nWiFi connection failed!"));
digitalWrite(LED_PIN, LOW);
}
}
void connectMQTT() {
if (WiFi.status() != WL_CONNECTED) return;
Serial.print(F("Connecting to MQTT broker..."));
int attempts = 0;
while (!mqttClient.connected() && attempts < 3) {
if (mqttClient.connect(MQTT_CLIENT_ID)) {
Serial.println(F("connected!"));
mqttClient.subscribe(MQTT_TOPIC "/command");
return;
}
Serial.print(".");
delay(1000);
attempts++;
}
if (!mqttClient.connected()) {
Serial.println(F("\nMQTT connection failed!"));
}
}
void mqttCallback(char* topic, byte* payload, unsigned int length) {
Serial.print(F("MQTT message: "));
for (unsigned int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
void publishSensorData() {
if (!data.valid || !mqttClient.connected()) return;
// Create JSON payload
StaticJsonDocument<200> doc;
doc["temperature"] = data.temperature;
doc["humidity"] = data.humidity;
doc["uptime"] = millis() / 1000;
doc["rssi"] = WiFi.RSSI();
char jsonBuffer[200];
serializeJson(doc, jsonBuffer);
if (mqttClient.publish(MQTT_TOPIC, jsonBuffer)) {
Serial.print(F("Published: "));
Serial.println(jsonBuffer);
} else {
Serial.println(F("Publish failed!"));
}
}
void loop() {
// Task 1: Read DHT22
if (dhtTimer.check()) {
data.temperature = dht.readTemperature();
data.humidity = dht.readHumidity();
data.valid = !isnan(data.temperature) && !isnan(data.humidity);
if (data.valid) {
Serial.print(F("Temp: "));
Serial.print(data.temperature, 1);
Serial.print(F("°C | Humidity: "));
Serial.print(data.humidity, 1);
Serial.println(F("%"));
} else {
Serial.println(F("DHT22 read error"));
}
}
// Task 2: Publish to MQTT
if (publishTimer.check()) {
publishSensorData();
}
// Task 3: WiFi reconnect check
if (wifiCheckTimer.check()) {
if (WiFi.status() != WL_CONNECTED) {
Serial.println(F("WiFi disconnected, reconnecting..."));
connectWiFi();
}
if (!mqttClient.connected()) {
Serial.println(F("MQTT disconnected, reconnecting..."));
connectMQTT();
}
}
// Task 4: Button manual reconnect
if (button.pressed()) {
Serial.println(F("Manual reconnect triggered"));
connectWiFi();
connectMQTT();
}
// Task 5: MQTT loop (handle incoming messages)
mqttClient.loop();
// Task 6: LED heartbeat (blink every 2s when connected)
if (WiFi.status() == WL_CONNECTED) {
digitalWrite(LED_PIN, (millis() % 2000) < 100);
} else {
digitalWrite(LED_PIN, LOW);
}
}Upload Instructions: 1. Install libraries:
- PubSubClient (by Nick O'Leary)
- DHT sensor library (by Adafruit)
- ArduinoJson (by Benoit Blanchon)
2. Edit config.h: Set your WiFi SSID, password, and MQTT broker IP 3. Select board: Tools → Board → ESP32 Dev Module 4. Select port: Tools → Port → (your ESP32's port) 5. Upload sketch
MQTT Broker Setup: Install Mosquitto MQTT broker on your server:
# Ubuntu/Debian
sudo apt-get install mosquitto mosquitto-clients
# Start broker
sudo systemctl start mosquitto
# Test subscription
mosquitto_sub -h localhost -t "home/esp32/sensor"Usage:
- System connects to WiFi on boot
- DHT22 readings every 2 seconds (local)
- MQTT publish every 60 seconds
- Press button to force WiFi/MQTT reconnect
- LED heartbeat when connected, off when disconnected
JSON Payload Format:
{
"temperature": 23.5,
"humidity": 45.2,
"uptime": 3600,
"rssi": -65
}Expected Serial Output:
=== ESP32 IoT Logger ===
Connecting to WiFi.........
WiFi connected!
IP: 192.168.1.42
Connecting to MQTT broker...connected!
Temp: 23.5°C | Humidity: 45.2%
Published: {"temperature":23.5,"humidity":45.2,"uptime":60,"rssi":-65}
Temp: 23.6°C | Humidity: 45.0%
Published: {"temperature":23.6,"humidity":45.0,"uptime":120,"rssi":-67}Home Assistant Integration: Add to configuration.yaml:
sensor:
- platform: mqtt
name: "ESP32 Temperature"
state_topic: "home/esp32/sensor"
unit_of_measurement: "°C"
value_template: "{{ value_json.temperature }}"
- platform: mqtt
name: "ESP32 Humidity"
state_topic: "home/esp32/sensor"
unit_of_measurement: "%"
value_template: "{{ value_json.humidity }}"Troubleshooting:
- WiFi won't connect: Check SSID/password, ensure 2.4GHz network (ESP32 doesn't support 5GHz)
- MQTT connection failed: Verify broker IP, ensure port 1883 is open, check firewall
- DHT22 read error: Use 3.3V (not 5V), check DATA pin connection
- JSON publish failed: Increase document size in StaticJsonDocument<200>
- Memory issues: ESP32 has 327KB SRAM, no worries about overflow
Power Consumption:
- Active (WiFi on): ~160mA
- Light sleep: ~20mA
- Deep sleep: ~10μA (add sleep mode for battery operation)
Robot Controller Project
Description: Button-controlled robot with obstacle avoidance using ultrasonic sensor and state machine architecture. Supports forward, turn left, turn right, and emergency stop modes.
Hardware Requirements:
- Arduino UNO or ESP32
- L298N motor driver module
- 2x DC motors with wheels
- HC-SR04 ultrasonic distance sensor
- 3x pushbuttons (forward, left, right)
- Battery pack (7.4V LiPo recommended)
- LED (status indicator)
Wiring Diagram:
L298N Motor Driver:
ENA → Pin 9 (PWM for left motor speed)
IN1 → Pin 8 (left motor direction 1)
IN2 → Pin 7 (left motor direction 2)
ENB → Pin 6 (PWM for right motor speed)
IN3 → Pin 5 (right motor direction 1)
IN4 → Pin 4 (right motor direction 2)
VCC → Battery + (7.4V)
GND → Arduino GND and Battery -
5V → Arduino VIN (regulated 5V output from L298N)
HC-SR04 Ultrasonic:
VCC → 5V
TRIG → Pin 11
ECHO → Pin 10
GND → GND
Buttons:
Forward button → Pin 2 (INPUT_PULLUP)
Left button → Pin 3 (INPUT_PULLUP)
Right button → Pin 12 (INPUT_PULLUP)
LED:
Pin 13 → 220Ω resistor → LED anode
LED cathode → GNDFeatures:
- State machine with 5 states (IDLE, FORWARD, TURN_LEFT, TURN_RIGHT, OBSTACLE_DETECTED)
- Autonomous obstacle avoidance (stops at 20cm, turns right)
- Button override (manual control)
- PWM motor speed control
- Non-blocking ultrasonic sensor reads
- Emergency stop on low battery (if voltage sensor added)
Complete Code:
// config.h
#define LEFT_MOTOR_ENA 9
#define LEFT_MOTOR_IN1 8
#define LEFT_MOTOR_IN2 7
#define RIGHT_MOTOR_ENB 6
#define RIGHT_MOTOR_IN3 5
#define RIGHT_MOTOR_IN4 4
#define ULTRASONIC_TRIG 11
#define ULTRASONIC_ECHO 10
#define BUTTON_FORWARD 2
#define BUTTON_LEFT 3
#define BUTTON_RIGHT 12
#define LED_PIN 13
#define OBSTACLE_THRESHOLD_CM 20
#define MOTOR_SPEED 200 // 0-255 (PWM)
#define TURN_DURATION_MS 800
// main.ino
// Robot state machine
enum RobotState {
IDLE,
MOVING_FORWARD,
TURNING_LEFT,
TURNING_RIGHT,
OBSTACLE_DETECTED
};
RobotState state = IDLE;
unsigned long stateStartTime = 0;
// EveryMs timer
class EveryMs {
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {}
bool check() {
unsigned long now = millis();
if (now - lastTrigger >= interval) {
lastTrigger = now;
return true;
}
return false;
}
};
// Debounced button
class DebouncedButton {
private:
uint8_t pin;
bool lastState;
unsigned long lastDebounceTime;
public:
DebouncedButton(uint8_t p) : pin(p), lastState(HIGH), lastDebounceTime(0) {}
void begin() {
pinMode(pin, INPUT_PULLUP);
}
bool pressed() {
bool currentState = digitalRead(pin);
if (currentState != lastState) {
lastDebounceTime = millis();
}
if ((millis() - lastDebounceTime) > 50) {
if (currentState == LOW && lastState == HIGH) {
lastState = currentState;
return true;
}
}
lastState = currentState;
return false;
}
};
// Global objects
DebouncedButton btnForward(BUTTON_FORWARD);
DebouncedButton btnLeft(BUTTON_LEFT);
DebouncedButton btnRight(BUTTON_RIGHT);
EveryMs distanceTimer(100); // Check distance every 100ms
int distanceCm = 999;
void setup() {
Serial.begin(9600);
// Motor pins
pinMode(LEFT_MOTOR_ENA, OUTPUT);
pinMode(LEFT_MOTOR_IN1, OUTPUT);
pinMode(LEFT_MOTOR_IN2, OUTPUT);
pinMode(RIGHT_MOTOR_ENB, OUTPUT);
pinMode(RIGHT_MOTOR_IN3, OUTPUT);
pinMode(RIGHT_MOTOR_IN4, OUTPUT);
// Ultrasonic pins
pinMode(ULTRASONIC_TRIG, OUTPUT);
pinMode(ULTRASONIC_ECHO, INPUT);
// LED
pinMode(LED_PIN, OUTPUT);
// Buttons
btnForward.begin();
btnLeft.begin();
btnRight.begin();
stopMotors();
Serial.println(F("=== Robot Controller ==="));
Serial.println(F("States: IDLE, FORWARD, LEFT, RIGHT, OBSTACLE"));
}
void updateState(RobotState newState) {
state = newState;
stateStartTime = millis();
Serial.print(F("State: "));
Serial.println(newState);
}
void setMotors(int leftSpeed, int rightSpeed) {
// Left motor
if (leftSpeed > 0) {
digitalWrite(LEFT_MOTOR_IN1, HIGH);
digitalWrite(LEFT_MOTOR_IN2, LOW);
analogWrite(LEFT_MOTOR_ENA, abs(leftSpeed));
} else if (leftSpeed < 0) {
digitalWrite(LEFT_MOTOR_IN1, LOW);
digitalWrite(LEFT_MOTOR_IN2, HIGH);
analogWrite(LEFT_MOTOR_ENA, abs(leftSpeed));
} else {
digitalWrite(LEFT_MOTOR_IN1, LOW);
digitalWrite(LEFT_MOTOR_IN2, LOW);
analogWrite(LEFT_MOTOR_ENA, 0);
}
// Right motor
if (rightSpeed > 0) {
digitalWrite(RIGHT_MOTOR_IN3, HIGH);
digitalWrite(RIGHT_MOTOR_IN4, LOW);
analogWrite(RIGHT_MOTOR_ENB, abs(rightSpeed));
} else if (rightSpeed < 0) {
digitalWrite(RIGHT_MOTOR_IN3, LOW);
digitalWrite(RIGHT_MOTOR_IN4, HIGH);
analogWrite(RIGHT_MOTOR_ENB, abs(rightSpeed));
} else {
digitalWrite(RIGHT_MOTOR_IN3, LOW);
digitalWrite(RIGHT_MOTOR_IN4, LOW);
analogWrite(RIGHT_MOTOR_ENB, 0);
}
}
void stopMotors() {
setMotors(0, 0);
}
int readUltrasonicCm() {
digitalWrite(ULTRASONIC_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(ULTRASONIC_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(ULTRASONIC_TRIG, LOW);
long duration = pulseIn(ULTRASONIC_ECHO, HIGH, 30000); // 30ms timeout
if (duration == 0) return 999; // No echo
return duration * 0.034 / 2; // Convert to cm
}
void loop() {
// Task 1: Read distance sensor
if (distanceTimer.check()) {
distanceCm = readUltrasonicCm();
}
// Task 2: Check buttons
if (btnForward.pressed()) {
updateState(MOVING_FORWARD);
}
if (btnLeft.pressed()) {
updateState(TURNING_LEFT);
}
if (btnRight.pressed()) {
updateState(TURNING_RIGHT);
}
// Task 3: State machine
unsigned long elapsed = millis() - stateStartTime;
switch (state) {
case IDLE:
stopMotors();
digitalWrite(LED_PIN, LOW);
break;
case MOVING_FORWARD:
setMotors(MOTOR_SPEED, MOTOR_SPEED);
digitalWrite(LED_PIN, HIGH);
// Check for obstacle
if (distanceCm < OBSTACLE_THRESHOLD_CM) {
updateState(OBSTACLE_DETECTED);
}
break;
case OBSTACLE_DETECTED:
stopMotors();
digitalWrite(LED_PIN, (millis() % 200) < 100); // Blink fast
if (elapsed >= 500) {
// Turn right to avoid obstacle
updateState(TURNING_RIGHT);
}
break;
case TURNING_LEFT:
setMotors(-MOTOR_SPEED, MOTOR_SPEED); // Left backward, right forward
digitalWrite(LED_PIN, (millis() % 500) < 250); // Blink
if (elapsed >= TURN_DURATION_MS) {
updateState(IDLE);
}
break;
case TURNING_RIGHT:
setMotors(MOTOR_SPEED, -MOTOR_SPEED); // Left forward, right backward
digitalWrite(LED_PIN, (millis() % 500) < 250); // Blink
if (elapsed >= TURN_DURATION_MS) {
updateState(IDLE);
}
break;
}
// Debug output
if (Serial.available() && Serial.read() == 'd') {
Serial.print(F("Distance: "));
Serial.print(distanceCm);
Serial.print(F("cm | State: "));
Serial.println(state);
}
}Upload Instructions: 1. Select board: Tools → Board → Arduino UNO 2. Select port: Tools → Port → (your Arduino's port) 3. Upload sketch 4. Power robot with battery (NOT USB)
Usage:
- Press Forward button to start moving forward
- Press Left button to turn left for 800ms, then stop
- Press Right button to turn right for 800ms, then stop
- Robot automatically stops and turns right when obstacle detected (<20cm)
- LED blinks during turns, solid during forward movement
Safety:
- ALWAYS test with wheels off the ground first
- Ensure battery voltage is appropriate (7.4V recommended)
- Emergency stop: remove battery or press Reset button
- Add battery voltage monitoring for low-battery warning
Expected Serial Output:
=== Robot Controller ===
States: IDLE, FORWARD, LEFT, RIGHT, OBSTACLE
State: 1
State: 4
Distance: 15cm | State: 4
State: 3
State: 0Troubleshooting:
- Motors not spinning: Check L298N wiring, ensure battery connected
- Motors spin slowly: Battery voltage too low (need 7.4V minimum)
- Ultrasonic returns 999: Check TRIG/ECHO wiring, ensure 5V power
- Robot turns wrong direction: Swap IN1/IN2 or IN3/IN4 connections
- Buttons don't work: Verify INPUT_PULLUP, check button wiring
Board-Specific Considerations
Hardware and software optimizations for different Arduino-compatible boards.
Arduino UNO/Nano (ATmega328P)
Memory Constraints
- SRAM: 2KB total - Keep arrays small, monitor usage constantly
- Flash: 32KB - Code size limited, optimize for space
- EEPROM: 1KB - Use sparingly, implement wear leveling
Hardware Features
- ADC: 10-bit resolution (0-1023 range)
- PWM: 6 pins (3, 5, 6, 9, 10, 11)
- Interrupts: Only 2 external (pins 2, 3)
- I2C: Software implementation, slower than hardware
Optimization Strategies
- Use F() macro for all string literals to save RAM
- Avoid large arrays and complex data structures
- Implement simple filtering algorithms
- Use interrupt-driven inputs sparingly
Communication
- No built-in WiFi/Bluetooth
- Serial communication at 9600 baud recommended
- External modules required for wireless connectivity
ESP32 (ESP32-WROOM-32)
Memory Resources
- SRAM: 327KB+ - Can use large buffers and complex structures
- Flash: 4MB+ - Ample space for code and data
- PSRAM: Optional external RAM for large datasets
Hardware Features
- ADC: 12-bit resolution (0-4095 range), 18 channels
- PWM: 16 channels with adjustable frequency
- Interrupts: Multiple GPIO pins support interrupts
- I2C/SPI: Hardware accelerated, multiple buses
Advanced Capabilities
- Dual-core: Run tasks in parallel on CPU0/CPU1
- WiFi/Bluetooth: Built-in 802.11 b/g/n WiFi, Bluetooth 4.2
- Deep sleep: Ultra-low power consumption modes
- RTC: Real-time clock with battery backup
IoT Optimization
- Implement WiFi reconnection with exponential backoff
- Use FreeRTOS tasks for concurrent operations
- Leverage deep sleep for battery-powered applications
- Implement OTA (Over-The-Air) updates
Raspberry Pi Pico (RP2040)
Memory Resources
- SRAM: 262KB - Good balance between UNO and ESP32
- Flash: 2MB - Sufficient for most applications
- No EEPROM: Use flash for persistent storage
Hardware Features
- ADC: 12-bit resolution (0-4095 range), 5 channels
- PWM: 16 channels, 8 slices
- PIO: Programmable I/O for custom protocols
- Interrupts: All GPIO pins support interrupts
Advanced Features
- Dual-core: Cortex-M0+ cores for parallel processing
- PIO State Machines: Hardware-accelerated custom protocols
- USB: Full-speed USB 1.1 with device/host support
- High-speed interfaces: SPI, I2C, UART with DMA
Performance Optimization
- Utilize PIO for timing-critical operations
- Implement DMA for high-speed data transfer
- Use both cores for computationally intensive tasks
- Leverage USB for high-bandwidth communication
Cross-Board Compatibility
Conditional Compilation
#ifdef ARDUINO_AVR_UNO
// UNO-specific code
#elif defined(ESP32)
// ESP32-specific code
#elif defined(ARDUINO_ARCH_RP2040)
// Pico-specific code
#endifHardware Detection
- Implement runtime board detection
- Use conditional compilation for optimal performance
- Provide fallback implementations for missing features
Memory Management
- Monitor memory usage across all platforms
- Implement different strategies for different memory sizes
- Use heap allocation carefully on constrained boards
Communication Abstraction
- Abstract communication interfaces (WiFi, Serial)
- Provide board-specific implementations
- Gracefully degrade when features unavailable
Integration Checklist
Comprehensive checklist to verify before delivering any Arduino project.
Sensor Integration
- [ ] All sensor readings validated (NaN checks, range checks)
- [ ] Sensor initialization successful (I2C scan, SPI connection)
- [ ] Calibration values applied correctly
- [ ] Sensor error handling implemented (timeout, retry logic)
- [ ] Multiple sensors don't interfere with each other
Input Handling
- [ ] Button inputs debounced (minimum 50ms debounce time)
- [ ] Interrupt pins used appropriately (UNO limited to pins 2,3)
- [ ] Analog inputs filtered for noise reduction
- [ ] Input validation prevents invalid states
Communication Systems
- [ ] I2C devices scanned and detected at startup
- [ ] SPI communication verified (clock polarity, phase)
- [ ] UART baud rates match between devices
- [ ] WiFi reconnection logic implemented (ESP32 projects)
- [ ] MQTT connection handling with retry logic
Data Management
- [ ] CSV logging includes proper headers
- [ ] Data formatting consistent (timestamps, units)
- [ ] Buffer sizes adequate for data rates
- [ ] EEPROM writes include CRC validation
- [ ] SD card file operations error-checked
State Machine Logic
- [ ] All states defined with clear transitions
- [ ] Default/error states implemented
- [ ] State transitions validated
- [ ] No infinite loops or deadlocks
- [ ] State persistence if required
Timing & Performance
- [ ] All timers use non-blocking millis() patterns
- [ ] Loop execution time within requirements
- [ ] Interrupt service routines are short
- [ ] Watchdog timer implemented for critical systems
- [ ] Power management for battery-operated devices
User Interface
- [ ] LED indicators for system status (heartbeat, error, active)
- [ ] Serial commands documented and implemented
- [ ] Serial baud rate matches board (9600 for UNO, 115200 for ESP32)
- [ ] User feedback for all interactive elements
Memory Management
- [ ] SRAM usage monitored and within limits
- [ ] String literals use F() macro to save RAM
- [ ] Dynamic memory allocation minimized
- [ ] Stack overflow protection implemented
Power Systems
- [ ] Power supply adequate for all components
- [ ] Brown-out detection implemented
- [ ] Sleep modes utilized for low-power applications
- [ ] Power-on reset sequence proper
Safety & Reliability
- [ ] Fail-safe modes for critical failures
- [ ] Watchdog timer prevents hangs
- [ ] Input validation prevents buffer overflows
- [ ] Critical operations have timeout protection
Documentation
- [ ] Wiring diagram complete and accurate
- [ ] Code comments explain complex logic
- [ ] Usage instructions clear and complete
- [ ] Troubleshooting guide addresses common issues
Quality Standards
All generated Arduino projects must adhere to these quality standards for production readiness.
Core Requirements
1. Hardware Abstraction
- config.h file: Required for all projects with board detection and pin definitions
- Conditional compilation: Use #ifdef/#endif for board-specific code
- Pin constants: Define all pins as named constants, not magic numbers
2. Non-blocking Design
- No delay() calls: Use millis() timers for all timing requirements
- EveryMs pattern: Implement task scheduling without blocking
- Responsive loop: Main loop must complete within timing constraints
3. Error Handling
- Sensor validation: Check for NaN, out-of-range, and failure conditions
- Graceful degradation: Continue operation when non-critical components fail
- Error states: State machines must include error/default states
4. Diagnostics & Monitoring
- Serial output: Print status messages and sensor readings
- Heartbeat indicators: LED blinking or serial status updates
- Debug levels: Different verbosity levels for troubleshooting
5. Memory Safety
- Bounds checking: Validate array indices before access
- CRC validation: Use CRC for EEPROM and critical data storage
- Memory monitoring: Track SRAM usage on constrained boards
6. Documentation Standards
- Wiring tables: Clear text-based pin assignment documentation
- Code comments: Explain complex logic and hardware interactions
- Usage instructions: Include setup and operation procedures
7. Compilation Verification
- Board compatibility: Verify compilation for target board
- Library dependencies: Ensure all required libraries are available
- Optimization flags: Use appropriate compiler optimizations
Code Quality Metrics
- Cyclomatic complexity: Keep functions under 10 complexity points
- Function length: Limit functions to 50 lines maximum
- Global variables: Minimize use, prefer local scope
- Magic numbers: Replace with named constants
Testing Requirements
- Unit testing: Test individual components in isolation
- Integration testing: Verify component interactions
- Performance testing: Ensure timing requirements are met
- Memory testing: Validate memory usage under load
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
"""
Arduino Project Scaffolding Tool
Generate complete Arduino project structures with config.h, main.ino,
platformio.ini, and README.md from project templates.
Usage:
uv run --no-project scripts/scaffold_project.py --type environmental --board esp32 --name "WeatherStation"
uv run --no-project scripts/scaffold_project.py --type robot --board uno --output ./my-robot
uv run --no-project scripts/scaffold_project.py --interactive
uv run --no-project scripts/scaffold_project.py --list
"""
import argparse
import os
import sys
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional
# =============================================================================
# Project Templates
# =============================================================================
PROJECT_TYPES = {
"environmental": {
"name": "Environmental Monitor",
"description": "Multi-sensor data logger (temperature, humidity, light)",
"sensors": ["DHT22", "Photoresistor"],
"features": ["CSV logging", "SD card (optional)", "Button control", "LED status"],
"patterns": ["config", "filtering", "scheduler", "csv", "data-logging"],
"libraries": ["DHT sensor library"],
},
"robot": {
"name": "Robot Controller",
"description": "Motor control with obstacle avoidance and state machine",
"sensors": ["Ultrasonic HC-SR04", "Line sensor (optional)"],
"actuators": ["DC motors (L298N)", "Servo"],
"features": ["State machine", "Button control", "Obstacle avoidance"],
"patterns": ["config", "buttons", "state-machine", "scheduler"],
"libraries": ["Servo"],
},
"iot": {
"name": "IoT Data Logger",
"description": "WiFi-connected sensor with MQTT/HTTP data transmission",
"sensors": ["BME280", "DHT22 (alternative)"],
"features": ["WiFi connectivity", "MQTT publishing", "JSON formatting", "Deep sleep"],
"patterns": ["config", "hardware-detection", "scheduler", "filtering"],
"libraries": ["WiFi", "PubSubClient", "ArduinoJson"],
"board_requirement": "esp32",
},
}
BOARD_CONFIGS = {
"uno": {
"name": "Arduino UNO",
"platform": "atmelavr",
"board": "uno",
"framework": "arduino",
"f_cpu": "16000000L",
"baud": 9600,
"sram": 2048,
"defines": ["ARDUINO_AVR_UNO"],
},
"esp32": {
"name": "ESP32 DevKit",
"platform": "espressif32",
"board": "esp32dev",
"framework": "arduino",
"f_cpu": "240000000L",
"baud": 115200,
"sram": 520000,
"defines": ["ESP32"],
},
"rp2040": {
"name": "Raspberry Pi Pico",
"platform": "raspberrypi",
"board": "pico",
"framework": "arduino",
"f_cpu": "133000000L",
"baud": 115200,
"sram": 264000,
"defines": ["ARDUINO_ARCH_RP2040"],
},
}
# =============================================================================
# Template Generators
# =============================================================================
def generate_config_h(project_type: str, board: str, project_name: str) -> str:
"""Generate config.h with board-specific settings."""
proj = PROJECT_TYPES[project_type]
config = f'''// config.h - Hardware configuration for {project_name}
// Project: {proj["name"]}
// Generated: {datetime.now().strftime("%Y-%m-%d")}
#ifndef CONFIG_H
#define CONFIG_H
// === Board Detection ===
#if defined(ARDUINO_AVR_UNO) || defined(ARDUINO_AVR_NANO)
#define BOARD_NAME "Arduino UNO"
#define LED_PIN 13
#define BUTTON_PIN 2
#define I2C_SDA A4
#define I2C_SCL A5
#define SRAM_SIZE 2048
#define SERIAL_BAUD 9600
#define USE_F_MACRO 1 // Use F() for string constants
#elif defined(ESP32)
#define BOARD_NAME "ESP32"
#define LED_PIN 2
#define BUTTON_PIN 4
#define I2C_SDA 21
#define I2C_SCL 22
#define SRAM_SIZE 520000
#define SERIAL_BAUD 115200
#define HAS_WIFI 1
#define HAS_BLE 1
#elif defined(ARDUINO_ARCH_RP2040)
#define BOARD_NAME "RP2040"
#define LED_PIN 25
#define BUTTON_PIN 14
#define I2C_SDA 4
#define I2C_SCL 5
#define SRAM_SIZE 264000
#define SERIAL_BAUD 115200
#else
#error "Unsupported board! Add configuration."
#endif
'''
# Project-specific pins
if project_type == "environmental":
config += '''// === Environmental Monitor Pins ===
#define DHT_PIN 2
#define LIGHT_PIN A0
#define SD_CS_PIN 10
// Timing intervals (ms)
#define DHT_INTERVAL 2000 // DHT22 needs 2s between reads
#define LIGHT_INTERVAL 1000
#define LOG_INTERVAL 60000
#define HEARTBEAT_INTERVAL 2000
'''
elif project_type == "robot":
config += '''// === Robot Controller Pins ===
#define MOTOR_L_EN 5
#define MOTOR_L_IN1 6
#define MOTOR_L_IN2 7
#define MOTOR_R_EN 9
#define MOTOR_R_IN1 10
#define MOTOR_R_IN2 11
#define ULTRASONIC_TRIG 12
#define ULTRASONIC_ECHO 3
#define SERVO_PIN 8
// Robot parameters
#define OBSTACLE_DISTANCE_CM 20
#define MOTOR_SPEED_DEFAULT 200
#define TURN_DURATION_MS 500
'''
elif project_type == "iot":
config += '''// === IoT Device Configuration ===
#define DHT_PIN 4
#define STATUS_LED 2
// WiFi credentials (change these!)
#define WIFI_SSID "your-wifi-ssid"
#define WIFI_PASSWORD "your-wifi-password"
// MQTT settings
#define MQTT_SERVER "mqtt.example.com"
#define MQTT_PORT 1883
#define MQTT_TOPIC "sensors/environmental"
// Timing intervals (ms)
#define SENSOR_INTERVAL 30000 // Read sensors every 30s
#define PUBLISH_INTERVAL 60000 // Publish every 60s
#define WIFI_TIMEOUT 30000 // WiFi connection timeout
'''
config += '''// === Common Settings ===
#define DEBOUNCE_MS 50
#define FILTER_SIZE 10
#endif // CONFIG_H
'''
return config
def generate_main_ino(project_type: str, board: str, project_name: str) -> str:
"""Generate main.ino for the project type."""
proj = PROJECT_TYPES[project_type]
if project_type == "environmental":
return f'''// {project_name} - Environmental Monitor
// {proj["description"]}
// Generated: {datetime.now().strftime("%Y-%m-%d")}
#include "config.h"
#include <DHT.h>
// === Timer Class ===
class EveryMs {{
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {{}}
bool check() {{
unsigned long now = millis();
if (now - lastTrigger >= interval) {{
lastTrigger = now;
return true;
}}
return false;
}}
}};
// === Moving Average Filter ===
class MovingAverageFilter {{
private:
static const uint8_t SIZE = FILTER_SIZE;
int values[SIZE];
uint8_t index = 0;
uint8_t count = 0;
public:
int filter(int newValue) {{
values[index] = newValue;
index = (index + 1) % SIZE;
if (count < SIZE) count++;
long sum = 0;
for (uint8_t i = 0; i < count; i++) sum += values[i];
return sum / count;
}}
}};
// === Debounced Button ===
class DebouncedButton {{
private:
uint8_t pin;
bool lastState = HIGH;
unsigned long lastDebounce = 0;
public:
DebouncedButton(uint8_t p) : pin(p) {{}}
void begin() {{ pinMode(pin, INPUT_PULLUP); }}
bool pressed() {{
bool state = digitalRead(pin);
if (state != lastState && (millis() - lastDebounce) > DEBOUNCE_MS) {{
lastDebounce = millis();
lastState = state;
return state == LOW;
}}
return false;
}}
}};
// === Global Objects ===
DHT dht(DHT_PIN, DHT22);
MovingAverageFilter lightFilter;
DebouncedButton button(BUTTON_PIN);
EveryMs dhtTimer(DHT_INTERVAL);
EveryMs lightTimer(LIGHT_INTERVAL);
EveryMs logTimer(LOG_INTERVAL);
EveryMs heartbeatTimer(HEARTBEAT_INTERVAL);
struct SensorData {{
float temperature = NAN;
float humidity = NAN;
int lightLevel = 0;
}} data;
bool loggingEnabled = true;
bool ledState = false;
void setup() {{
Serial.begin(SERIAL_BAUD);
pinMode(LED_PIN, OUTPUT);
button.begin();
dht.begin();
Serial.println(F("=== {project_name} ==="));
Serial.print(F("Board: "));
Serial.println(F(BOARD_NAME));
Serial.println(F("time_ms,temp_c,humidity_%,light"));
}}
void loop() {{
// Heartbeat LED
if (heartbeatTimer.check()) {{
ledState = !ledState;
digitalWrite(LED_PIN, ledState);
}}
// Toggle logging with button
if (button.pressed()) {{
loggingEnabled = !loggingEnabled;
Serial.print(F("Logging: "));
Serial.println(loggingEnabled ? F("ON") : F("OFF"));
}}
// Read DHT22
if (dhtTimer.check()) {{
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) data.temperature = t;
if (!isnan(h)) data.humidity = h;
}}
// Read light sensor
if (lightTimer.check()) {{
data.lightLevel = lightFilter.filter(analogRead(LIGHT_PIN));
}}
// Log data
if (loggingEnabled && logTimer.check()) {{
Serial.print(millis());
Serial.print(',');
Serial.print(data.temperature, 1);
Serial.print(',');
Serial.print(data.humidity, 1);
Serial.print(',');
Serial.println(data.lightLevel);
}}
}}
'''
elif project_type == "robot":
return f'''// {project_name} - Robot Controller
// {proj["description"]}
// Generated: {datetime.now().strftime("%Y-%m-%d")}
#include "config.h"
#include <Servo.h>
// === State Machine ===
enum class RobotState {{
IDLE,
FORWARD,
TURNING_LEFT,
TURNING_RIGHT,
REVERSE,
SCANNING
}};
const char* stateNames[] = {{"IDLE", "FORWARD", "LEFT", "RIGHT", "REVERSE", "SCAN"}};
// === Timer Class ===
class EveryMs {{
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {{}}
bool check() {{
unsigned long now = millis();
if (now - lastTrigger >= interval) {{
lastTrigger = now;
return true;
}}
return false;
}}
void reset() {{ lastTrigger = millis(); }}
}};
// === Debounced Button ===
class DebouncedButton {{
private:
uint8_t pin;
bool lastState = HIGH;
unsigned long lastDebounce = 0;
public:
DebouncedButton(uint8_t p) : pin(p) {{}}
void begin() {{ pinMode(pin, INPUT_PULLUP); }}
bool pressed() {{
bool state = digitalRead(pin);
if (state != lastState && (millis() - lastDebounce) > DEBOUNCE_MS) {{
lastDebounce = millis();
lastState = state;
return state == LOW;
}}
return false;
}}
}};
// === Global Objects ===
Servo servo;
DebouncedButton startButton(BUTTON_PIN);
RobotState currentState = RobotState::IDLE;
unsigned long stateStartTime = 0;
EveryMs sensorTimer(50); // Check sensors every 50ms
EveryMs statusTimer(1000); // Print status every 1s
// === Motor Control ===
void setMotors(int leftSpeed, int rightSpeed) {{
// Left motor
if (leftSpeed >= 0) {{
digitalWrite(MOTOR_L_IN1, HIGH);
digitalWrite(MOTOR_L_IN2, LOW);
}} else {{
digitalWrite(MOTOR_L_IN1, LOW);
digitalWrite(MOTOR_L_IN2, HIGH);
leftSpeed = -leftSpeed;
}}
analogWrite(MOTOR_L_EN, constrain(leftSpeed, 0, 255));
// Right motor
if (rightSpeed >= 0) {{
digitalWrite(MOTOR_R_IN1, HIGH);
digitalWrite(MOTOR_R_IN2, LOW);
}} else {{
digitalWrite(MOTOR_R_IN1, LOW);
digitalWrite(MOTOR_R_IN2, HIGH);
rightSpeed = -rightSpeed;
}}
analogWrite(MOTOR_R_EN, constrain(rightSpeed, 0, 255));
}}
void stopMotors() {{
setMotors(0, 0);
}}
// === Ultrasonic Sensor ===
long readDistanceCm() {{
digitalWrite(ULTRASONIC_TRIG, LOW);
delayMicroseconds(2);
digitalWrite(ULTRASONIC_TRIG, HIGH);
delayMicroseconds(10);
digitalWrite(ULTRASONIC_TRIG, LOW);
long duration = pulseIn(ULTRASONIC_ECHO, HIGH, 30000);
return duration * 0.034 / 2; // Convert to cm
}}
// === State Machine ===
void transitionTo(RobotState newState) {{
if (newState != currentState) {{
currentState = newState;
stateStartTime = millis();
Serial.print(F("State: "));
Serial.println(stateNames[static_cast<int>(currentState)]);
}}
}}
void updateStateMachine() {{
long distance = readDistanceCm();
unsigned long elapsed = millis() - stateStartTime;
switch (currentState) {{
case RobotState::IDLE:
stopMotors();
break;
case RobotState::FORWARD:
setMotors(MOTOR_SPEED_DEFAULT, MOTOR_SPEED_DEFAULT);
if (distance > 0 && distance < OBSTACLE_DISTANCE_CM) {{
transitionTo(RobotState::REVERSE);
}}
break;
case RobotState::REVERSE:
setMotors(-MOTOR_SPEED_DEFAULT/2, -MOTOR_SPEED_DEFAULT/2);
if (elapsed > 500) {{
transitionTo(RobotState::SCANNING);
}}
break;
case RobotState::SCANNING:
stopMotors();
// Scan left and right, pick clearest direction
servo.write(45);
delay(200);
long leftDist = readDistanceCm();
servo.write(135);
delay(200);
long rightDist = readDistanceCm();
servo.write(90);
if (leftDist > rightDist) {{
transitionTo(RobotState::TURNING_LEFT);
}} else {{
transitionTo(RobotState::TURNING_RIGHT);
}}
break;
case RobotState::TURNING_LEFT:
setMotors(-MOTOR_SPEED_DEFAULT, MOTOR_SPEED_DEFAULT);
if (elapsed > TURN_DURATION_MS) {{
transitionTo(RobotState::FORWARD);
}}
break;
case RobotState::TURNING_RIGHT:
setMotors(MOTOR_SPEED_DEFAULT, -MOTOR_SPEED_DEFAULT);
if (elapsed > TURN_DURATION_MS) {{
transitionTo(RobotState::FORWARD);
}}
break;
}}
}}
void setup() {{
Serial.begin(SERIAL_BAUD);
// Motor pins
pinMode(MOTOR_L_EN, OUTPUT);
pinMode(MOTOR_L_IN1, OUTPUT);
pinMode(MOTOR_L_IN2, OUTPUT);
pinMode(MOTOR_R_EN, OUTPUT);
pinMode(MOTOR_R_IN1, OUTPUT);
pinMode(MOTOR_R_IN2, OUTPUT);
// Ultrasonic pins
pinMode(ULTRASONIC_TRIG, OUTPUT);
pinMode(ULTRASONIC_ECHO, INPUT);
// Servo
servo.attach(SERVO_PIN);
servo.write(90);
// Button
startButton.begin();
pinMode(LED_PIN, OUTPUT);
Serial.println(F("=== {project_name} ==="));
Serial.println(F("Press button to start/stop"));
}}
void loop() {{
// Start/stop with button
if (startButton.pressed()) {{
if (currentState == RobotState::IDLE) {{
transitionTo(RobotState::FORWARD);
}} else {{
transitionTo(RobotState::IDLE);
}}
}}
// Update state machine
if (sensorTimer.check()) {{
updateStateMachine();
}}
// Status LED
digitalWrite(LED_PIN, currentState != RobotState::IDLE);
// Print status
if (statusTimer.check()) {{
Serial.print(F("Distance: "));
Serial.print(readDistanceCm());
Serial.println(F(" cm"));
}}
}}
'''
elif project_type == "iot":
return f'''// {project_name} - IoT Data Logger
// {proj["description"]}
// Generated: {datetime.now().strftime("%Y-%m-%d")}
// Requires: ESP32
#include "config.h"
#ifdef HAS_WIFI
#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
// === Timer Class ===
class EveryMs {{
private:
unsigned long interval;
unsigned long lastTrigger;
public:
EveryMs(unsigned long ms) : interval(ms), lastTrigger(0) {{}}
bool check() {{
unsigned long now = millis();
if (now - lastTrigger >= interval) {{
lastTrigger = now;
return true;
}}
return false;
}}
}};
// === Global Objects ===
WiFiClient wifiClient;
PubSubClient mqtt(wifiClient);
DHT dht(DHT_PIN, DHT22);
EveryMs sensorTimer(SENSOR_INTERVAL);
EveryMs publishTimer(PUBLISH_INTERVAL);
EveryMs statusTimer(5000);
struct SensorData {{
float temperature = NAN;
float humidity = NAN;
}} data;
// === WiFi Management ===
bool connectWiFi() {{
if (WiFi.status() == WL_CONNECTED) return true;
Serial.print(F("Connecting to WiFi"));
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
unsigned long start = millis();
while (WiFi.status() != WL_CONNECTED) {{
if (millis() - start > WIFI_TIMEOUT) {{
Serial.println(F(" FAILED"));
return false;
}}
delay(500);
Serial.print('.');
}}
Serial.println(F(" OK"));
Serial.print(F("IP: "));
Serial.println(WiFi.localIP());
return true;
}}
// === MQTT Management ===
bool connectMQTT() {{
if (mqtt.connected()) return true;
if (!connectWiFi()) return false;
Serial.print(F("Connecting to MQTT..."));
String clientId = "ESP32-";
clientId += String(random(0xffff), HEX);
if (mqtt.connect(clientId.c_str())) {{
Serial.println(F(" OK"));
return true;
}}
Serial.print(F(" FAILED, rc="));
Serial.println(mqtt.state());
return false;
}}
// === JSON Publishing ===
void publishData() {{
if (!connectMQTT()) return;
char json[128];
snprintf(json, sizeof(json),
"{{\\"temp\\":%.1f,\\"humidity\\":%.1f,\\"uptime\\":%lu}}",
data.temperature, data.humidity, millis() / 1000);
if (mqtt.publish(MQTT_TOPIC, json)) {{
Serial.print(F("Published: "));
Serial.println(json);
}} else {{
Serial.println(F("Publish failed"));
}}
}}
void setup() {{
Serial.begin(SERIAL_BAUD);
pinMode(STATUS_LED, OUTPUT);
dht.begin();
mqtt.setServer(MQTT_SERVER, MQTT_PORT);
Serial.println(F("=== {project_name} ==="));
Serial.println(F("IoT Environmental Logger"));
connectWiFi();
}}
void loop() {{
mqtt.loop();
// Status LED (blink when connected)
static bool ledState = false;
if (WiFi.status() == WL_CONNECTED) {{
ledState = !ledState;
digitalWrite(STATUS_LED, ledState);
}} else {{
digitalWrite(STATUS_LED, LOW);
}}
// Read sensors
if (sensorTimer.check()) {{
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) data.temperature = t;
if (!isnan(h)) data.humidity = h;
}}
// Publish data
if (publishTimer.check()) {{
publishData();
}}
// Status output
if (statusTimer.check()) {{
Serial.print(F("Temp: "));
Serial.print(data.temperature, 1);
Serial.print(F("C, Humidity: "));
Serial.print(data.humidity, 1);
Serial.print(F("%, WiFi: "));
Serial.println(WiFi.status() == WL_CONNECTED ? F("OK") : F("DISCONNECTED"));
}}
delay(100);
}}
#else
void setup() {{
Serial.begin(115200);
Serial.println(F("ERROR: IoT project requires ESP32 with WiFi"));
Serial.println(F("Please use --board esp32"));
}}
void loop() {{}}
#endif
'''
return f"// {project_name} - Generated {datetime.now()}\n// TODO: Implement project type '{project_type}'"
def generate_platformio_ini(board: str, project_name: str, libraries: List[str]) -> str:
"""Generate platformio.ini configuration."""
cfg = BOARD_CONFIGS[board]
lib_deps = "\n".join(f" {lib}" for lib in libraries) if libraries else " ; No external libraries"
return f'''; PlatformIO Project Configuration
; {project_name}
; Generated: {datetime.now().strftime("%Y-%m-%d")}
[env:{cfg["board"]}]
platform = {cfg["platform"]}
board = {cfg["board"]}
framework = {cfg["framework"]}
; Serial monitor
monitor_speed = {cfg["baud"]}
; Build flags
build_flags =
-D {cfg["defines"][0]}
; Library dependencies
lib_deps =
{lib_deps}
; Upload settings
upload_speed = 921600
'''
def generate_readme(project_name: str, project_type: str, board: str) -> str:
"""Generate README.md for the project."""
proj = PROJECT_TYPES[project_type]
cfg = BOARD_CONFIGS[board]
sensors = "\n".join(f"- {s}" for s in proj.get("sensors", []))
features = "\n".join(f"- {f}" for f in proj["features"])
return f'''# {project_name}
> {proj["description"]}
## Features
{features}
## Hardware Required
**Board:** {cfg["name"]}
**Sensors:**
{sensors}
## Installation
### PlatformIO (Recommended)
```bash
# Clone/download this project
cd {project_name.lower().replace(" ", "-")}
# Build and upload
pio run -t upload
# Open serial monitor
pio device monitor
```
### Arduino IDE
1. Open `src/main.ino`
2. Select board: **{cfg["name"]}**
3. Install required libraries from Library Manager
4. Upload
## Configuration
Edit `src/config.h` to customize:
- Pin assignments
- Timing intervals
- WiFi credentials (if applicable)
## Usage
1. Connect hardware according to pin definitions
2. Upload firmware
3. Open Serial Monitor at {cfg["baud"]} baud
4. Press button to start/stop (if applicable)
## Troubleshooting
- **No serial output:** Check baud rate ({cfg["baud"]})
- **Sensor not detected:** Verify wiring and I2C address
- **WiFi connection fails:** Check credentials in config.h
## License
MIT License - feel free to use and modify!
---
Generated by Arduino Project Builder
'''
# =============================================================================
# Project Scaffolding
# =============================================================================
def scaffold_project(
project_name: str,
project_type: str,
board: str,
output_dir: Optional[str] = None
) -> str:
"""Create complete project directory structure."""
# Validate inputs
if project_type not in PROJECT_TYPES:
return f"Error: Unknown project type '{project_type}'"
if board not in BOARD_CONFIGS:
return f"Error: Unknown board '{board}'"
# Check board requirement
proj = PROJECT_TYPES[project_type]
if "board_requirement" in proj and board != proj["board_requirement"]:
return f"Error: {proj['name']} requires {proj['board_requirement']} board"
# Create output directory
safe_name = project_name.lower().replace(" ", "-")
if output_dir:
base_path = Path(output_dir)
else:
base_path = Path(safe_name)
src_path = base_path / "src"
try:
base_path.mkdir(parents=True, exist_ok=True)
src_path.mkdir(exist_ok=True)
# Generate files
files_created = []
# config.h
config_content = generate_config_h(project_type, board, project_name)
(src_path / "config.h").write_text(config_content)
files_created.append("src/config.h")
# main.ino
main_content = generate_main_ino(project_type, board, project_name)
(src_path / "main.ino").write_text(main_content)
files_created.append("src/main.ino")
# platformio.ini
libraries = proj.get("libraries", [])
pio_content = generate_platformio_ini(board, project_name, libraries)
(base_path / "platformio.ini").write_text(pio_content)
files_created.append("platformio.ini")
# README.md
readme_content = generate_readme(project_name, project_type, board)
(base_path / "README.md").write_text(readme_content)
files_created.append("README.md")
# .gitignore
gitignore = '''.pio/
.vscode/
*.o
*.elf
*.hex
'''
(base_path / ".gitignore").write_text(gitignore)
files_created.append(".gitignore")
result = f"✓ Created project: {project_name}\n"
result += f" Location: {base_path.absolute()}\n"
result += f" Type: {proj['name']}\n"
result += f" Board: {BOARD_CONFIGS[board]['name']}\n"
result += f" Files created:\n"
for f in files_created:
result += f" - {f}\n"
result += f"\nNext steps:\n"
result += f" cd {base_path}\n"
result += f" pio run -t upload\n"
return result
except Exception as e:
return f"Error creating project: {e}"
def list_project_types():
"""List available project types."""
print("\n=== Available Project Types ===\n")
for key, proj in PROJECT_TYPES.items():
req = f" (requires {proj['board_requirement']})" if "board_requirement" in proj else ""
print(f" {key:15} - {proj['name']}{req}")
print(f" {proj['description']}")
print()
print("=== Supported Boards ===\n")
for key, cfg in BOARD_CONFIGS.items():
print(f" {key:10} - {cfg['name']} ({cfg['sram']} bytes SRAM)")
print()
def interactive_mode():
"""Interactive project creation wizard."""
print("\n🔧 Arduino Project Builder - Interactive Mode\n")
# Get project name
project_name = input("Project name: ").strip()
if not project_name:
project_name = "MyArduinoProject"
# Select project type
print("\nAvailable project types:")
types_list = list(PROJECT_TYPES.keys())
for i, t in enumerate(types_list, 1):
proj = PROJECT_TYPES[t]
print(f" {i}. {t} - {proj['name']}")
while True:
try:
choice = input("\nSelect type (1-3): ").strip()
idx = int(choice) - 1
if 0 <= idx < len(types_list):
project_type = types_list[idx]
break
except ValueError:
pass
print("Invalid choice, try again.")
# Select board
print("\nAvailable boards:")
boards_list = list(BOARD_CONFIGS.keys())
for i, b in enumerate(boards_list, 1):
cfg = BOARD_CONFIGS[b]
print(f" {i}. {b} - {cfg['name']}")
while True:
try:
choice = input("\nSelect board (1-3): ").strip()
idx = int(choice) - 1
if 0 <= idx < len(boards_list):
board = boards_list[idx]
break
except ValueError:
pass
print("Invalid choice, try again.")
# Output directory
output_dir = input(f"\nOutput directory (default: ./{project_name.lower().replace(' ', '-')}): ").strip()
if not output_dir:
output_dir = None
# Create project
print("\n" + "=" * 60)
result = scaffold_project(project_name, project_type, board, output_dir)
print(result)
# =============================================================================
# Main
# =============================================================================
def main():
parser = argparse.ArgumentParser(
description="Scaffold complete Arduino projects from templates",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
uv run --no-project scripts/scaffold_project.py --list
uv run --no-project scripts/scaffold_project.py --type environmental --board esp32 --name "WeatherStation"
uv run --no-project scripts/scaffold_project.py --type robot --board uno --output ./my-robot
uv run --no-project scripts/scaffold_project.py --interactive
""",
)
parser.add_argument("--type", "-t", help="Project type (see --list)")
parser.add_argument("--board", "-b", default="uno", help="Target board: uno, esp32, rp2040")
parser.add_argument("--name", "-n", default="MyProject", help="Project name")
parser.add_argument("--output", "-o", help="Output directory")
parser.add_argument("--list", "-l", action="store_true", help="List project types")
parser.add_argument("--interactive", "-i", action="store_true", help="Interactive wizard")
args = parser.parse_args()
if args.list:
list_project_types()
return
if args.interactive:
interactive_mode()
return
if not args.type:
parser.print_help()
print("\nError: --type is required (or use --list / --interactive)")
sys.exit(1)
result = scaffold_project(args.name, args.type, args.board, args.output)
print(result)
if __name__ == "__main__":
main()
Project Output Template
Standardized format for delivering complete Arduino projects.
Template Structure
=== [PROJECT_NAME] for [BOARD_TYPE] ===
[BRIEF_DESCRIPTION]
## WIRING DIAGRAM
[ASCII_ART_OR_TEXT_BASED_WIRING]
Component Connections:
- [COMPONENT]: [PIN_ASSIGNMENT]
- [COMPONENT]: [PIN_ASSIGNMENT]
- [COMPONENT]: [PIN_ASSIGNMENT]
## UPLOAD INSTRUCTIONS
Board: [BOARD_TYPE]
Baud Rate: [BAUD_RATE]
Port: [AUTO_DETECT_OR_SPECIFY]
## CONFIGURATION
[CONFIGURABLE_PARAMETERS]
- Parameter: [DEFAULT_VALUE] ([DESCRIPTION])
- Parameter: [DEFAULT_VALUE] ([DESCRIPTION])
## USAGE
### Setup
1. [STEP_1]
2. [STEP_2]
3. [STEP_3]
### Operation
- [COMMAND]: [DESCRIPTION]
- [COMMAND]: [DESCRIPTION]
- [INDICATOR]: [MEANING]
### Serial Commands
- '[COMMAND]': [ACTION_DESCRIPTION]
- '[COMMAND]': [ACTION_DESCRIPTION]
## TROUBLESHOOTING
### Common Issues
- **Issue**: [DESCRIPTION]
**Solution**: [STEP_BY_STEP_FIX]
- **Issue**: [DESCRIPTION]
**Solution**: [STEP_BY_STEP_FIX]
### Error Codes
- **Code**: [NUMBER] - [DESCRIPTION] ([CAUSE])
## CODE
[COMPLETE_INO_FILE_CONTENT]Required Sections
Project Header
- Project Name: Descriptive and specific
- Board Type: UNO, ESP32, Pico (with variant if applicable)
- Brief Description: One-sentence project summary
Hardware Documentation
- Wiring Diagram: Clear, text-based representation
- Component List: All required parts with pin connections
- Power Requirements: Voltage and current specifications
Software Documentation
- Upload Instructions: Board selection and configuration
- Configuration Options: User-modifiable parameters
- Usage Guide: Setup and operation procedures
- Serial Interface: Available commands and responses
Support Documentation
- Troubleshooting Guide: Common problems and solutions
- Error Codes: Diagnostic information
- Performance Notes: Expected behavior and limitations
Code Delivery
- Complete Source: Full .ino file content
- Additional Files: config.h, platformio.ini if applicable
- Library Dependencies: Required Arduino libraries
Quality Standards
- [ ] All sections completed
- [ ] Wiring diagram accurate and clear
- [ ] Code compiles and runs on target board
- [ ] Instructions tested and verified
- [ ] Troubleshooting covers common issues
- [ ] Links to additional resources provided
Example Implementation
=== Environmental Monitor for Arduino UNO ===
Multi-sensor data logger with temperature, humidity, and light monitoring.
## WIRING DIAGRAMDHT22 → Pin 2 Photoresistor → A0 Button → Pin 3 (INPUT_PULLUP) LED → Pin 13
Component Connections:
- DHT22 Temperature/Humidity Sensor: Digital Pin 2
- Photoresistor (Light Sensor): Analog Pin A0
- Push Button: Digital Pin 3 (with internal pull-up)
- Status LED: Digital Pin 13
## UPLOAD INSTRUCTIONS
Board: Arduino UNO
Baud Rate: 9600
Port: Auto-detect
## CONFIGURATION
- LOG_INTERVAL: 60 seconds (How often to log data)
- SENSOR_TIMEOUT: 5000ms (Sensor read timeout)
- LED_BLINK_RATE: 2000ms (Status LED blink interval)
## USAGE
### Setup
1. Connect sensors according to wiring diagram
2. Upload code to Arduino UNO
3. Open Serial Monitor at 9600 baud
4. Press button to start/stop logging
### Operation
- LED blinks every 2 seconds (heartbeat)
- Serial output shows sensor readings every 5 seconds
- Data logging occurs every 60 seconds when active
### Serial Commands
- 'd': Dump all logged CSV data
- 'c': Clear logged data
- 's': Show current sensor readings
## TROUBLESHOOTING
### Common Issues
- **No sensor readings**: Check wiring connections and power
**Solution**: Verify DHT22 is connected to pin 2, photoresistor to A0
- **Compilation errors**: Ensure DHT library is installed
**Solution**: Install "DHT sensor library" from Arduino IDE
### Error Codes
- **Code: 1** - DHT sensor read failure (Check wiring and power)
- **Code: 2** - SD card initialization failed (Check card insertion)
## CODE
[Full Arduino sketch content here]Step 1: Requirements Gathering
Analyze user request to understand project scope and gather all necessary information.
Key Activities
- User Intent Analysis: Understand the project goal (what should it do?)
- Hardware Inventory: Identify required sensors, actuators, and communication modules
- Board Selection: Determine appropriate Arduino board (UNO, ESP32, or Raspberry Pi Pico)
- Constraint Assessment: Evaluate power source, memory limits, and real-time requirements
Questions to Ask
- What is the primary function of the project?
- What sensors or actuators are needed?
- What is the target board (UNO/ESP32/Pico)?
- Are there power constraints (battery vs. wall power)?
- What data output is required (Serial, SD card, WiFi)?
- Are there size or enclosure requirements?
Deliverables
- Clear project requirements document
- Hardware component list
- Board selection justification
- Constraint specifications
Step 2: Architecture Design
Design the overall system architecture including component connections, data flow, and state management.
Key Activities
- Component Diagram: Map which sensors/actuators connect to which pins
- Data Flow Design: Define how data moves through the system
- State Machine Design: Identify distinct modes/states if project has them
- Timing Requirements: Specify task intervals and execution frequencies
Architecture Elements
Hardware Architecture
- Pin assignments for all components
- Power distribution design
- Communication bus layout (I2C, SPI, UART)
Software Architecture
- Main loop structure
- Timer-based task scheduling
- Interrupt handling strategy
- Memory allocation plan
Data Architecture
- Sensor data structures
- Logging format specifications
- Communication protocols
Design Validation
- [ ] No pin conflicts
- [ ] Adequate power for all components
- [ ] Memory usage within board limits
- [ ] Timing requirements achievable
Deliverables
- System architecture diagram
- Pin assignment table
- State machine diagram (if applicable)
- Timing specifications
Step 3: Code Assembly
Assemble the complete Arduino project by combining patterns and customizing for user requirements.
Key Activities
- Pattern Integration: Pull and combine patterns from examples/ directory
- Hardware Customization: Adapt code for specific pin assignments and sensor types
- State Machine Implementation: Implement state logic if project has modes
- Data Logging Integration: Add appropriate logging mechanism (Serial, SD card, EEPROM)
Code Components
Core Files
- main.ino: Main sketch file with setup() and loop()
- config.h: Hardware abstraction and pin definitions
- platformio.ini: Build configuration (if using PlatformIO)
Pattern Integration
- Sensor Reading: Implement appropriate sensor interfaces
- Actuator Control: Add motor, relay, or LED control logic
- Communication: Integrate I2C, SPI, or WiFi as needed
- Data Processing: Add filtering, validation, and formatting
State Machine (if applicable)
- Define state enumeration
- Implement state transition logic
- Add state-specific behavior
- Include error state handling
Quality Checks
- [ ] Non-blocking code (no delay() calls)
- [ ] Proper error handling
- [ ] Memory-safe operations
- [ ] Board-specific optimizations
Deliverables
- Complete .ino file
- config.h file
- platformio.ini (if needed)
- Code documentation comments
Step 4: Testing & Validation
Verify the assembled project meets all requirements and functions correctly.
Key Activities
- Compilation Testing: Ensure code compiles for target board
- Memory Analysis: Verify usage is within board limits
- Pin Conflict Check: Confirm no duplicate pin assignments
- Timing Validation: Ensure all tasks fit within loop execution time
Validation Checks
Compilation
- [ ] Code compiles without errors for target board
- [ ] All libraries are available and compatible
- [ ] Board-specific optimizations applied correctly
Memory Usage
- [ ] SRAM usage within limits (UNO: 2KB, ESP32: 327KB, Pico: 262KB)
- [ ] Program memory (Flash) within board capacity
- [ ] No dynamic memory allocation issues
Hardware Validation
- [ ] Pin assignments match wiring diagram
- [ ] No conflicts between digital/analog pins
- [ ] Interrupt pins used appropriately
- [ ] Power requirements met
Functional Testing
- [ ] Sensor readings are valid (no NaN, within expected ranges)
- [ ] Actuators respond correctly to inputs
- [ ] State machine transitions work properly
- [ ] Data logging functions as expected
Performance Testing
- [ ] Loop execution time meets real-time requirements
- [ ] Timer intervals are accurate
- [ ] No blocking operations in main loop
Testing Tools
- Arduino IDE Serial Monitor for debugging
- PlatformIO for advanced compilation checking
- Logic analyzer for timing verification
- Multimeter for hardware validation
Deliverables
- Compilation verification report
- Memory usage analysis
- Test results summary
- Bug fixes and improvements
Step 5: Documentation
Create comprehensive documentation for the completed Arduino project.
Key Activities
- Wiring Documentation: Create clear pin connection diagrams
- Usage Instructions: Provide upload and configuration guidance
- Serial Commands: Document available commands and responses
- Troubleshooting Guide: Address common issues and solutions
Documentation Components
Hardware Documentation
- Wiring Diagram: ASCII art or text-based pin connections
- Component List: All required parts with specifications
- Power Requirements: Voltage and current specifications
- Enclosure Notes: Size and mounting considerations
Software Documentation
- Upload Instructions: Board selection and upload procedure
- Configuration: How to modify settings and parameters
- Serial Interface: Available commands and expected output
- API Reference: Function descriptions and parameters
Usage Guide
- Setup Process: Step-by-step initialization
- Operation: Normal usage procedures
- Maintenance: Calibration and upkeep procedures
- Safety Notes: Important safety considerations
Troubleshooting
- Common Issues: Frequently encountered problems
- Error Messages: What error codes mean and how to fix
- Debugging Tips: How to diagnose issues
- Recovery Procedures: How to reset or recover from errors
Output Format
Standardize documentation in this format:
=== [Project Name] for [Board Type] ===
WIRING:
[Component] → [Pin]
[Component] → [Pin]
...
UPLOAD:
Board: [Board Type]
Baud Rate: [Rate]
USAGE:
- [Instruction]
- [Instruction]
...
TROUBLESHOOTING:
- [Issue]: [Solution]
- [Issue]: [Solution]
...
CODE:
[Full .ino file content]Deliverables
- Complete project documentation
- Wiring diagrams
- Usage instructions
- Troubleshooting guide
- README file for the project
Related skills
How it compares
Use arduino-project-builder instead of generic C++ helpers when the deliverable is a full PlatformIO repo with wiring docs, not a single `.ino` snippet.
FAQ
Which boards does arduino-project-builder support?
arduino-project-builder optimizes for Arduino UNO, ESP32, and Raspberry Pi Pico. Board-specific constraints and memory guidance live in `rules/board-considerations.md`, and the scaffold CLI accepts `--board uno`, `--board esp32`, or Pico targets.
What files does scaffold_project.py generate?
arduino-project-builder’s `scaffold_project.py` emits a complete starter tree: `config.h`, `main.ino`, `platformio.ini`, and a project README. Run it with `--type`, `--board`, and `--name` or use `--interactive` to pick a template.
What project types can arduino-project-builder create?
arduino-project-builder covers environmental monitors, robot controllers, IoT devices, home automation, and data acquisition systems. Each type has a worked example under `examples/` plus a five-phase workflow from requirements gathering through documentation.