SmartPi Agentic Assistant

An LLM-Powered, Expandable Raspberry Pi Pico W Personal Assistant

Overview

The idea of the SmartPi Assistant is a desktop device that highlights important events to improve the productivity of busy people. Often we are deluged by emails, slack messages, meetings etc. and it would be nice if a LLM could process these huge information sources and summarize them as actionable inputs through a simple device sitting on your desk or bedside.

Hand-drawn concept sketch of SmartPi Assistant showing dimensions and features
Hand-drawn concept sketch
Generated technical sketch of SmartPi hardware components and layout
Generated technical sketch

SmartPi Agentic Assistant is a compact, low-power personal assistant built on the Raspberry Pi Pico W, using large language models (LLMs) to convert raw digital data — from your calendar, email, weather feeds, or news sources — into concise text for a 64×64 display, and natural speech via a small speaker. We could also have a microphone on the SmartPi Assistant so we ask the Assistant to perform specific tasks - this pull mode can be quite useful to request for specific tasks.

It bridges the physical world of sensors, displays, and sound with the reasoning power of LLMs, creating a modular platform for context-aware, ambient intelligence at the edge.

System Design

The SmartPi Assistant

SmartPi Agentic Assistant high-level system design diagram
Hardware, networking, and software flow: enclosure with display (SmartPi device) connects over Wi-Fi/USB; a local gateway orchestrates agentic workflows using LLMs, tools, and servers.

SmartPi Embedded Controller Design

The SmartPi Assistant is an embedded interactive device built around the Raspberry Pi Pico W microcontroller. It integrates a LED matrix panel as the primary visual display, a 3–4 Ω speaker for audio output, and an Adafruit MAX98357 amplifier to drive the speaker with clear sound. The design also accommodates an Adafruit ICS-43434 digital I²S MEMS microphone for capturing voice commands, enabling natural, voice-based interaction. Together, these components form a compact, low-power system capable of displaying dynamic information, responding to spoken inputs, and communicating wirelessly through the Pico W's built-in Wi-Fi—making it a versatile platform for experimenting with edge AI, local sensing, and responsive embedded control.

Why Raspberry Pi Pico W?

The Raspberry Pi Pico W is an ideal choice for the SmartPi Embedded Controller because it combines affordability, performance, and built-in wireless connectivity in a compact form factor. Powered by the RP2040 dual-core ARM Cortex-M0+ processor, it provides sufficient computational power for real-time sensor data acquisition, control logic, and lightweight edge AI tasks, while maintaining low power consumption—critical for continuous operation in field environments. Its Wi-Fi capability enables seamless integration with cloud or local networks for data exchange and remote updates, eliminating the need for external modules. Additionally, the Pico W's rich GPIO interface, extensive community support, and compatibility with MicroPython and C/C++ SDKs make it both versatile and developer-friendly, ensuring rapid prototyping and reliable deployment.

Display Interface: HUB75 LED Matrix Connection

The HUB75 (2×8) connector serves as the standard interface between the Raspberry Pi Pico W and the 64×64 RGB LED matrix panel. It provides a set of 16 data and control lines, including color data inputs (R, G, B), row address lines, latch, clock, and output enable signals, which together allow precise control of the timing and brightness of individual LEDs. The HUB75 interface does not carry any intelligence—it simply maps the Pico's GPIO outputs directly to the LED driver circuitry on the panel. Because the display requires rapid and synchronized data updates to render smooth animations and text, the HUB75 connector offers a convenient and electrically robust way to handle high-speed parallel communication. Using this standardized interface simplifies wiring, ensures compatibility with a wide range of LED panels, and allows the SmartPi Assistant to produce dynamic visual feedback efficiently from the Pico W.

Connection Between Raspberry Pi Pico W and HUB75 Display Interface

The Raspberry Pi Pico W interfaces with the 64×64 RGB LED matrix through a 2×8 HUB75-style header (MTSW-108-23-G-D-264), which provides a standardized set of data, address, and control signals to drive the LED rows. The HUB75 connector carries the color data lines for both the upper and lower halves of the display (R1, G1, B1, R2, G2, B2), four row address lines (A–D), and three control signals: CLK (clock), LAT (latch), and OE (output enable). Three ground pins (at positions 4, 8, and 16) complete the interface. Power for the LED panel is provided separately via its dedicated 5 V, high-current 2-pin connector, and the Pico's ground must be shared with the panel's ground to ensure proper signal referencing.

