Can an AI assistant write working firmware for your next Arduino project? That’s the question I asked myself after seeing the new Arduino AI Assistant launch inside Arduino Cloud. I’ve used ChatGPT successfully in dozens of projects—from BLE to Wi-Fi automation—but how would Arduino’s own AI stack up?
To find out, I gave both tools the exact same prompt. Not a toy problem. Not a generic sketch. A real challenge from one of my production-ready builds: a BLE-triggered smart gate using an ESP32-C6 and a wireless beacon.
This article is a hands-on comparison between two AI assistants solving the same embedded problem, and the results may surprise you.
Table of Contents
The Project: BLE Beacon Smart Gate
To make this comparison meaningful, I didn’t want to use a generic “blink an LED” example. Instead, I chose a real-world project I had already built and tested: a smart gate controller powered by an ESP32-C6 and a BLE beacon.
A BLE beacon (a small round tag powered by a coin cell) is placed inside a car.
You can find low-power BLE beacons like this one on Amazon for less than $10.
The concept is simple, but the implementation requires multiple layers of logic. A BLE beacon (a small round tag powered by a coin cell) is placed inside a car. As the car approaches the gate, the ESP32 scans nearby BLE devices. When it detects the specific MAC address of the beacon with a strong RSSI signal, it activates a relay wired to GPIO 5 for exactly 2 seconds, triggering the gate to open.
To avoid false triggers and multiple activations in a short time, the firmware also enforces a cooldown window of 10 seconds. This means the system must:
- Continuously scan BLE devices and filter by MAC address
- Check RSSI to determine signal strength (proximity)
- Control a digital output pin for a timed relay pulse
- Track time between triggers to prevent spamming
This kind of project is common in DIY access control, parking systems, and smart automation in gated communities. But it’s also a great test for AI-generated firmware because it touches on:
- BLE scanning and filtering
- Non-blocking timing (millis-based cooldown)
- GPIO pin control
- Event-driven logic
I already had a working version of this system, which you can read about in my detailed tutorial: ESP32 Smart Gate Access. That gave me a perfect baseline for evaluating the quality of AI-generated firmware: could an assistant write something close to what I already knew worked?
To find out, I wrote a single natural-language prompt and sent it to both AI assistants.
Setting Up the Arduino AI Assistant
One important detail many users miss: the Arduino AI Assistant only works in the Arduino Cloud Web Editor. You won’t find it in the classic Arduino IDE or the new desktop IDE v2.x — at least not for now.
To access the assistant, you need to create an Arduino account and use their online IDE. It runs entirely in your browser and integrates a chat-style AI panel that generates code based on natural language prompts.
Here’s how to set it up:

- Go to cloud.arduino.cc and create a free account.
- Open the Web Editor once logged in.
- Look in the bottom-right corner of the editor interface — that’s where you’ll find the AI Assistant panel.
- Accept the terms of use for AI-based code generation.

Once activated, the assistant works like a simplified ChatGPT: you type what you want to build, and it replies with code suggestions, brief explanations, and sometimes even next steps. You can paste the code directly into the editor and upload it to your board if it’s compatible.
During my test, I used Google Chrome on desktop, and everything loaded without issues. The AI was available on the free plan — no payment required.
I also captured a few screenshots of the login and onboarding flow, which I’ll include below to help first-time users get started.
The Prompt I Used
To compare both AI assistants fairly, I gave them the exact same prompt, written in plain English with no code or technical hints. The goal was to simulate how a real Arduino user might describe a problem — not how a firmware engineer might phrase it.

Here’s the prompt I used for both Arduino AI Assistant and ChatGPT:
Write firmware for an ESP32-C6 that uses BLE to scan for a beacon. When a specific MAC address is seen with high RSSI (strong signal), activate a relay on GPIO 5 for 2 seconds. Add cooldown logic to prevent re-triggers for 10 seconds.

