Programming an Arduino with CrewAI Agents
You can find the code for this tutorial in this GitHub repository.
While I don't consider myself an expert in Arduino programming , I really enjoy building electronic projects in my spare time. So the other day an idea popped into my head: I know a few things about AI and a few things about Arduino, so how about making them work together?
Having experimented with CrewAI [1] over the past few weeks, I came up with the following experiment: connecting CrewAI with Arduino. Additionally, I thought it would be interesting to use some local LLMs, such as those offered by Ollama [2].
But, before we get started with the code, you might not be familiar with Arduino. So, let's start with the basics.
What's an Arduino?

An Arduino [3] is a small programmable computer that allows you to build your own electronic projects, ranging from basic circuits that blink LEDs to advanced robots capable of moving.
In simple terms, when using the Arduino platform, you need to understand that there are two main "parts" to work with.
1. The Board

The Arduino board is the physical hardware that holds the microcontroller chip, which is the main component. This chip runs the instructions you provide through your code.
2. Programming

You write code, known as sketches, using the Arduino programming language (based on C/C++). These sketches instruct the Arduino on what actions to take, like turning on a light, reading sensor data, or controlling a servomotor.
For example, this is the sketch for turning on a LED for one second, then off for one second, repeatedly.
void setup() {
pinMode(11, OUTPUT);
}
void loop() {
digitalWrite(11, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(11, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
I won't get into the details of sketch programming, as it is beyond the scope of this tutorial, but, if you are interested in this fascinating world, I recommend you to go through the Arduino tutorials [4].
Building a simple circuit

To explore the connection between LLMs and Arduino, I selected a simple project: making three LEDs blink in a loop. You can see the circuit setup in the image above.
Now that the circuit is set up, the next logical step would be to manually write a sketch to control the LEDs. However, that's exactly what we want to automate using LLMs!
So, instead of writing the code ourselves, we'll have a CrewAI agent handle the programming. Likewise, instead of manually compiling and uploading the code to the Arduino, another CrewAI agent will take care of that process for us.
Implementing the Crew of Agents

Let's focus on the diagram above, which outlines the CrewAI application we're about to build.
As you can see, the setup is simple, consisting of two agents [5]: the Sketch Programmer Agent and the Arduino Uploader Agent. The first agent will receive a description of the circuit and its expected behavior, then generate a sketch file – which has a .ino
extension. The second agent will compile the generated sketch and upload the code to the Arduino.
Let's examine each agent in detail.
Sketch Programmer Agent
Earlier in this article, I mentioned that we'd be using local LLMs. For this purpose, we'll be working with Ollama.
If you you're not familiar with Ollama, Matthew Berman has a very good video about it [6].
To integrate Ollama with CrewAI, simply add the following lines before defining the agents.
from langchain_openai import ChatOpenAI
llama3 = ChatOpenAI(
model="llama3",
base_url="http://localhost:11434/v1"
)
Note that I'm using the Llama 3 [7] model. If you want to do the same, make sure it's downloaded in Ollama. If it's not, you can download it by running this simple command.
ollama pull llama3
Now, it's time to show you the agent implementation.
sketch_programmer_agent = Agent(
role="Sketch Programmer",
goal=dedent(
"""Write a Sketch script for Arduino that
lights a red led in digital pin 11,
a blue led in digital pin 10 and a green
led in digital pin 9 with a time
between the three of 1 second."""),
backstory=dedent(
"""
You are an experienced Sketch programmer
who really enjoys programming Arduinos
"""
),
verbose=True,
allow_delegation=False,
llm=llama3,
)
The agent's goal includes information about the circuit we've implemented, as well as its intended behavior. Additionally, you'll notice that the agent is using llama3
as the LLM.
The task [8] assigned to this agent is the following.
sketch_programming_task = Task(
description=dedent(
"Write a Sketch script that can be immediately
uploaded to an Arduino. Just the Arduino code,
nothing else."),
expected_output=dedent(
"A plain Sketch script that can be copy and pasted
directly into the Arduino CLI"),
agent=sketch_programmer_agent,
output_file="./tmp/tmp.ino",
)
Pay attention to the output_file
attribute. This is crucial, as the Sketch Programmer Agent will save the generated code to the ./tmp/tmp.ino
file.
Arduino Uploader Agent
Now that the previous agent has generated the sketch file, the Arduino Uploader Agent needs to compile it and upload it to the Arduino. How can we achieve this?
The solution is to use a custom tool.
You can learn how to implement your own custom tools following this CrewAI documentation [9]
import re
import subprocess
from crewai_tools import BaseTool
class CompileAndUploadToArduinoTool(BaseTool):
name: str = "CompileAndUploadToArduinoTool"
description: str = """Compiles and Uploads an Arduino Sketch script
to an Arduino"""
ino_file_dir: str = "The directory that contains the ino file"
board_fqbn: str = "The board type, e.g. 'arduino:avr:uno'"
port: str = "The port where the Arduino is connected"
def __init__(self, ino_file_dir: str, board_fqbn: str, port: str, **kwargs):
super().__init__(**kwargs)
self.ino_file_dir = ino_file_dir
self.board_fqbn = board_fqbn
self.port = port
def _fix_ino_file(self):
"""
This is a helper method for fixing the output .ino file
when Llama3 adds some unintended text that invalidates
the compilation.
"""
with open(f"{self.ino_file_dir}/tmp.ino", "r") as f:
content = f.read()
pattern = r'```.*?n(.*?)```'
match = re.search(pattern, content, re.DOTALL).group(1).strip()
with open(f"{self.ino_file_dir}/tmp.ino", "w") as f:
f.write(match)
def _run(self):
self._fix_ino_file()
try:
subprocess.check_call([
"arduino-cli", "compile", "--fqbn",
self.board_fqbn, self.ino_file_dir
])
subprocess.check_call([
"arduino-cli", "upload", "--port",
self.port, "--fqbn", self.board_fqbn, self.ino_file_dir
])
except subprocess.CalledProcessError:
return "Compilation failed"
return "Code successfully uploaded to the board"
This tool expects three arguments:
ino_file_dir
: The directory containing the sketch. Remember we were using the tmp dir.board_fqbn
: The board type. I'm using an Arduino UNO, so my board type is arduino:avr:uno, but it may be different in your case.port
: The port where the Arduino is connected. Mine is connected to port /dev/cu.usbmodem1201.
When the agent uses the tool (by accessing the _run
method), it will compile the code and then upload it to the Arduino. You can check the agent implementation below – in this case I've used GPT-4 since it works much better when using tools:
tool = CompileAndUploadToArduinoTool(
ino_file_dir="./tmp",
board_fqbn="arduino:avr:uno",
port="/dev/cu.usbmodem1201"
)
arduino_uploader_agent = Agent(
role="Arduino Uploader Agent",
goal="""Your goal is to compile and upload the
received arduino script using a tool""",
backstory=dedent(
"""
You are a hardware geek.
"""
),
verbose=True,
allow_delegation=False,
tools=[tool]
)
And this is the task assigned to the agent.
arduino_uploading_task = Task(
description=dedent(
"Compile and Upload the the Sketch script into the Arduino"),
expected_output=dedent(
"""Just compile the code and upload it into the Arduino"""),
agent=arduino_uploader_agent,
)
Results
It took some time, but we've now assembled all the components needed to build our CrewAI application. It's time to build the Crew.
from crewai import Crew
from dotenv import load_dotenv
from agents import sketch_programmer_agent, arduino_uploader_agent
from tasks import sketch_programming_task, arduino_uploading_task
load_dotenv()
crew = Crew(
agents=[sketch_programmer_agent, arduino_uploader_agent],
tasks=[sketch_programming_task, arduino_uploading_task],
)
result = crew.kickoff()
print(result)
If you'd like to see the agents in action, you can check out my experiment in the following video. It also features a step-by-step explanation of everything covered in this article so, if you found it helpful, give it a try!
References
[1] CrewAI Home Page
[2] Ollama Home Page
[3] About Arduino, Arduino Documentation
[4] Arduino Tutorials, Arduino Documentation
[5] Core Concepts: Agents, CrewAI Documentation
[6] Using Ollama To Build a FULLY LOCAL "ChatGPT Clone", Matthew Berman
[7] Introducing Meta Llama 3: The most capable openly available LLM to date, Meta Blog
[8] Core Concepts: Tasks, CrewAI Documentation
[9] Create Custom Tools, CrewAI Documentation
Enjoyed the article? If you want to stay in touch and read similar content, you can find me here