The complete pin mapping between the Raspberry Pi Pico W and the HUB75 connector is shown in the table below:

Pico Pin # Pico GPIO HUB75 Pin # HUB75 Signal Description
4 GP2 1 R1 Upper half red data
5 GP3 2 G1 Upper half green data
6 GP4 3 B1 Upper half blue data
8 GND 4 GND Ground
7 GP5 5 R2 Lower half red data
9 GP6 6 G2 Lower half green data
10 GP7 7 B2 Lower half blue data
8 GND Ground
15 GP11 9 A Row address bit 0
16 GP12 10 B Row address bit 1
17 GP13 11 C Row address bit 2
19 GP14 12 D Row address bit 3
11 GP8 13 CLK Pixel clock (shift register clock)
12 GP9 14 LAT Latch (strobe)
14 GP10 15 OE Output enable (active-low blanking)
16 GND Ground

Good PCB layout practice includes placing multiple ground vias near the header and maintaining a solid ground pour under the signal traces to minimize skew and EMI.

SmartPi Hardware Implementation Details

  1. I procured a 6RGB full-color LED matrix panel, with 64x64 pixels and 2mm Pitch. From Waveshare.com. These are modular pieces that can be daisy chained together to create larger LED matrices. I liked the idea of modular expandable components.
RGB Matrix P2 64x64 LED panel
RGB Matrix P2 64x64 LED panel from Waveshare
RGB Matrix specifications and features
RGB Matrix specifications and modular design
  1. I designed a PCB board to interface the above LED matrix panel with Raspberry Pi Pico W with HUB75 standard socket
  1. Created the traces in the layout view with the following
PCB layout traces and routing
PCB layout view showing trace routing and component placement

There were some crossover issues after the manual trace layout, so dealt with a couple of crossovers using zero ohm resistors as bridges. There were 3 more that I had to deal with. So instead of option for a 2 sided board (and the more involved milling process), I discussed with Anthony and came up with making 3 holes through which we'd run wires and connect the HUB75 CLK, LAT and OE pins from the back of the board, using header pins on the Raspberry Pi Pico.

Added a JST for power and a switch to turn the device on/off.

  1. Created a Eagle 9.x compatible board file with a .brd extension from AutoCAD fusion. Loaded the .brd file on the Bantam software connected to the OtherMill(creative name!!) milling machine.
  2. Took a single sided board and taped it up using a double sided tape, making sure there are no overlaps (will mess up the milling if the board is uneven)
  1. Loaded the 1/64 mill bit and started milling the .brd file
PCB milling process
PCB milling process using 1/32" mill bit for larger traces
  1. Clicked "Mill All Visible" with the 1/64" mill bit, to mill the finer portions of the PCB board.
  2. After the fine milling, loaded the 1/32" larger mill bit, and started the fast milling of the larger board
  3. After completing all the traces, had to pick the holes and outline to cut using the 1/32" mill bit.
  4. After all this found that the board was not complete, it was missing the right side of the Raspberry Pi Pico pins and the power points and most importantly the text with course name!!
Incomplete PCB board from first milling attempt
Incomplete PCB board missing right side pins and text
  1. Anthony, exported a .gerber file and loaded its constituent parts for the trace, holes and outline, and restarted the job, and lo and behold:
  2. This time the milling machine completed the entire board with all the pins, traces, holes and outline and importantly the text with the course and my name on it, except for the JST power pins, which was no big deal, so we took it for done.
Completed PCB board with all traces and text
Successfully completed PCB board with all traces, pins, holes, and course text
  1. Started soldering the Raspberry Pi Pico W onto the newly printed PCB. The adonstar digital magnifier was super useful in placing the RP Pico W precisely on the correct position on the board, such that the tiny copper pads created by the layout tool was centered correctly.
Digital magnifier for precise component placement
Digital magnifier setup for precise component placement and inspection
  1. Cleaned my soldering iron with a small amount of soldering lead to get good flow, then put a blob of solder lead on the bottom left pin and holding the RP Pico W down applied the soldering iron and got one pin securely fastened. After that it was just a matter of getting the rest going.

The digital magnifier was not actually useful to solder, it helped to look directly at the components, but it helped in inspecting the solders and ensure there were no bridges between pins.

