LLM-Based Remote LED Control

The author’s native language is Chinese. This document is translated using AI.

1. Introduction

This blog demonstrates how to remotely control an LED using an LLM Chat tool, communicating via the MCP (Model Context Protocol). The device side uses the ESP32-C6-DevKitC-1 development board. Many of the steps in this tutorial are the same as in the previous article, RTIO-based Remote LED Control. However, instead of using curl on the user side, control is now performed via an LLM (acting as an Assistant).

The overall architecture is shown in the diagram below:

+-------+    +----------+    +--------+    +--------+
|       |--->|MCP-Server|--->|  RTIO  |--->|ESP32-C6|
| Chat  |    +----------+    +--------+    +--------+
| or    |
| Agent |    +----------+ 
|       |--->| LLM-APIs |                                    
+-------+    +----------+                                    

2. Hardware and Environment Setup

For details on setting up the hardware and system development environment on the device side, refer to RTIO-based Remote LED Control- Hardware and Environment Setup.

3. Experiment Steps

3.1. Run the Server

docker run -it --entrypoint ./rtio \
--rm  -p 17017:17017 -p 17917:17917 registry.cn-guangzhou.aliyuncs.com/rtio/rtio:v0.8.0  \
-disable.deviceverify -disable.hubconfiger \
-enable.hub.tls \
-hub.tls.certfile ./certificates/demo_server.crt \
-hub.tls.keyfile ./certificates/demo_server.key \
-log.level info

If running the service on WSL, you’ll need to configure the firewall and port mapping to WSL. Refer to RTIO-based Remote LED Control - WSL Network Configuration.

3.2. Compile and Run on the Device Side

Follow the steps in RTIO-based Remote LED Control - Device Compilation to compile and run the device-side code.

3.3. Configure MCP Server

The MCP Server can be integrated using various MCP Host tools. In this example, we use fast-agent. For detailed usage, refer to the fast-agent.ai.

Below is fastagent.config.yaml,

  • Add a rtio_led_switch entry under mcp.servers;
  • RTIO_DEVICE_URL - The RTIO server address and deviceID.
mcp:
    servers:
        rtio_led_switch:
            command: "npx"
            args: ["-y", "@mkrainbow/rtio-led-switch-demo"]
            env: 
                RTIO_DEVICE_URL: "http://localhost:17917/cfa09baa-4913-4ad7-a936-3e26f9671b10"

Also, ensure that rtio_led_switch is included in agent.py:

# Define the agent
@fast.agent(instruction="You are a helpful AI Agent", servers=["rtio_led_switch", "memory"])
async def main():
    # use the --model command line switch or agent arguments to change model
    async with fast.run() as agent:
        await agent.interactive()

3.4. Chat with LLM

Prompts:

  • Turn LED on via RTIO service.
  • Turn LED off via RTIO service.

Below is a sample conversation with the LLM. You can also refer to Showcase.

Device online:

$ uv run agent.py

default > Turn LED on via RTIO service.
╭────────────────────────────────────────────────────── (default) [USER] ─╮
│                                                                         │
│  Turn LED on via RTIO service.                                          │
│                                                                         │
╰─ gemma3-4b turn 1 ──────────────────────────────────────────────────────╯

╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│                                                                         │
│  the assistant requested tool calls                                     │
│                                                                         │
╰─ [rtio_led_switch] [memory]  ───────────────────────────────────────────╯

╭─ [TOOL CALL] ───────────────────────────────────────────────────────────╮
│                                                                         │
{"value":"on"}│                                                                         │
╰─ [rtio_led_sw…]  ───────────────────────────────────────────────────────╯

╭───────────────────────────────────────────────────────── [TOOL RESULT] ─╮
│                                                                         │
meta=None content=[TextContent(type='text', text='{"code":"OK"}',      │
annotations=None)] isError=False                                       │
│                                                                         │
╰─────────────────────────────────────────────────────────────────────────╯

╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│                                                                         │
│  The LED has been turned on.                                            │
│                                                                         │
╰─ [rtio_led_switch] [memory]  ───────────────────────────────────────────╯


default > Turn LED off via RTIO service.
╭────────────────────────────────────────────────────── (default) [USER] ─╮
│                                                                         │
│  Turn LED off via RTIO service.                                         │
│                                                                         │
╰─ gemma3-4b turn 2 ──────────────────────────────────────────────────────╯

╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│                                                                         │
│  the assistant requested tool calls                                     │
│                                                                         │
╰─ [rtio_led_switch] [memory]  ───────────────────────────────────────────╯

╭─ [TOOL CALL] ───────────────────────────────────────────────────────────╮
│                                                                         │
{"value":"off"}│                                                                         │
╰─ [rtio_led_sw…]  ───────────────────────────────────────────────────────╯

╭───────────────────────────────────────────────────────── [TOOL RESULT] ─╮
│                                                                         │
meta=None content=[TextContent(type='text', text='{"code":"OK"}',      │
annotations=None)] isError=False                                       │
│                                                                         │
╰─────────────────────────────────────────────────────────────────────────╯

╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│                                                                         │
│  The LED has been turned off.                                           │
│                                                                         │
╰─ [rtio_led_switch] [memory]  ───────────────────────────────────────────╯

Device offline:

default > Turn LED on via RTIO service.
╭────────────────────────────────────────────────────── (default) [USER] ─╮
│                                                                         │
│  Turn LED on via RTIO service.                                          │
│                                                                         │
╰─ gemma3-4b turn 7 ──────────────────────────────────────────────────────╯

╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│                                                                         │
│  the assistant requested tool calls                                     │
│                                                                         │
╰─ [rtio_led_switch] [memory]  ───────────────────────────────────────────╯

╭─ [TOOL CALL] ───────────────────────────────────────────────────────────╮
│                                                                         │
{"value":"on"}│                                                                         │
╰─ [rtio_led_sw…]  ───────────────────────────────────────────────────────╯

╭───────────────────────────────────────────────────────── [TOOL RESULT] ─╮
│                                                                         │
meta=None content=[TextContent(type='text',                            │
text='{"code":"DEVICEID_OFFLINE"}', annotations=None)] isError=False   │
│                                                                         │
╰─────────────────────────────────────────────────────────────────────────╯

╭─ [ASSISTANT] (default) ─────────────────────────────────────────────────╮
│                                                                         │
│  I'm unable to control the LED device at this time. It appears to be    │
│  offline.                                                               │
│                                                                         │
╰─ [rtio_led_switch] [memory]  ───────────────────────────────────────────╯

4. Appendix: Showcase

alt text

In conclusion, the REST-like nature of RTIO (e.g., immediate return of device call results) makes it extremely easy for an LLM to remotely control devices via MCP (Model Context Protocol). It is even feasible to generate MCP-Server code directly through the LLM.