Hardware components | ||||||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
|
This is a demo showing the custom gpt I'm developing and posting a series on r/arduino about. It can easily work with any of your projects in your standard../Arduino folder. It's multi-platform so it knows where that folder is regardless if you are running Windows, macOS, or Linux.
In this example project I show using it with an Arduino Nano that has a Microchip ATmega328 chip on it.
It talks directly to your board using the `arduino-cli` tool which is available on all platforms.
It can analyze and edit any of your existing projects all just by talking with it, give you advice about any of them, and compile and upload them all without using any IDE.
I'm also posting a series of articles on how to build this and other Customer GPT's using OpenAI.
If there is interest I will also develop the same kind of specialized Gemini Gem for Google's AI platform.
Have Fun! (also, check out the GPT's logo 😉)
ripred
edit: Yes I had to film my screen showing the two separate films of the screen and the Nano videos because I don't have video editing that allows me to create a picture in a picture video. But the GPT IS controlling the Nano I promise heh. all of the code will be available in the series as well as on my github repositories. 😄
#!/usr/bin/env python3
"""
server.py
FastAPI server for managing Arduino projects with arduino-cli.
Exposes granular endpoints for OpenAI GPT integration.
@author Trent M. Wyatt (ripred)
@date February 19, 2025
@version 1.1
"""
import os
import subprocess
import logging
import platform
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import Optional, Dict, List
from pathlib import Path
# Set up logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler("server.log"),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
app = FastAPI(
title="Arduino Project Manager",
description="API for managing Arduino projects with arduino-cli",
version="1.1.0"
)
# Determine OS and set Arduino directory dynamically
OS_TYPE = platform.system() # Detects 'Windows', 'Linux', or 'Darwin' (macOS)
if OS_TYPE == "Darwin": # macOS
ARDUINO_DIR = Path.home() / "Documents" / "Arduino"
elif OS_TYPE == "Windows": # Windows
ARDUINO_DIR = Path(os.environ["USERPROFILE"]) / "Documents" / "Arduino"
elif OS_TYPE == "Linux": # Linux
ARDUINO_DIR = Path.home() / "Arduino"
else:
raise RuntimeError(f"Unsupported operating system: {OS_TYPE}")
# Ensure the directory exists
ARDUINO_DIR.mkdir(parents=True, exist_ok=True)
logger.info(f"Arduino projects directory set to: {ARDUINO_DIR}")
# Pydantic models for request validation
class ProjectRequest(BaseModel):
project_name: str
class SketchRequest(BaseModel):
project_name: str
sketch_content: str
class UploadRequest(BaseModel):
project_name: str
port: str
# Helper function to run shell commands
def run_command(command: List[str], cwd: Optional[Path] = None) -> Dict[str, str]:
try:
result = subprocess.run(
command,
cwd=cwd,
capture_output=True,
text=True,
check=True
)
return {"status": "success", "output": result.stdout, "error": ""}
except subprocess.CalledProcessError as e:
logger.error(f"Command failed: {command}, Error: {e.stderr}")
return {"status": "error", "output": "", "error": e.stderr}
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
return {"status": "error", "output": "", "error": str(e)}
@app.post("/check_folder", summary="Check if project folder exists")
async def check_folder(request: ProjectRequest):
"""Check if the specified project folder exists."""
project_dir = ARDUINO_DIR / request.project_name
exists = project_dir.exists() and project_dir.is_dir()
logger.info(f"Checked folder {project_dir}: {'exists' if exists else 'does not exist'}")
return {"exists": exists}
@app.post("/read_files", summary="Read all files in project folder")
async def read_files(request: ProjectRequest):
"""Read all files in the specified project folder."""
project_dir = ARDUINO_DIR / request.project_name
if not project_dir.exists() or not project_dir.is_dir():
logger.error(f"Project folder not found: {project_dir}")
raise HTTPException(status_code=404, detail="Project folder not found")
files_content = {}
for file_path in project_dir.glob("*"):
try:
with open(file_path, "r") as f:
files_content[file_path.name] = f.read()
except Exception as e:
logger.error(f"Failed to read file {file_path}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to read file {file_path.name}: {str(e)}")
logger.info(f"Read files from {project_dir}: {list(files_content.keys())}")
return {"files": files_content}
@app.post("/create_project", summary="Create project folder and write sketch")
async def create_project(request: SketchRequest):
"""Create a project folder and write the sketch file if it doesn't exist."""
project_dir = ARDUINO_DIR / request.project_name
sketch_file = project_dir / f"{request.project_name}.ino"
if project_dir.exists() and sketch_file.exists():
logger.error(f"Project already exists: {project_dir}")
raise HTTPException(status_code=400, detail="Project already exists")
try:
project_dir.mkdir(parents=True, exist_ok=True)
with open(sketch_file, "w") as f:
f.write(request.sketch_content)
logger.info(f"Created project {project_dir} with sketch {sketch_file}")
return {"status": "success", "message": f"Created project {request.project_name}"}
except Exception as e:
logger.error(f"Failed to create project {project_dir}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to create project: {str(e)}")
@app.post("/update_sketch", summary="Update sketch file contents")
async def update_sketch(request: SketchRequest):
"""Update the contents of the sketch file in the project folder."""
project_dir = ARDUINO_DIR / request.project_name
sketch_file = project_dir / f"{request.project_name}.ino"
if not project_dir.exists() or not sketch_file.exists():
logger.error(f"Project or sketch not found: {sketch_file}")
raise HTTPException(status_code=404, detail="Project or sketch file not found")
try:
with open(sketch_file, "w") as f:
f.write(request.sketch_content)
logger.info(f"Updated sketch {sketch_file}")
return {"status": "success", "message": f"Updated sketch {request.project_name}.ino"}
except Exception as e:
logger.error(f"Failed to update sketch {sketch_file}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to update sketch: {str(e)}")
@app.post("/compile_project", summary="Compile project using arduino-cli")
async def compile_project(request: ProjectRequest):
"""Compile the specified project using arduino-cli."""
project_dir = ARDUINO_DIR / request.project_name
if not project_dir.exists() or not (project_dir / f"{request.project_name}.ino").exists():
logger.error(f"Project or sketch not found: {project_dir}")
raise HTTPException(status_code=404, detail="Project or sketch file not found")
command = ["arduino-cli", "compile", "--fqbn", "arduino:avr:nano:cpu=atmega328old", str(project_dir)]
result = run_command(command, cwd=ARDUINO_DIR)
logger.info(f"Compilation result for {project_dir}: {result['status']}")
if result["status"] == "error":
raise HTTPException(status_code=500, detail=f"Compilation failed: {result['error']}")
return result
@app.post("/upload_project", summary="Upload compiled project to Arduino")
async def upload_project(request: UploadRequest):
"""Upload the compiled project to the specified Arduino port."""
project_dir = ARDUINO_DIR / request.project_name
if not project_dir.exists() or not (project_dir / f"{request.project_name}.ino").exists():
logger.error(f"Project or sketch not found: {project_dir}")
raise HTTPException(status_code=404, detail="Project or sketch file not found")
command = ["arduino-cli", "upload", "-p", request.port, "--fqbn", "arduino:avr:nano:cpu=atmega328old", str(project_dir)]
result = run_command(command, cwd=ARDUINO_DIR)
logger.info(f"Upload result for {project_dir} to {request.port}: {result['status']}")
if result["status"] == "error":
raise HTTPException(status_code=500, detail=f"Upload failed: {result['error']}")
return result
Instructions for Custom GPT
snippetsArduino Project Manager GPT Instructions
This custom GPT integrates with a Python server to manage Arduino projects using arduino-cli. It provides granular control over project management, compilation, and uploading through exposed actions. Below are detailed instructions, required information, workflows, and an exhaustive list of example use cases to guide the GPT in interpreting user requests and invoking the appropriate actions.
---
### Overview
The Arduino Project Manager GPT allows users to:
- Create new Arduino projects with .ino files.
- Modify existing projects by updating .ino files.
- Compile projects using arduino-cli.
- Upload compiled binaries to Arduino boards via specified ports.
- Check project folder existence and read file contents for inspection or modification.
The server exposes six granular actions:
1. **Check Folder (/check_folder)**: Check if a project folder exists.
2. **Read Files (/read_files)**: Read all .ino files in a project folder.
3. **Create Project (/create_project)**: Create a project folder and write a new .ino file.
4. **Update Sketch (/update_sketch)**: Update the contents of an existing .ino file.
5. **Compile Project (/compile_project)**: Compile a project using arduino-cli.
6. **Upload Project (/upload_project)**: Upload a compiled binary to an Arduino board.
The GPT must interpret user requests, prompt for missing information, and sequence actions appropriately. Below are detailed guidelines and examples to ensure comprehensive coverage.
---
### Required User Information
To perform actions, the following information may be required:
- **Project Name**: The name of the Arduino project (e.g., "Blink", "TemperatureSensor").
- Must be a valid folder name (no special characters like /, \, :, etc.).
- Used for all actions to identify the project in ../Arduino/.
- **COM Port or /dev/... Path**: The serial port for uploading (e.g., "COM3" on Windows, "/dev/ttyUSB0" on Linux/macOS).
- Required for /upload_project.
- Must be a valid port name; validate by checking common formats (e.g., "COM1" to "COM99", "/dev/ttyUSB*", "/dev/cu.*").
- **Sketch Content**: The contents of the .ino file for creation or updates.
- Required for /create_project and /update_sketch.
- Must be valid Arduino C++ code; provide examples if generating content.
If any required information is missing, prompt the user for it before proceeding. Use clear, conversational prompts to ensure clarity. For example:
- If the user says "Upload the Blink sketch to my Arduino" but doesn't provide a port, respond:
"Please provide the COM port (e.g., COM3) or /dev/... path (e.g., /dev/ttyUSB0) for your Arduino."
- If the user says "Write a new sketch" but doesn't provide a project name, respond:
"Please provide a name for your project (e.g., Blink, TemperatureSensor)."
- If the user says "Modify my sketch" but doesn't specify changes, respond:
"Please describe the changes you'd like to make to your sketch, and provide the project name if not already mentioned."
---
### Action Workflows and Dependencies
Each action has specific dependencies and use cases. The GPT must sequence actions logically based on user intent. Below are the workflows and hints for each action:
#### 1. Check Folder (/check_folder)
- **Purpose**: Determine if a project folder exists in ../Arduino/.
- **Parameters**:
- project_name (required): The name of the project folder.
- **When to Use**:
- Before creating a new project to avoid overwriting existing files.
- Before reading, updating, compiling, or uploading to ensure the project exists.
- As a standalone check for user queries like "Does my Blink project exist?"
- **Dependencies**:
- None; can be used independently.
- **Hints**:
- Always check folder existence before /create_project to avoid errors.
- If the folder doesn't exist, suggest creating it with /create_project.
- If the folder exists, proceed with other actions as needed.
- **Response Handling**:
- Returns {"exists": true/false}.
- If false, prompt for creation if relevant to the user's intent.
- If true, proceed with reading, updating, compiling, or uploading.
#### 2. Read Files (/read_files)
- **Purpose**: Read all .ino files in a project folder for inspection or modification.
- **Parameters**:
- project_name (required): The name of the project folder.
- **When to Use**:
- To inspect existing code before modification (e.g., "Show me my Blink sketch").
- To provide context for updates (e.g., "Add a second LED to my Blink sketch").
- As a standalone query for file contents.
- **Dependencies**:
- Requires /check_folder to confirm existence first.
- **Hints**:
- Always check folder existence before reading files.
- Display file contents in a code block (```) for readability.
- If the folder doesn't exist, suggest creating it with /create_project.
- If files are empty or missing, inform the user and suggest updates.
- **Response Handling**:
- Returns {"files": {"filename.ino": "content", ...}}.
- Display contents in the conversation for user review.
- Use contents for subsequent updates with /update_sketch.
#### 3. Create Project (/create_project)
- **Purpose**: Create a project folder and write a new .ino file.
- **Parameters**:
- project_name (required): The name of the project folder.
- sketch_content (required): The contents of the .ino file.
- **When to Use**:
- To create a new project (e.g., "Write a Blink sketch").
- After /check_folder confirms the folder doesn't exist.
- **Dependencies**:
- Requires /check_folder to confirm non-existence.
- **Hints**:
- Always check folder existence before creating to avoid overwriting.
- Generate valid Arduino C++ code if the user doesn't provide content.
- Provide example sketches (e.g., Blink, AnalogRead) if requested.
- After creation, suggest compilation (/compile_project) or upload (/upload_project).
- **Response Handling**:
- Returns {"status": "success", "message": "..."} or error.
- If successful, confirm creation and suggest next steps.
- If failed (e.g., folder exists), inform the user and suggest reading or updating.
#### 4. Update Sketch (/update_sketch)
- **Purpose**: Update the contents of an existing .ino file.
- **Parameters**:
- project_name (required): The name of the project folder.
- sketch_content (required): The updated contents of the .ino file.
- **When to Use**:
- To modify existing code (e.g., "Add a second LED to my Blink sketch").
- After /read_files provides current contents for modification.
- **Dependencies**:
- Requires /check_folder to confirm existence.
- Requires /read_files to provide current contents for updates.
- **Hints**:
- Always check folder and file existence before updating.
- Use /read_files to display current contents before updating.
- Generate updated code based on user instructions (e.g., add pins, change delays).
- After updating, suggest compilation (/compile_project) or upload (/upload_project).
- **Response Handling**:
- Returns {"status": "success", "message": "..."} or error.
- If successful, confirm update and suggest next steps.
- If failed (e.g., file not found), inform the user and suggest creation.
#### 5. Compile Project (/compile_project)
- **Purpose**: Compile a project using arduino-cli.
- **Parameters**:
- project_name (required): The name of the project folder.
- **When to Use**:
- To verify project code before uploading (e.g., "Compile my Blink sketch").
- After creating or updating a project to ensure it compiles.
- **Dependencies**:
- Requires /check_folder to confirm existence.
- Requires /create_project or /update_sketch to ensure valid code.
- **Hints**:
- Always check folder and file existence before compiling.
- If compilation fails, display errors and suggest fixes.
- After successful compilation, suggest upload (/upload_project).
- **Response Handling**:
- Returns {"status": "success/error", "output": "...", "error": "..."}.
- If successful, confirm compilation and suggest upload.
- If failed, display error details and suggest updates.
#### 6. Upload Project (/upload_project)
- **Purpose**: Upload a compiled binary to an Arduino board.
- **Parameters**:
- project_name (required): The name of the project folder.
- port (required): The COM port or /dev/... path.
- **When to Use**:
- To deploy a project to an Arduino (e.g., "Upload my Blink sketch to COM3").
- After successful compilation (/compile_project).
- **Dependencies**:
- Requires /check_folder to confirm existence.
- Requires /compile_project to ensure successful compilation
openapi: 3.1.0
info:
title: Arduino Project Manager API
description: API for managing Arduino projects using arduino-cli, with granular control over project creation, modification, compilation, and uploading.
version: 1.0.0
servers:
- url: YOUR_NGROK_URL_HERE
description: Ngrok tunnel URL for the FastAPI server
paths:
/check_folder:
post:
operationId: checkFolder
summary: Check if project folder exists
description: Check if the specified project folder exists in ../Arduino/. Use this to verify existence before creating, reading, updating, compiling, or uploading a project.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
project_name:
type: string
description: The name of the Arduino project (e.g., "Blink"). Must be a valid folder name without special characters.
minLength: 1
maxLength: 50
required:
- project_name
responses:
"200":
description: Folder existence status
content:
application/json:
schema:
type: object
properties:
exists:
type: boolean
description: Whether the project folder exists.
"403":
description: Invalid or missing API key
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"500":
description: Server error
content:
application/json:
schema:
type: object
properties:
detail:
type: string
/read_files:
post:
operationId: readFiles
summary: Read all files in project folder
description: Read all .ino files in the specified project folder and return their contents. Use this to inspect or modify existing code. Requires folder existence check first.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
project_name:
type: string
description: The name of the Arduino project (e.g., "Blink"). Must be a valid folder name without special characters.
minLength: 1
maxLength: 50
required:
- project_name
responses:
"200":
description: File contents
content:
application/json:
schema:
type: object
properties:
files:
type: object
additionalProperties:
type: string
description: Dictionary of file names and their contents.
"403":
description: Invalid or missing API key
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"404":
description: Project folder not found
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"500":
description: Server error
content:
application/json:
schema:
type: object
properties:
detail:
type: string
/create_project:
post:
operationId: createProject
summary: Create project folder and write sketch
description: Create a project folder and write a new .ino file if it doesn't exist. Use this to start a new project. Requires folder existence check first to avoid overwriting.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
project_name:
type: string
description: The name of the Arduino project (e.g., "Blink"). Must be a valid folder name without special characters.
minLength: 1
maxLength: 50
sketch_content:
type: string
description: The contents of the .ino file. Must be valid Arduino C++ code.
required:
- project_name
- sketch_content
responses:
"200":
description: Project creation status
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: Status of the operation (e.g., "success").
message:
type: string
description: Detailed message.
"400":
description: Project already exists
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"403":
description: Invalid or missing API key
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"500":
description: Server error
content:
application/json:
schema:
type: object
properties:
detail:
type: string
/update_sketch:
post:
operationId: updateSketch
summary: Update sketch file contents
description: Update the contents of an existing .ino file in the project folder. Use this to modify existing code. Requires folder and file existence check first.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
project_name:
type: string
description: The name of the Arduino project (e.g., "Blink"). Must be a valid folder name without special characters.
minLength: 1
maxLength: 50
sketch_content:
type: string
description: The updated contents of the .ino file. Must be valid Arduino C++ code.
required:
- project_name
- sketch_content
responses:
"200":
description: Sketch update status
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: Status of the operation (e.g., "success").
message:
type: string
description: Detailed message.
"403":
description: Invalid or missing API key
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"404":
description: Project or sketch file not found
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"500":
description: Server error
content:
application/json:
schema:
type: object
properties:
detail:
type: string
/compile_project:
post:
operationId: compileProject
summary: Compile project using arduino-cli
description: Compile the specified project using arduino-cli. Use this to verify project code before uploading. Requires folder and file existence check first.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
project_name:
type: string
description: The name of the Arduino project (e.g., "Blink"). Must be a valid folder name without special characters.
minLength: 1
maxLength: 50
required:
- project_name
responses:
"200":
description: Compilation result
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: Status of the operation (e.g., "success", "error").
output:
type: string
description: Compilation output.
error:
type: string
description: Error message if compilation failed.
"403":
description: Invalid or missing API key
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"404":
description: Project or sketch file not found
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"500":
description: Compilation failed or server error
content:
application/json:
schema:
type: object
properties:
detail:
type: string
/upload_project:
post:
operationId: uploadProject
summary: Upload compiled project to Arduino
description: Upload the compiled project to the specified Arduino port using arduino-cli. Use this to deploy a project to an Arduino board. Requires folder, file existence, and successful compilation first.
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
project_name:
type: string
description: The name of the Arduino project (e.g., "Blink"). Must be a valid folder name without special characters.
minLength: 1
maxLength: 50
port:
type: string
description: The COM port (e.g., "COM3") or /dev/... path (e.g., "/dev/ttyUSB0") for the Arduino board. Must be a valid port name.
required:
- project_name
- port
responses:
"200":
description: Upload result
content:
application/json:
schema:
type: object
properties:
status:
type: string
description: Status of the operation (e.g., "success", "error").
output:
type: string
description: Upload output.
error:
type: string
description: Error message if upload failed.
"403":
description: Invalid or missing API key
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"404":
description: Project or sketch file not found
content:
application/json:
schema:
type: object
properties:
detail:
type: string
"500":
description: Upload failed or server error
content:
application/json:
schema:
type: object
properties:
detail:
type: string
Comments
Please log in or sign up to comment.