Raspberry Pi Pico W soldered to PCB
Raspberry Pi Pico W successfully soldered to custom PCB
  1. Then proceeded to solder the zero ohm resistors, which were super tiny and difficult to keep in place as the soldering iron and lead came close, it would move and get soldered in weird angles, one needed to heat the trace pad again and ever so slightly move it back into place - this was challenging. But got all 3 zero ohm resistor soldered in place to bridge underlying traces. Test connectivity of the resistors as well as the underlying trace which it was bridging over. All was fine.
  2. Finally got the diode from the external 5V Vcc point to the VSYS (pin 39) soldered ensuring that the tiny (barely visible) arrow mark was in the correct direction. Added the 3 wires that were needed to avoid the trace crossover challenges.
All components soldered to PCB
Completed PCB with all components soldered including zero-ohm resistors and diode

Lessons Learned: Multiple Errors in the PCB Design

Despite careful planning, several critical mistakes emerged during the PCB fabrication and assembly process that prevented the board from functioning correctly. These errors provided valuable learning experiences about PCB design considerations and the importance of thinking through physical constraints early in the design process.

1. Inverted HUB75 Connector Placement

Since I did not have a female HUB75 connector component in the lab, I had to make the HUB75 connector using header pins. Due to the single-sided PCB board, I was forced to solder the header pins on the milled side of the PCB, with the connector header pins sticking out at the bottom (on the back side of the PCB board). This resulted in basically wrong (inverted) connections driving the HUB75 ribbon to the display matrix.

This orientation issue meant that the signal mapping from the Raspberry Pi Pico GPIO pins to the LED matrix was completely reversed, making it impossible to display anything correctly without rewiring or using an adapter board.

2. Missing Power Supply for the LED Matrix

The LED matrix needs a 5V power supply with high current capacity—it cannot be driven by the small current capacity of the Raspberry Pi Pico W. I made no provisions to drive the display board, besides powering the Raspberry Pi Pico W itself. This was yet another rookie mistake.

A 64×64 RGB LED matrix can draw several amps of current when displaying bright, full-screen images. The Pico W's 5V pin can only source a few hundred milliamps at most, which is far insufficient. A proper design would have included a dedicated 5V, high-current power input (such as a barrel jack or JST connector) connected directly to the LED matrix panel's power pins, with only signal-level connections going through the Pico.

3. Poor USB Connector Accessibility

By placing the Raspberry Pi Pico W in the middle of the PCB board, I made it hard to insert the micro USB port provided on top of the RP Pico board. I should have designed the board such that the RP Pico W would be at the top edge of the board, making it easy to connect the micro-USB cable for programming and power.

This seemingly minor oversight created significant practical problems during development and testing, requiring awkward cable angles and making it difficult to access the BOOTSEL button for firmware updates.

Key Takeaways

  • Connector orientation matters: Always verify that through-hole connectors will mate correctly with their counterpart cables, considering which side of the board they'll be soldered to.
  • Power budget planning: Calculate the maximum current draw of all components early in the design and provision adequate power supply connections.
  • Physical accessibility: Place connectors and buttons that need frequent access near the board edges, away from other components that might obstruct them.
  • Prototype early: These mistakes could have been caught with a simple cardboard mockup or 3D-printed test fixture before committing to PCB fabrication.

Trip to India and Time to Derisk the Project

I needed to go to India for 10days for a meeting and event. I knew that the PCB still had the power connections and switch to be connected and the HUB75 pins to be tested. It was unlikely all this was going to work in a day. Then there was the learning about the software environment and then developing the software to test the LED matrix board.

I decided that I would de-risk the project by using a pre-fabricated RGB Matrix Adapter Board, which I had bought when I ordered the Matrix Board itself. Decided to use the adapter to interface the Raspberry Pi Pico W to the RGB Matrix. This was a life saver.

Pre-fabricated RGB Matrix Adapter Board
Pre-fabricated RGB Matrix Adapter Board for easy interfacing
RGB Matrix Adapter Board
RGB Matrix Adapter Board close-up view
  1. Installing header pins to the Raspberry Pi Pico so it could be fit into the Adapter board.
Raspberry Pi Pico with header pins
Raspberry Pi Pico W with header pins installed for adapter board connection
  1. Pushed the RP Pico W with header pins into the adapter board and connected the a the HUB75 ribbon connector and power supply JST connector
Adapter board connected to RGB matrix
Adapter board connected to RGB matrix with HUB75 ribbon cable and power supply
  1. Connected the adapter card to the RGB Matrix
  1. Connected the micro USB cable into the raspberry Pi Pico and the other end to my Macbook.

Setting up the Raspberry Pi Pico with CircuitPython Environment