This isn’t a toy problem. It involves:
- BLE scanning and device filtering by MAC address
- RSSI signal strength logic (for proximity detection)
- Digital pin control using GPIO 5
- Cooldown tracking using timers or millis()
- Non-blocking logic that works in a loop
- Compatibility with the ESP32-C6 board variant
In short, this is the kind of real-world logic many smart gate or home automation projects require — and it’s the perfect challenge for testing whether AI assistants actually understand Arduino-level development.
What the Arduino AI Assistant Gave Me
Once I submitted my prompt to the Arduino AI Assistant inside the Cloud Editor, it responded within a few seconds. The code it generated was surprisingly complete — not just a snippet or skeleton, but a full sketch that could, in theory, be uploaded and tested right away.
Here’s what I asked:
Write firmware for an ESP32-C6 that uses BLE to scan for a beacon. When a specific MAC address is seen with high RSSI (strong signal), activate a relay on GPIO 5 for 2 seconds. Add cooldown logic to prevent retriggers for 10 seconds.
And here’s a summary of what the Arduino AI generated:
#include <ArduinoBLE.h>
const char* TARGET_MAC = "AA:BB:CC:DD:EE:FF";
const int RSSI_THRESHOLD = -65;
const int RELAY_PIN = 5;
const unsigned long RELAY_DURATION = 2000;
const unsigned long COOLDOWN_TIME = 10000;
unsigned long lastTriggerTime = 0;
bool relayActive = false;
unsigned long relayStartTime = 0;
void setup() {
Serial.begin(9600);
while (!Serial && millis() < 5000);
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
if (!BLE.begin()) {
Serial.println("BLE initialization failed!");
while (1);
}
Serial.println("BLE initialized, scanning for beacons...");
BLE.scan(true);
}
void loop() {
if (relayActive && (millis() - relayStartTime >= RELAY_DURATION)) {
digitalWrite(RELAY_PIN, LOW);
relayActive = false;
Serial.println("Relay deactivated");
}
BLEDevice device = BLE.available();
if (device) {
String address = device.address();
int rssi = device.rssi();
if (address.equals(TARGET_MAC) && rssi >= RSSI_THRESHOLD) {
Serial.print("Target detected! RSSI: ");
Serial.println(rssi);
if (!relayActive && (millis() - lastTriggerTime >= COOLDOWN_TIME)) {
digitalWrite(RELAY_PIN, HIGH);
relayActive = true;
relayStartTime = millis();
lastTriggerTime = millis();
Serial.println("Relay activated");
}
}
}
}
Now let’s break down what this response got right — and where it stumbled.
BLE Library and Compatibility
The AI chose to use the ArduinoBLE library, which is typically intended for official Arduino boards like the Nano 33 BLE, not ESP32. This is the first major issue: while the logic looks good, ArduinoBLE is not compatible with ESP32-C6.
If you tried compiling this code for an ESP32 board, you’d hit errors immediately. A correct choice here would have been NimBLEDevice
, which is optimized for ESP32 and well supported by Espressif.
BLE Filtering Logic
The assistant does filter based on MAC address and RSSI, which is exactly what I asked. It checks device.address()
and compares against a hardcoded string, and it uses an RSSI threshold (-65 dBm
) to determine proximity.
This logic is solid and demonstrates a good understanding of how BLE-based proximity detection works in practice.
GPIO and Relay Logic
Relay control is handled using digitalWrite(RELAY_PIN, HIGH)
to activate and LOW
to deactivate — standard Arduino style. It uses a non-blocking timer based on millis()
to keep the relay on for exactly two seconds. It also tracks lastTriggerTime
to enforce a 10-second cooldown between triggers.
This part is correctly implemented and would be usable — if the board and BLE library were compatible.
Cooldown Behavior
Cooldown logic is implemented with a timestamp check (millis() - lastTriggerTime >= COOLDOWN_TIME
), which works and keeps the sketch from reactivating the relay too soon.
Serial Output and Debugging
The assistant also included Serial.print()
statements for feedback. This is helpful when testing proximity thresholds or debugging BLE visibility. It even includes a small delay before serial starts, which is a nice touch.
Final Verdict
From a pure logic standpoint, this is excellent Arduino-style code. The AI correctly mapped out the BLE scan logic, MAC filtering, proximity checks, cooldown, and GPIO control using millis()
— all without blocking or oversimplifying.
However, the BLE library choice makes the sketch unusable on ESP32-C6, which was part of the original prompt. This is a major limitation. If you’re using an Arduino Nano 33 BLE or similar board, this sketch might work fine. But on an ESP32, it won’t compile.
If Arduino’s AI wants to compete with tools like ChatGPT, it needs to better detect board types and choose appropriate libraries — especially when the prompt clearly specifies a platform like ESP32-C6.
What ChatGPT Gave Me
For this test, I used ChatGPT in an incognito Chrome session, without logging in or enabling chat history. This ensured the results were generated without personalization or prior influence. The language defaulted to Romanian, which I manually switched to English by visiting chat.openai.com/?hl=en.

