Hackster is hosting Impact Spotlights: Smart Home. Watch the stream live on Thursday!Hackster is hosting Impact Spotlights: Smart Home. Stream on Thursday!
Ripred
Published © MIT

Demo of my New Arduino Project Manager GPT

This is a demo showing the custom gpt I'm developing and posting a series on r/arduino about.

IntermediateFull instructions provided4 hours104
Demo of my New Arduino Project Manager GPT

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1

Software apps and online services

OpenAI Custom GPT Editor

Story

Read more

Code

server.py

Python
The action service that carries everything out locally
#!/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

snippets
The instructions for the custom GPT telling it how to behave and make use of the services in the python file
Arduino 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

The YAML defining the Services

YAML
give to the Custom GPT for it's configuration
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

Credits

Ripred
9 projects • 5 followers
30++ yrs development exp. Kernel, Compiler, ML, Sentiment Analysis, Robotics, Embedded, Game Theory, Networking, and Intelligence work.
Contact

Comments

Please log in or sign up to comment.