I decided to use CircuitPython on the RP Pico, using the Mu Editor, here are the setup instructions:

  1. Downloaded the CircuitPython UF2 file.
  2. I held down the BOOTSEL button, then plugged the Pico into a USB port on my macbook.
  3. Release the BOOTSEL button after connecting the Pico.
  4. It will install as a mass storage device named "RPI-RP2".
  5. Drag and drop the CircuitPython UF2 file onto the "RPI-RP2" volume. The Pico will reboot, a new disk drive will appear named CIRCUITPY, and you're done flashing.
  6. There will be a default code.py file in the new disk drive, you open it with Mu Editor, and the content in it is: "print("Hello World!")", the specific opening steps are shown in the last figure.

Loaded a Test Program

import board
import displayio
import framebufferio
import rgbmatrix
import time

# Always release any existing display before (re)initializing
displayio.release_displays()

# --- HUB75 + Seengreat Pico adapter pin map ---
rgb_pins = [board.GP2, board.GP3, board.GP4,   # R1, G1, B1
            board.GP5, board.GP8, board.GP9]   # R2, G2, B2
addr_pins = [board.GP10, board.GP16, board.GP18, board.GP20, board.GP22]  # A,B,C,D,E
clock_pin = board.GP11
latch_pin = board.GP12
oe_pin     = board.GP13

WIDTH = 64
HEIGHT = 64

matrix = rgbmatrix.RGBMatrix(
    width=WIDTH, height=HEIGHT, bit_depth=5,  # bit_depth 4–6 is a good starting point
    rgb_pins=rgb_pins,
    addr_pins=addr_pins,
    clock_pin=clock_pin,
    latch_pin=latch_pin,
    output_enable_pin=oe_pin,
    tile=1, serpentine=False, doublebuffer=True
)

display = framebufferio.FramebufferDisplay(matrix, auto_refresh=True)

# Create a 1-color full-screen bitmap we can recolor quickly
palette = displayio.Palette(1)
bitmap = displayio.Bitmap(WIDTH, HEIGHT, 1)
tg = displayio.TileGrid(bitmap, pixel_shader=palette)
group = displayio.Group()
group.append(tg)
display.root_group = group

def fill(rgb_hex):
    palette[0] = rgb_hex
    # bitmap already all index 0, so changing palette updates whole screen
    time.sleep(0.5)

while True:
    fill(0xFF0000)  # red
    fill(0x00FF00)  # green
    fill(0x0000FF)  # blue
    fill(0xFFFFFF)  # white (brief)
    fill(0x000000)  # black (off)

The Test program worked!!

RGB Matrix test program successfully running - cycling through red, green, blue, white, and black colors
RGB Matrix displaying 'HELLO HTMAA' successfully
Victory Achieved! The pixels obey my commands!

SmartPi Enhancement: Adding Voice Interaction Capabilities

Following the insights gained from the MAS.863 classes on input devices and output devices, I recognized an opportunity to significantly enhance the SmartPi Assistant's functionality by adding bidirectional audio capabilities. While the initial design focused solely on visual output through the LED matrix—operating in a "push" model where the n8n cloud server unilaterally displays information—the addition of audio I/O transforms the device into a truly interactive assistant.

This enhancement enables two critical capabilities:

For the audio subsystem, I chose to implement a fully digital audio path using the I²S (Inter-IC Sound) protocol. This decision offers several advantages over traditional analog solutions: I²S eliminates the need for external analog-to-digital (ADC) or digital-to-analog (DAC) converters on the microcontroller side, reduces susceptibility to electrical noise and ground loops, simplifies PCB routing by using digital logic levels, and ensures bit-perfect audio transmission between the Pico W and the audio peripherals. The I²S standard is well-supported in embedded audio applications and provides a clean, standardized interface that integrates seamlessly with the RP2040's PIO (Programmable I/O) subsystem.

Audio Output: Adafruit MAX98357 I²S Amplifier

The Adafruit MAX98357 is a digital-to-analog Class-D audio amplifier designed to convert digital audio signals (I²S) into high-quality analog sound capable of driving a speaker directly. The Raspberry Pi Pico W itself lacks both a digital-to-analog converter (DAC) and sufficient output power to drive a low-impedance speaker—its GPIO pins can only source a few milliamps of current, suitable for logic signals but not for audio. The MAX98357 fills this gap by taking the Pico's I²S digital audio stream, converting it to an amplified analog output, and efficiently delivering up to 3 watts of power to a 3–4 Ω speaker. This makes it ideal for producing clear, audible speech or alert tones in a compact, low-power embedded system like the SmartPi Assistant, without the need for bulky external amplification hardware.