I entered the same prompt used for the Arduino AI Assistant:
Write firmware for an ESP32-C6 that uses BLE to scan for a beacon. When a specific MAC address is seen with high RSSI (strong signal), activate a relay on GPIO 5 for 2 seconds. Add cooldown logic to prevent re-triggers for 10 seconds.
At no point in this prompt did I mention the Arduino platform. That detail turns out to be significant. While the Arduino AI Assistant is explicitly built to output Arduino-style sketches, ChatGPT is platform-agnostic and chose to respond based on what it deemed most appropriate for the ESP32-C6 platform.
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "driver/gpio.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "esp_bt.h"
#include "esp_gap_ble_api.h"
#include "esp_bt_main.h"
#define TAG "BLE_RELAY"
#define RELAY_GPIO 5
#define RSSI_THRESHOLD -60
#define RELAY_ON_TIME_MS 2000
#define COOLDOWN_TIME_MS 10000
static const uint8_t target_mac[6] = {0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56}; // <-- Replace with your beacon MAC
static bool in_cooldown = false;
// Forward declarations
void activate_relay();
void start_cooldown();
// GAP callback
void gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
if (event == ESP_GAP_BLE_SCAN_RESULT_EVT) {
esp_ble_gap_cb_param_t *scan_result = param;
if (scan_result->scan_rst.search_evt == ESP_GAP_SEARCH_INQ_RES_EVT) {
int rssi = scan_result->scan_rst.rssi;
uint8_t *mac = scan_result->scan_rst.bda;
if (memcmp(mac, target_mac, 6) == 0) {
ESP_LOGI(TAG, "Target device found. RSSI: %d", rssi);
if (rssi > RSSI_THRESHOLD && !in_cooldown) {
ESP_LOGI(TAG, "RSSI is strong. Activating relay.");
activate_relay();
start_cooldown();
}
}
}
}
}
// Activate relay for RELAY_ON_TIME_MS
void activate_relay() {
gpio_set_level(RELAY_GPIO, 1);
vTaskDelay(pdMS_TO_TICKS(RELAY_ON_TIME_MS));
gpio_set_level(RELAY_GPIO, 0);
}
// Start cooldown timer
void start_cooldown() {
in_cooldown = true;
xTaskCreate([](void *arg) {
vTaskDelay(pdMS_TO_TICKS(COOLDOWN_TIME_MS));
in_cooldown = false;
ESP_LOGI(TAG, "Cooldown finished.");
vTaskDelete(NULL);
}, "cooldown_task", 2048, NULL, 5, NULL);
}
void app_main(void) {
// Initialize relay GPIO
gpio_config_t io_conf = {
.pin_bit_mask = 1ULL << RELAY_GPIO,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = 0,
.pull_down_en = 0,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
gpio_set_level(RELAY_GPIO, 0);
// Initialize BT controller
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
esp_bt_controller_init(&bt_cfg);
esp_bt_controller_enable(ESP_BT_MODE_BLE);
esp_bluedroid_init();
esp_bluedroid_enable();
// Register GAP callback
esp_ble_gap_register_callback(gap_cb);
// Start scanning
esp_ble_gap_set_scan_params(& (esp_ble_scan_params_t) {
.scan_type = BLE_SCAN_TYPE_PASSIVE,
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL,
.scan_interval = 0x50,
.scan_window = 0x30,
.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE
});
ESP_LOGI(TAG, "BLE scanning started...");
}
As a result, ChatGPT returned a complete C project using the ESP-IDF SDK, which is the official development framework for ESP32 chips. It provided a main.c
file, BLE scan handling through the GAP callback system, native GPIO control, and FreeRTOS-based cooldown logic. There were also instructions for flashing the firmware using the idf.py
CLI tool.
While technically impressive, this response assumes the developer is working within the ESP-IDF ecosystem — not the Arduino IDE. For experienced embedded developers, this is a strong answer. But for someone expecting Arduino-style code, the result might feel more complex or unfamiliar.
This observation is important: ChatGPT defaulted to ESP-IDF because the prompt lacked Arduino context, whereas the Arduino AI Assistant can only respond with Arduino C++ by design.
To keep the comparison fair, we’ll now try a second variation of the same prompt — this time with a minor adjustment to add Arduino context. Here’s the updated version:
Write Arduino code for an ESP32-C6 that uses BLE to scan for a beacon. When a specific MAC address is seen with high RSSI (strong signal), activate a relay on GPIO 5 for 2 seconds. Add cooldown logic to prevent re-triggers for 10 seconds.
This slight change will help test how ChatGPT adapts its response when Arduino is explicitly mentioned, and whether it switches to using Arduino libraries such as NimBLEDevice
, BLEDevice
, or similar.

We’ll feed this updated prompt into ChatGPT and examine the result in the next section.
What ChatGPT Gave Me (with Arduino Context)
After noticing that ChatGPT initially responded with a full ESP-IDF project — not an Arduino sketch — I decided to test how it would behave with a slightly adjusted prompt. This time, I simply added the word “Arduino”:
Write Arduino code for an ESP32-C6 that uses BLE to scan for a beacon. When a specific MAC address is seen with high RSSI (strong signal), activate a relay on GPIO 5 for 2 seconds. Add cooldown logic to prevent retriggers for 10 seconds.
The difference was immediate. ChatGPT switched from using low-level ESP-IDF functions to producing an Arduino-compatible sketch, using the NimBLEDevice
library — which is better suited for ESP32 boards, especially when working with BLE in a memory-efficient way.
#include <NimBLEDevice.h>
#define RELAY_PIN 5
#define RSSI_THRESHOLD -60 // Trigger if RSSI is greater than this
#define RELAY_ON_TIME_MS 2000 // 2 seconds
#define COOLDOWN_TIME_MS 10000 // 10 seconds
// Replace with the actual MAC address of the BLE beacon
uint8_t TARGET_MAC[6] = { 0xAB, 0xCD, 0xEF, 0x12, 0x34, 0x56 };
bool inCooldown = false;
unsigned long lastTriggerTime = 0;
class MyAdvertisedDeviceCallbacks : public NimBLEAdvertisedDeviceCallbacks {
void onResult(NimBLEAdvertisedDevice* advertisedDevice) override {
if (memcmp(advertisedDevice->getAddress().getNative(), TARGET_MAC, 6) == 0) {
int rssi = advertisedDevice->getRSSI();
Serial.printf("Target found! RSSI: %d\n", rssi);
if (rssi > RSSI_THRESHOLD && !inCooldown) {
triggerRelay();
}
}
}
};
void triggerRelay() {
Serial.println("Triggering relay!");
digitalWrite(RELAY_PIN, HIGH);
delay(RELAY_ON_TIME_MS);
digitalWrite(RELAY_PIN, LOW);
inCooldown = true;
lastTriggerTime = millis();
}
void setup() {
Serial.begin(115200);
// Initialize relay pin
pinMode(RELAY_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
// Initialize BLE
NimBLEDevice::init("");
NimBLEScan* pScan = NimBLEDevice::getScan();
pScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks(), false);
pScan->setActiveScan(false); // Passive scan
pScan->setInterval(45);
pScan->setWindow(15);
pScan->start(0, nullptr); // 0 = continuous scanning
Serial.println("BLE scanning started...");
}
void loop() {
// Handle cooldown
if (inCooldown && (millis() - lastTriggerTime >= COOLDOWN_TIME_MS)) {
inCooldown = false;
Serial.println("Cooldown finished. Ready to trigger again.");
}
delay(100); // Keep loop light
}
Here’s a summary of what the updated response included:
- Relay logic using
digitalWrite()
anddelay()
- BLE scanning using
NimBLEScan
andMyAdvertisedDeviceCallbacks
- MAC filtering using
memcmp()
on raw BLE addresses - RSSI threshold check to evaluate signal strength
- A cooldown mechanism handled in the
loop()
withmillis()
tracking
The full sketch used typical Arduino structure: a setup()
function to initialize BLE and GPIO, and a loop()
function to handle cooldown and timing.
Key Differences from the First Response
What changed between the two ChatGPT outputs came down to one keyword — “Arduino”. In the first response, ChatGPT assumed I wanted the official Espressif development environment (ESP-IDF). In the second, it correctly mapped the request to the Arduino programming model, returning code that can be run in the Arduino IDE or Arduino Cloud.
This version would be much more approachable for most makers using ESP32-C6 boards in the Arduino environment.
BLE Logic with NimBLE
The code uses NimBLEDevice::getScan()
with passive scanning enabled, and a custom class extending NimBLEAdvertisedDeviceCallbacks
. When a device is detected, the code compares the beacon’s MAC address with a target using memcmp()
, and then checks the RSSI value.
Once the conditions are met, it calls triggerRelay()
to pulse GPIO 5 high for two seconds using delay()
, and then returns LOW. Cooldown is implemented using a global timestamp (lastTriggerTime
) and a simple if
check in loop()
to reset the inCooldown
flag after 10 seconds.
Considerations
While this version is much closer to what most Arduino developers would expect, there are still a few details worth noting:
- The
delay()
intriggerRelay()
is blocking — meaning the board can’t do anything else during the 2-second pulse. In time-sensitive applications, this could be replaced with a non-blocking timer (e.g.millis()
orTicker
). - MAC address comparison is handled manually using raw bytes. While correct, this might be confusing for beginners who expect string-style comparisons like
address.equals(...)
. - Serial debug messages are included, which is helpful for testing proximity thresholds and cooldown timing.
Summary
By simply including the word “Arduino” in the prompt, ChatGPT produced a result that aligned with the expected development environment — and that would actually run on an ESP32-C6 using the Arduino framework. It used appropriate libraries (NimBLEDevice
), handled relay and cooldown logic as requested, and returned a full, uploadable sketch.
This test highlights an important lesson: general-purpose AI tools like ChatGPT adapt their output based on context, so how you phrase your request directly affects the platform, language, and code structure it chooses.
In the next section, we’ll compare both assistants side by side.
Side-by-Side Comparison: Arduino AI vs ChatGPT
After testing both assistants with the same prompt — and a slight variation that added Arduino context — it’s clear that each tool interprets and responds to requests based on its design and scope.
Below is a comparison table based on the key technical requirements from the original BLE-based smart gate project:
Feature / Criteria | Arduino AI Assistant | ChatGPT (no Arduino) | ChatGPT (with Arduino) |
---|---|---|---|
Platform Assumption | Arduino IDE / Arduino-style code | ESP-IDF C with FreeRTOS | Arduino IDE / Arduino-compatible |
BLE Library Used | ArduinoBLE | Native ESP-IDF BLE (esp_ble_gap_cb_event_t ) | NimBLEDevice |
ESP32-C6 Compatibility | No (ArduinoBLE not supported on ESP32-C6) | Yes | Yes |
MAC Address Filtering | address.equals() (string) | memcmp() (raw MAC) | memcmp() (raw MAC) |
RSSI Threshold Logic | Yes | Yes | Yes |
Relay Control | digitalWrite() with millis() for timing | gpio_set_level() with vTaskDelay() | digitalWrite() with delay() |
Cooldown Implementation | millis() based, in loop() | Separate FreeRTOS task | millis() based, in loop() |
Blocking Behavior | Non-blocking (relay timing handled with millis() ) | Blocking (relay timing handled inside delay) | Blocking (relay uses delay() ) |
Debug Output | Serial.println() | ESP_LOGI() | Serial.println() |
Project Setup / Build Guidance | No | Yes (idf.py , folder structure) | No |
Ease of Use (Arduino-style) | High | Low (requires ESP-IDF experience) | High |
Interpretation
- Arduino AI Assistant provided beginner-friendly Arduino code, but used a BLE library that’s incompatible with ESP32-C6, making the sketch unworkable without modification.
- ChatGPT (initial prompt) responded with low-level ESP-IDF code, assuming professional-level development. Technically correct and efficient, but not aligned with Arduino workflows.
- ChatGPT (Arduino prompt) adapted to the platform expectations and generated a valid Arduino sketch using
NimBLEDevice
, making it compatible with ESP32-C6 and the Arduino IDE.
Each AI has its own strengths depending on what the user wants:
- Arduino AI excels in generating familiar Arduino-style sketches.
- ChatGPT adjusts based on how your prompt is framed — offering either high-level or low-level output.
In the next and final section, we’ll look at which assistant might be right for different use cases.
Conclusion: Which AI Should You Use for Arduino Projects?
Both Arduino AI Assistant and ChatGPT are capable of generating working code for real-world Arduino projects — but their behavior, strengths, and limitations vary depending on the environment, the prompt, and the developer’s expectations.
Arduino AI Assistant is tightly integrated with the Arduino Cloud Editor and is designed specifically for generating Arduino-compatible sketches. It provides beginner-friendly code that fits well into the Arduino workflow. However, in this test case, it used a BLE library (ArduinoBLE
) that is not compatible with the ESP32-C6, limiting its usefulness unless the user is working with officially supported Arduino boards.
ChatGPT, on the other hand, is a general-purpose assistant. Without additional context, it defaulted to ESP-IDF — which is technically correct for the ESP32-C6 platform, but potentially overwhelming for Arduino users. When the prompt was adjusted to include the word “Arduino”, ChatGPT adapted accordingly and produced a sketch that could be uploaded directly using the Arduino IDE and the NimBLEDevice
library.
What This Means in Practice
- If you’re using official Arduino boards or developing with Arduino Cloud, the Arduino AI Assistant offers a smooth, focused experience — as long as you’re targeting supported hardware.
- If you’re working with ESP32 boards, especially newer variants like the ESP32-C6, and you want more flexibility, ChatGPT may give you more accurate and adaptable results — especially if you give it clear platform context in the prompt.
- If you’re just starting out or want a quick sketch to test an idea, either assistant can be helpful — just be aware of the default assumptions each one makes.
Final Tip
The most important variable in both cases isn’t the AI itself — it’s how you write the prompt. Adding one word — “Arduino” — changed ChatGPT’s entire output. Understanding how to guide these tools is as important as reviewing the code they generate.