Adafruit MAX98357 amplifier front view showing component layout
MAX98357 amplifier front view
Adafruit MAX98357 amplifier back view showing pin labels
MAX98357 amplifier back view with pin labels

Connecting the MAX98357 to Raspberry Pi Pico W

Purpose: Converts Pico's digital audio (I²S) into amplified analog output for a speaker.
Power: 3 V – 5 V (use 5 V if available).

MAX98357 Pin Connect to Pico W Notes
VIN 5 V Same 5 V supply as LED panel (with shared GND)
GND GND Common ground with Pico
DIN GP18 I²S Data Out
BCLK GP19 I²S Bit Clock
LRCLK GP20 I²S Left/Right Clock (a.k.a. Word Select)
GAIN / SD Pull-up or GPIO (optional) Controls shutdown / gain; tie high to enable always-on

Speaker: Connect the 3–4 Ω speaker to L+ and L- outputs.
✅ This group can be tested independently by playing audio tones or speech from the Pico without connecting the mic.

Audio Input: Adafruit ICS-43434 I²S MEMS Microphone

The Adafruit ICS-43434 is a digital I²S MEMS microphone that captures high-quality audio and outputs it as a clean, noise-free digital signal. Unlike analog microphones, which require external amplification and are prone to interference, the ICS-43434 performs the analog-to-digital conversion internally and transmits audio data directly to the Raspberry Pi Pico W over the I²S interface. This eliminates the need for an external amplifier stage and ensures consistent performance even in electrically noisy environments. The microphone's small form factor and digital output make it ideal for embedded applications such as the SmartPi Assistant, where it provides accurate voice input for commands or speech recognition. By using the ICS-43434, the system gains a reliable, low-power, and high-fidelity audio capture capability that complements the Pico W's digital audio architecture.

Adafruit ICS-43434 I²S MEMS digital microphone breakout board
Adafruit ICS-43434 I²S MEMS Digital Microphone

Connecting the ICS-43434 to Raspberry Pi Pico W

Purpose: Captures digital audio directly from sound; sends I²S data to Pico.
Power: 3.3 V only (do not connect to 5 V).

ICS-43434 Pin Connect to Pico W Notes
3V3 (VDD) 3.3 V Power supply
GND GND Common ground
WS (LRCLK) GP22 I²S Left/Right Clock
SCK (BCLK) GP26 I²S Bit Clock
SD (DOUT) GP27 I²S Data Output from mic to Pico
SEL GND (or 3V3) Sets left/right channel (tie to GND for left)

✅ This group can be tested independently by capturing raw audio samples or displaying waveform data via USB serial.

PCB Design Take 2 with Speaker and Mic

Building upon the lessons learned from the initial PCB design, I started work on an improved version that integrates the audio capabilities directly into the board. This second iteration incorporates both the speaker amplifier and microphone interfaces alongside the existing HUB75 LED matrix connection, creating a more integrated and capable SmartPi platform.

Component Integration

The enhanced PCB design includes the following additions:

PCB Version 2 schematic showing Raspberry Pi Pico W with MAX98357 amplifier, ICS-43434 microphone, and HUB75 connections
PCB V2 Schematic: Integrated design with audio input/output and LED matrix interface

Two-Sided PCB Design

The routing complexity increased significantly with the addition of the audio components. After attempting single-sided routing, it became clear that a two-sided PCB board was necessary to accommodate all the connections cleanly. The final layout uses three vias to route HUB75 connections to the bottom layer, minimizing trace crossovers while maintaining signal integrity. This approach provides a cleaner, more manufacturable design compared to using jumper wires or zero-ohm resistors as bridges.

Design Rules and Improvements

Reflecting on the challenges encountered during the first PCB assembly—particularly the difficulty of soldering 16 mil traces with closely-spaced nets and pins—I revised the design rules for improved manufacturability:

PCB Version 2 layout view showing trace routing, component placement, and via locations
PCB V2 Layout: Two-sided design with optimized trace widths and via routing

These design refinements not only improve the physical assembly process but also enhance the electrical characteristics of the board, particularly for the analog audio output path where wider traces reduce impedance and potential interference. The result is a more robust, easier-to-manufacture platform that fully integrates the SmartPi Assistant's display, audio output, and audio input capabilities.

Milling PCB 2

With the improved two-sided PCB design complete, I proceeded to mill and assemble the second-generation SmartPi board. The milling process for the two-sided board required careful alignment and the use of vias to connect traces between the top and bottom layers.

Assembly Process

The assembly of PCB V2 involved several critical steps to ensure proper electrical connectivity and mechanical stability:

Fully assembled PCB Version 2 with Raspberry Pi Pico W, audio components, HUB75 connector, and power connections soldered
PCB V2 Fully Assembled: All components soldered including Pico W, audio peripherals, and HUB75 interface

The improved trace widths and spacing made the soldering process notably smoother than the first iteration. The two-sided design with vias, while more complex to manufacture, resulted in a cleaner board with better electrical characteristics and no need for jumper wires or zero-ohm resistor bridges.

Design of the Agentic Workflow Software Components

The idea is to take inputs from various sources like Email, Calendar and Weather and let LLMs review the sources to give useful alerts to make my day more productive. Currently the LLM output being considered is a 64×64 pixel LED display output alert, we should be able to add other outputs such as a speaker to provide a voice output as well.

We should be able to plug in different LLMs depending on the capability and cost. We can also envision using a quantized LLM to fit into the Raspberry Pi Pico controller to be self contained and be able to process inputs without having to make API calls online.

The outputs from the LLM needs to be converted into an image that can be rendered on the 64×64 pixel LED screen.

n8n Workflow Architecture

To orchestrate the data collection and LLM processing, I built an n8n workflow that automates the entire pipeline from data sources to SmartPi display output. The workflow is triggered manually or on a schedule and consists of three parallel processing branches:

n8n workflow diagram showing parallel branches for calendar, weather, and email processing
n8n workflow architecture with three parallel data processing branches

The workflow architecture consists of the following components:

  1. Create an Assistant Node: Initializes an AI assistant instance at the start of the workflow, configuring the LLM that will process and summarize the incoming data.
  2. Manual Trigger: The "When clicking 'Execute workflow'" node allows on-demand execution for testing and immediate updates.
  3. Calendar Branch:
    • Get Calendar events: Fetches upcoming events from Google Calendar API
    • JS Code1: Processes and filters calendar data, extracting relevant meeting information
    • Calendar Alert: Sends formatted calendar notifications to the SmartPi display via MCP
  4. Weather Branch:
    • Get Weather Data: Retrieves current weather conditions from OpenWeatherMap API for Cambridge, MA
    • JS Code: Formats weather data and generates contextual advice (e.g., "Wear a Jacket" if temperature is below 40°F)
    • Weather Alert: Displays weather summary and recommendations on the SmartPi LED matrix
  5. Email Branch:
    • Get Email messages: Connects to email via IMAP and retrieves recent unread messages
    • JS Code2: Extracts sender and subject information, passing it to the LLM for summarization
    • Email Alert: Sends concise email summaries to the SmartPi display

All three branches execute in parallel, allowing the workflow to aggregate multiple data sources simultaneously. Each branch processes its data independently and sends MCP commands to the SmartPi bridge server, which then forwards display instructions to the Raspberry Pi Pico W. This parallel architecture ensures low latency and enables the SmartPi Assistant to provide real-time, multi-source contextual alerts.

The workflow demonstrates the power of visual programming for agentic systems — complex data orchestration, LLM integration, and hardware control are all coordinated through a no-code interface, making the system easy to modify and extend as new data sources or output modes are added.

Software Design and MCP Integration

For the software integration of the SmartPi Agentic Assistant, I decided to implement a Model Context Protocol (MCP)–based architecture to connect my n8n agentic workflow with the SmartPi hardware. MCP provides a standardized way for different AI agents, tools, and hardware components to communicate through a shared schema of "tools" and "contexts." This approach gives my project modularity, expandability, and long-term flexibility as I add new capabilities.

Using MCP to connect n8n and the SmartPi

In my setup, the n8n workflow — which collects inputs from email, calendar, and weather APIs and processes them through an LLM — acts as the MCP client. The SmartPi device, through a lightweight bridge service I wrote in Python, acts as the MCP server.

The MCP client (n8n) sends structured messages to the server whenever the workflow produces an actionable summary. For example, when my LLM determines that I have an upcoming meeting, the workflow constructs an MCP message like:

{
  "tool": "display_text",
  "args": {
    "text": "Meeting at 10:30 AM with John Doe",
    "fg": "#00FF00",
    "bg": "#000000"
  }
}

This message is passed to the SmartPi MCP server, which exposes functions such as display_text(), fill_color(), and show_icon(). The server then forwards these commands to the Raspberry Pi Pico W via Wi-Fi or serial communication, using a simple JSON-based protocol.

Role of MCP client and server in my design

In this system:

Conceptually, the workflow looks like this:

flowchart TD
    subgraph n8n["n8n Agentic Workflow"]
        Node["LLM Node"]
    end

    subgraph BridgeServer["SmartPi MCP Bridge Server"]
        PythonAPI["Python + FastAPI + mcp-tools"]
        ExposedAPIs["Exposes MCP functions:
'display_text', 'fill_color', etc."] end subgraph Pico["Raspberry Pi Pico W"] CircuitPython["CircuitPython Firmware"] LEDMatrix["64×64 LED Matrix Display"] end Node -->|"MCP Client sends JSON"| BridgeServer BridgeServer -->|"Wi-Fi JSON command"| Pico Pico -->|"Parse JSON → Update LEDs"| LEDMatrix

Why I chose MCP

I chose to integrate MCP because it provides a modular and extensible interface for hardware-agent communication. Instead of hard-coding MQTT topics or HTTP endpoints, the SmartPi's capabilities can be exposed as well-defined "tools". This allows me to easily add new hardware features later — such as microphone input, temperature sensors, or speech output — by simply registering new MCP tools on the server side.

This architecture also aligns well with the agentic computing theme of my project: the SmartPi becomes a peripheral that LLMs and automation workflows can interact with contextually, rather than a passive display device.

How this fits into my workflow

The n8n workflow aggregates and summarizes my daily digital inputs — for example:

When these events are generated, the MCP client in n8n sends them directly to the SmartPi MCP server, which immediately updates the LED matrix display through the Pico W. This push model ensures that the device reflects real-time, meaningful summaries without requiring the Pico to constantly poll for updates.

Future directions

By structuring my software around MCP, I can later connect the SmartPi system to other MCP-compatible environments — for example, ChatGPT or LangChain-based local agents — allowing LLMs to control the physical device directly. This makes the SmartPi Assistant not just a display, but a node in a larger context-aware ambient intelligence network.

Appendix: Implementation Details — MCP Bridge Server

To implement the MCP bridge between my n8n workflow and the SmartPi device, I created a lightweight Python MCP Server that runs on my laptop (or on a Raspberry Pi Zero). This server exposes a set of "tools" compliant with the Model Context Protocol, so that any MCP client — such as my n8n agentic workflow — can invoke them to control the SmartPi hardware.

MCP Bridge Architecture

The bridge acts as the translation layer between high-level agentic intents (like "display meeting reminder") and low-level hardware commands (like "fill the screen blue and render text"). It communicates with the Raspberry Pi Pico W over Wi-Fi via a simple HTTP or WebSocket interface.

The software stack is structured as follows:

[n8n Agentic Workflow / LLM Node]
        ↓
 (MCP Client sends JSON)
        ↓
[SmartPi MCP Bridge Server]
  - Python + FastAPI + mcp-tools
  - Exposes "display_text", "fill_color", etc.
        ↓
 (Wi-Fi JSON command)
        ↓
[Raspberry Pi Pico W running CircuitPython]
  - Parses JSON, updates 64×64 LED Matrix

MCP Server Implementation (Python)

Below is the simplified version of my MCP bridge code. It defines a small set of SmartPi tools (display_text, fill_color) and handles incoming MCP calls from the n8n workflow.

# smartpi_mcp_server.py
from fastapi import FastAPI
from pydantic import BaseModel
import requests
import os

app = FastAPI(title="SmartPi MCP Bridge")

# Device configuration
SMARTPI_DEVICE_URL = os.getenv("SMARTPI_DEVICE_URL", "http://192.168.1.100:8000/update")

# Define MCP-compatible request schema
class MCPCommand(BaseModel):
    tool: str
    args: dict

@app.post("/mcp")
def handle_mcp(cmd: MCPCommand):
    """Receive tool calls from an MCP client (e.g. n8n)."""
    tool = cmd.tool
    args = cmd.args

    if tool == "display_text":
        payload = {
            "mode": "text",
            "text": args.get("text", ""),
            "fg": args.get("fg", "#FFFFFF"),
            "bg": args.get("bg", "#000000")
        }
    elif tool == "fill_color":
        payload = {"mode": "fill", "color": args.get("color", "#000000")}
    else:
        return {"error": f"Unknown tool '{tool}'"}

    try:
        r = requests.post(SMARTPI_DEVICE_URL, json=payload, timeout=3)
        return {"status": "ok", "device_response": r.json()}
    except Exception as e:
        return {"status": "failed", "error": str(e)}

# Optional: expose metadata for MCP discovery
@app.get("/.well-known/mcp.json")
def mcp_manifest():
    return {
        "name": "SmartPi MCP Server",
        "description": "Bridge for controlling SmartPi LED matrix and peripherals",
        "tools": [
            {
                "name": "display_text",
                "description": "Render text message on SmartPi LED matrix",
                "parameters": {
                    "text": "string",
                    "fg": "string (hex color)",
                    "bg": "string (hex color)"
                }
            },
            {
                "name": "fill_color",
                "description": "Fill the SmartPi screen with a single color",
                "parameters": {"color": "string (hex color)"}
            }
        ]
    }

I run this service using:

uvicorn smartpi_mcp_server:app --host 0.0.0.0 --port 5055

Once running, any MCP-compatible client (including n8n, LangChain, or ChatGPT with MCP) can call:

POST http://<bridge-ip>:5055/mcp
{
  "tool": "display_text",
  "args": {"text": "Meeting at 10:30AM", "fg": "#00FF00", "bg": "#000000"}
}

The server immediately forwards this to the SmartPi device via Wi-Fi, which then updates the LED matrix accordingly.

Pico Listener Firmware

On the Raspberry Pi Pico W, I modified my earlier CircuitPython program to act as a lightweight listener. It exposes a simple HTTP endpoint (/update) to receive JSON payloads. This keeps the device code minimal and makes it protocol-agnostic — the Pico doesn't need to know anything about MCP, only how to parse commands.

# code.py on Pico W
import wifi, socketpool, adafruit_requests, board, displayio, framebufferio, rgbmatrix, json
from secrets import secrets
from adafruit_display_text import label
import terminalio

# Wi-Fi setup
wifi.radio.connect(secrets["ssid"], secrets["password"])
pool = socketpool.SocketPool(wifi.radio)
requests = adafruit_requests.Session(pool, None)

# LED Matrix setup (same as before)
displayio.release_displays()
rgb_pins = [board.GP2, board.GP3, board.GP4, board.GP5, board.GP8, board.GP9]
addr_pins = [board.GP10, board.GP16, board.GP18, board.GP20, board.GP22]
clock_pin, latch_pin, oe_pin = board.GP11, board.GP12, board.GP13

matrix = rgbmatrix.RGBMatrix(
    width=64, height=64, bit_depth=5,
    rgb_pins=rgb_pins, addr_pins=addr_pins,
    clock_pin=clock_pin, latch_pin=latch_pin,
    output_enable_pin=oe_pin
)
display = framebufferio.FramebufferDisplay(matrix)

palette = displayio.Palette(1)
bitmap = displayio.Bitmap(64, 64, 1)
tile = displayio.TileGrid(bitmap, pixel_shader=palette)
group = displayio.Group()
group.append(tile)
display.root_group = group

label_area = label.Label(terminalio.FONT, text="", color=0xFFFFFF)
group.append(label_area)

def fill(rgb_hex):
    palette[0] = rgb_hex

def show_text(msg, fg, bg):
    fill(bg)
    label_area.text = msg
    label_area.color = fg

# Minimal web server to receive updates
import adafruit_httpserver
server = adafruit_httpserver.Server(pool, "/update")

@server.route("/update", methods=["POST"])
def handle_update(request):
    data = request.json()
    mode = data.get("mode")
    if mode == "fill":
        fill(int(data.get("color").replace("#", "0x"), 16))
    elif mode == "text":
        fg = int(data.get("fg").replace("#", "0x"), 16)
        bg = int(data.get("bg").replace("#", "0x"), 16)
        show_text(data.get("text", ""), fg, bg)
    return request.respond("OK")

print("SmartPi listening at:", wifi.radio.ipv4_address)
server.serve_forever()

How it all comes together

When the n8n workflow produces an actionable event:

  1. The MCP client node sends a display_text command to the MCP server.
  2. The SmartPi MCP Server translates this into a /update request to the Pico W.
  3. The Pico W updates the LED display instantly.

This architecture cleanly separates responsibilities:

By building the integration around MCP, I've made the SmartPi Assistant a general-purpose agent endpoint — it can now accept commands not only from n8n, but also from any other MCP-compliant tool, LLM agent, or automation framework.