Arduino UNO Q - Web Server

The Arduino UNO Q has a built-in Debian Linux MPU, which means it can run a full web server — no extra Wi-Fi shield or cloud service needed. This tutorial shows you how to run a Python WebUI web server on the Arduino UNO Q in three progressive examples, starting from the simplest possible page and building up to a clean API.

In this tutorial, you will learn:

Arduino UNO Q Web Server

Hardware Preparation

1×Arduino UNO Q
1×USB Cable for Arduino Uno Q
1×Potentiometer
1×Alternatively, 10k Ohm Trimmer Potentiometer
1×Alternatively, Potentiometer Kit
1×Alternatively, Potentiometer Module with Knob
1×Breadboard
1×Jumper Wires
1×Recommended: Screw Terminal Block Shield for Arduino Uno
1×Recommended: Sensors/Servo Expansion Shield for Arduino Uno
1×Recommended: Breadboard Shield for Arduino Uno
1×Recommended: Enclosure for Arduino Uno
1×Recommended: Prototyping Base Plate & Breadboard Kit for Arduino UNO

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

How the Arduino UNO Q Web Server Works

The Arduino UNO Q combines two processors:

  • STM32 MCU — runs your Arduino sketch (C/C++) in real-time. It reads sensors, controls relays, drives motors, and more.
  • Qualcomm MPU — runs full Debian Linux. It connects to Wi-Fi and can run a Python WebUI web server that any browser on your network can reach.

The two processors communicate via the Bridge. The MCU exposes hardware functions through Bridge, and the Python web server on the MPU calls those functions to get sensor readings. A browser then fetches the data — either as a plain HTML page, a styled web page, or a JSON API response.

※ NOTE THAT:

No external Wi-Fi shield is needed. The Arduino UNO Q has built-in Wi-Fi on the Linux side.

Wiring Diagram

The wiring diagram between Arduino UNO Q Potentiometer

This image is created using Fritzing. Click to enlarge image

Arduino UNO Q Code

The Arduino UNO Q has two processors. For a web server, you write:

  • An MCU sketch that reads the analog pin and exposes the value via Bridge
  • A Python WebUI script on the Linux MPU that serves the value over HTTP

All three examples below use the same MCU sketch. Only the Python code (and optional HTML assets) differ.

Example 1 — Simple Web Page (No CSS, No JavaScript)

The simplest possible web server: open a browser, visit the board's IP, and see the raw analog reading in plain HTML. No styling, no auto-refresh — just the number.

The MCU sketch reads pin A0 and exposes the value through a Bridge function called get_analog. The Python server calls that function when a browser requests / and returns a bare HTML response.

MCU Code

/* * This Arduino UNO Q code was developed by newbiely.com * * This Arduino UNO Q code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-uno-q/arduino-uno-q-web-server */ #include "Arduino_RouterBridge.h" String get_analog(String arg) { int value = analogRead(A0); return String(value); } void setup() { Monitor.begin(); Bridge.begin(); Bridge.provide_safe("get_analog", get_analog); Monitor.println("Bridge ready"); } void loop() { }

Python Code

""" This Arduino UNO Q script was developed by newbiely.com This Arduino UNO Q script is made available for public use without any restriction For comprehensive instructions and wiring diagrams, please visit: https://newbiely.com/tutorials/arduino-uno-q/arduino-uno-q-web-server """ from arduino.app_utils import * from arduino.app_bricks.web_ui import WebUI from fastapi.responses import HTMLResponse def index(): value = Bridge.call("get_analog", "") html = "<!DOCTYPE html><html><body><p>Analog A0: " + value + "</p></body></html>" return HTMLResponse(content=html) web_ui = WebUI() web_ui.expose_api("GET", "/", index) App.run()

Detailed Instructions

  • Connect: Plug the Arduino UNO Q into your computer with a USB-C cable and wire the potentiometer to A0 as described in the Wiring Diagram section.
  • Connect to Wi-Fi: Make sure the Arduino UNO Q is connected to your Wi-Fi network. Use the network icon in Arduino App Lab to connect first.
  • Open Arduino App Lab: Launch Arduino App Lab and wait until it detects your Arduino UNO Q.
  • Create a new App: Click the Create New App button.
Create New App in Arduino App Lab on Arduino UNO Q
  • Give the App a name, for example: WebServerSimple
  • Click Create to confirm.
Arduino App Lab App folders and files on Arduino UNO Q
  • Paste the MCU sketch: Copy the MCU code above and paste it into sketch/sketch.ino.
  • Paste the Python code: Open python/main.py in the App. Select all existing content and delete it, then paste the Python web server code above.
  • Install the library: Click the Add sketch library button (the open book icon with a + sign) in the left sidebar.
Add sketch library in Arduino App Lab on Arduino UNO Q
  • Search for Arduino_RouterBridge created by Arduino and click the Install button.
My Apps / DIYables Apps
Run
Bricks
No bricks added...
Sketch Libraries
No sketch libra...
Files
python
sketch
.gitignore
README.md
app.yaml
sketch.ino
Add sketch library
Arduino_RouterBridge Arduino

This library provides a simple RPC bridge for Arduino UNO Q boards, allowing communication between the board and other devices using MsgPack serialization.

0.4.1
Install
More Info
  • Add the WebUI Brick: Click the Add Brick button in the Editor sidebar to open the Bricks catalog.
Add Brick button in Arduino App Lab Editor sidebar

Find and select WebUI - HTML from the list, then follow any configuration prompts.

WebUI - HTML Brick selected in Arduino App Lab Bricks catalog

Arduino App Lab automatically adds the Brick entry to your app.yaml file — do not edit that entry manually.

※ NOTE THAT:

A Brick is a pre-built, plug-and-play service that runs on the Arduino UNO Q Linux side. The WebUI - HTML Brick handles the HTTP server so you do not need to install Flask or any other web framework manually. See About Bricks for more details.

  • Upload: Click the Run button in Arduino App Lab to compile and upload.
Click Run button in Arduino App Lab on Arduino UNO Q
  • Open a web browser on your phone or PC and navigate to: http://:7000/

Replace <ARDUINO_UNO_Q_IP> with the IP address of your Arduino UNO Q.

  • You will see a plain HTML page showing the current raw ADC value from A0.
  • Turn the potentiometer knob and refresh the browser page — the number changes.

App Lab Console Output

DIYables_Apps
Stop
sketch.ino
1#include "Arduino_RouterBridge.h"
Serial Monitor
Python
Message (Enter to send a message to "Newbiely" on usb(2820070321))
New Line
9600 baud
[2026-05-08 09:00:01] Bridge ready
DIYables_Apps
Stop
sketch.ino
1#include "Arduino_RouterBridge.h"
Serial Monitor
Python
[2026-05-08 09:00:02] WebUI started on http://0.0.0.0:7000 [2026-05-08 09:00:02] WebUI started on http://192.168.0.45:7000

Browser Output

Open http://<ARDUINO_UNO_Q_IP>:7000/ in your browser. You will see a plain HTML page like:

Analog A0: 512

_RENDER

Arduino UNO Q Simple Web Server Page

Web Server with Asset Folder (HTML + CSS + JavaScript)

This example separates the front-end files into an assets/ folder, just like the blink-with-ui example. The Python server exposes a /api/analog endpoint; the index.html in the assets folder calls that endpoint every second with JavaScript and updates the displayed value — no page refresh needed.

The MCU sketch is the same as Example 1. The differences are:

  • The Python code exposes /api/analog instead of a raw HTML route.
  • The assets/index.html file contains the HTML, CSS, and JavaScript.
  • The WebUI Brick automatically serves all files in the assets/ folder at the root URL (/).

MCU Code

/* * This Arduino UNO Q code was developed by newbiely.com * * This Arduino UNO Q code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-uno-q/arduino-uno-q-web-server */ #include "Arduino_RouterBridge.h" String get_analog(String arg) { int value = analogRead(A0); return String(value); } void setup() { Monitor.begin(); Bridge.begin(); Bridge.provide_safe("get_analog", get_analog); Monitor.println("Bridge ready"); } void loop() { }

Python Code

""" This Arduino UNO Q script was developed by newbiely.com This Arduino UNO Q script is made available for public use without any restriction For comprehensive instructions and wiring diagrams, please visit: https://newbiely.com/tutorials/arduino-uno-q/arduino-uno-q-web-server """ from arduino.app_utils import * from arduino.app_bricks.web_ui import WebUI def get_analog(): value = Bridge.call("get_analog", "") return {"pin": "A0", "value": int(value)} web_ui = WebUI() web_ui.expose_api("GET", "/api/analog", get_analog) App.run()

HTML (assets/index.html)

Place this file in the assets/ folder of your App:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Analog Sensor</title> <style> *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', Arial, sans-serif; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; background: linear-gradient(135deg, #0f2027, #203a43, #2c5364); color: #fff; padding: 24px; } .card { background: rgba(255, 255, 255, 0.07); backdrop-filter: blur(12px); border: 1px solid rgba(255, 255, 255, 0.12); border-radius: 20px; padding: 48px 56px; text-align: center; width: 100%; max-width: 380px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); } .pin-badge { display: inline-block; background: rgba(0, 188, 212, 0.2); border: 1px solid #00bcd4; color: #00bcd4; font-size: 0.75rem; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; padding: 4px 12px; border-radius: 999px; margin-bottom: 20px; } h1 { font-size: 1.35rem; font-weight: 600; color: #e0e0e0; margin-bottom: 32px; letter-spacing: 0.02em; } /* Gauge ring */ .gauge-wrap { position: relative; width: 160px; height: 160px; margin: 0 auto 32px; } .gauge-wrap svg { transform: rotate(-90deg); width: 160px; height: 160px; } .gauge-bg { fill: none; stroke: rgba(255,255,255,0.08); stroke-width: 12; } .gauge-fill { fill: none; stroke: url(#gaugeGrad); stroke-width: 12; stroke-linecap: round; stroke-dasharray: 440; stroke-dashoffset: 440; transition: stroke-dashoffset 0.6s ease; } .gauge-center { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; } .gauge-value { font-size: 2.4rem; font-weight: 700; color: #fff; line-height: 1; } .gauge-unit { font-size: 0.75rem; color: rgba(255,255,255,0.5); margin-top: 4px; letter-spacing: 0.08em; } /* Progress bar */ .bar-wrap { background: rgba(255,255,255,0.08); border-radius: 999px; height: 8px; overflow: hidden; margin-bottom: 10px; } .bar-fill { height: 100%; width: 0%; border-radius: 999px; background: linear-gradient(90deg, #00bcd4, #7c4dff); transition: width 0.6s ease; } .bar-labels { display: flex; justify-content: space-between; font-size: 0.72rem; color: rgba(255,255,255,0.4); margin-bottom: 28px; } /* Voltage row */ .voltage-row { display: flex; align-items: center; justify-content: center; gap: 8px; font-size: 0.9rem; color: rgba(255,255,255,0.6); } .voltage-val { font-size: 1.05rem; font-weight: 600; color: #80cbc4; } /* Status dot */ .status { display: flex; align-items: center; justify-content: center; gap: 6px; margin-top: 28px; font-size: 0.75rem; color: rgba(255,255,255,0.35); letter-spacing: 0.06em; text-transform: uppercase; } .dot { width: 7px; height: 7px; border-radius: 50%; background: #4caf50; animation: pulse 2s infinite; } .dot.error { background: #f44336; animation: none; } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } } </style> </head> <body> <div class="card"> <span class="pin-badge">Pin A0</span> <h1>Analog Sensor</h1> <div class="gauge-wrap"> <svg viewBox="0 0 160 160"> <defs> <linearGradient id="gaugeGrad" x1="0%" y1="0%" x2="100%" y2="0%"> <stop offset="0%" stop-color="#00bcd4" /> <stop offset="100%" stop-color="#7c4dff" /> </linearGradient> </defs> <circle class="gauge-bg" cx="80" cy="80" r="70" /> <circle class="gauge-fill" id="gaugeFill" cx="80" cy="80" r="70" /> </svg> <div class="gauge-center"> <span class="gauge-value" id="val">--</span> <span class="gauge-unit">/ 1023</span> </div> </div> <div class="bar-wrap"> <div class="bar-fill" id="bar"></div> </div> <div class="bar-labels"><span>0</span><span>1023</span></div> <div class="voltage-row"> Voltage: <span class="voltage-val" id="volt">--</span> V </div> <div class="status"> <div class="dot" id="dot"></div> <span id="statusText">connecting…</span> </div> </div> <script> var CIRCUMFERENCE = 2 * Math.PI * 70; // ~439.8 function refresh() { fetch('/api/analog') .then(function(r) { return r.json(); }) .then(function(d) { var raw = d.value; var pct = raw / 1023; document.getElementById('val').textContent = raw; document.getElementById('bar').style.width = (pct * 100).toFixed(1) + '%'; document.getElementById('gaugeFill').style.strokeDashoffset = (CIRCUMFERENCE * (1 - pct)).toFixed(2); document.getElementById('volt').textContent = (pct * 5).toFixed(2); document.getElementById('dot').className = 'dot'; document.getElementById('statusText').textContent = 'live'; }) .catch(function() { document.getElementById('dot').className = 'dot error'; document.getElementById('statusText').textContent = 'disconnected'; }); } setInterval(refresh, 1000); refresh(); </script> </body> </html>

Detailed Instructions

  • Follow the same App creation steps as Example 1, but name the App WebServerAssets.
  • Paste the MCU code into sketch/sketch.ino.
  • Paste the Python code into python/main.py (replace all existing content).
  • In the assets/ folder, open (or create) index.html and paste the HTML code above.
  • Add the WebUI - HTML Brick as in Example 1.
  • Click Run to upload.
  • Open http://<ARDUINO_UNO_Q_IP>:7000/ in your browser.
  • The page loads the styled web page from the assets folder and updates the analog value automatically every second.
  • Turn the potentiometer — the value on the page changes without refreshing.

App Lab Console Output

DIYables_Apps
Stop
sketch.ino
1#include "Arduino_RouterBridge.h"
Serial Monitor
Python
Message (Enter to send a message to "Newbiely" on usb(2820070321))
New Line
9600 baud
[2026-05-08 09:00:01] Bridge ready
DIYables_Apps
Stop
sketch.ino
1#include "Arduino_RouterBridge.h"
Serial Monitor
Python
[2026-05-08 09:00:02] WebUI started on http://0.0.0.0:7000 [2026-05-08 09:00:02] WebUI started on http://192.168.0.45:7000

API Response

The JavaScript in index.html calls GET /api/analog. You can also test this endpoint directly:

{"pin": "A0", "value": 512}
Arduino UNO Q Web Server Assets Page

Web Server as API (Pure JSON)

This example strips away all HTML and serves only a JSON API endpoint. Any client — browser, mobile app, curl, Postman, or another microcontroller — can call GET /api/analog to receive the current analog reading as structured data.

Use this pattern when you want to:

  • Integrate Arduino UNO Q data into an existing web app or dashboard
  • Query the board from a mobile app or back-end service
  • Test or log sensor values from the command line

MCU Code

/* * This Arduino UNO Q code was developed by newbiely.com * * This Arduino UNO Q code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/arduino-uno-q/arduino-uno-q-web-server */ #include "Arduino_RouterBridge.h" String get_analog(String arg) { int value = analogRead(A0); return String(value); } void setup() { Monitor.begin(); Bridge.begin(); Bridge.provide_safe("get_analog", get_analog); Monitor.println("Bridge ready"); } void loop() { }

Python Code

""" This Arduino UNO Q script was developed by newbiely.com This Arduino UNO Q script is made available for public use without any restriction For comprehensive instructions and wiring diagrams, please visit: https://newbiely.com/tutorials/arduino-uno-q/arduino-uno-q-web-server """ from arduino.app_utils import * from arduino.app_bricks.web_ui import WebUI def get_analog(): value = Bridge.call("get_analog", "") return {"pin": "A0", "value": int(value)} web_ui = WebUI() web_ui.expose_api("GET", "/api/analog", get_analog) App.run()

Detailed Instructions

  • Follow the same App creation steps as Example 1, but name the App WebServerAPI.
  • Paste the MCU code into sketch/sketch.ino.
  • Paste the Python code into python/main.py (replace all existing content).
  • Add the WebUI - HTML Brick as in Example 1.
  • Click Run to upload.
  • Test the endpoint from your browser
Arduino UNO Q Web Server API Response
  • Test the endpoint from your terminal:
GET http://ARDUINO_UNO_Q_IP:7000/api/analog
  • Test the endpoint from from any client. For example, with curl:
curl http://:7000/api/analog

API Response

{"pin": "A0", "value": 512}

App Lab Console Output

DIYables_Apps
Stop
sketch.ino
1#include "Arduino_RouterBridge.h"
Serial Monitor
Python
Message (Enter to send a message to "Newbiely" on usb(2820070321))
New Line
9600 baud
[2026-05-08 09:00:01] Bridge ready
DIYables_Apps
Stop
sketch.ino
1#include "Arduino_RouterBridge.h"
Serial Monitor
Python
[2026-05-08 09:00:02] WebUI started on http://0.0.0.0:7000 [2026-05-08 09:00:02] WebUI started on http://192.168.0.45:7000

Separating HTML from Python Code

With the WebUI approach, your Python code only exposes API endpoints — it does not serve HTML. This is a clean separation by design:

  • Python WebUI script — exposes JSON API endpoints (e.g. /api/status, /api/led/on)
  • HTML file — a standalone file you open in the browser. Its JavaScript fetches data from the API.

Here is the Python script (same as Example 1 — no changes needed):

""" This Arduino UNO Q script was developed by newbiely.com This Arduino UNO Q script is made available for public use without any restriction For comprehensive instructions and wiring diagrams, please visit: https://newbiely.com/tutorials/arduino-uno-q/arduino-uno-q-web-server """ from arduino.app_utils import * from arduino.app_bricks.web_ui import WebUI def get_status(): uptime = Bridge.call("get_uptime", "") led = Bridge.call("get_led_state", "") return {"uptime": uptime, "led": led} web_ui = WebUI() web_ui.expose_api("GET", "/api/status", get_status) App.run()

Here is the standalone HTML page (index.html) that fetches from the API:

""" This Arduino UNO Q script was developed by newbiely.com This Arduino UNO Q script is made available for public use without any restriction For comprehensive instructions and wiring diagrams, please visit: https://newbiely.com/tutorials/arduino-uno-q/arduino-uno-q-web-server """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Arduino UNO Q Web Server</title> <style> body { font-family: Arial, sans-serif; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; margin: 0; background: #1a1a2e; color: #eee; } h1 { font-size: 1.8rem; margin-bottom: 32px; } .card { background: #16213e; border-radius: 12px; padding: 32px 48px; box-shadow: 0 4px 24px rgba(0,0,0,0.4); text-align: center; min-width: 280px; } .label { font-size: 0.9rem; color: #aaa; margin-bottom: 4px; } .value { font-size: 2rem; font-weight: bold; color: #e94560; margin-bottom: 24px; } .led-on { color: #4caf50; } .led-off { color: #aaa; } </style> </head> <body> <h1>Arduino UNO Q Web Server</h1> <div class="card"> <p class="label">MCU Uptime (seconds)</p> <p class="value" id="uptime">--</p> <p class="label">Built-in LED</p> <p class="value" id="ledState">--</p> </div> <script> // Update this IP address to match your Arduino UNO Q const API_BASE = 'http://192.168.0.3:7000'; function update() { fetch(API_BASE + '/api/status') .then(r => r.json()) .then(data => { document.getElementById('uptime').textContent = data.uptime; const ledEl = document.getElementById('ledState'); ledEl.textContent = data.led; ledEl.className = 'value ' + (data.led === 'ON' ? 'led-on' : 'led-off'); }) .catch(e => console.error(e)); } setInterval(update, 2000); update(); </script> </body> </html>

※ NOTE THAT:

Open index.html directly in your browser (from your computer) or host it anywhere. The JavaScript inside fetches http://<ARDUINO_UNO_Q_IP>:7000/api/status every two seconds. Update the IP address in the HTML file to match your board.

Arduino UNO Q Web Server — Multiple Pages

To build a web server with multiple API endpoints (e.g., /api/status, /api/led/on, /api/settings), add one expose_api call per endpoint in the Python WebUI script.

For a detailed guide, see the Arduino UNO Q - Web Server Multiple Pages tutorial.

OpenClaw

You can adapt the OpenClaw to this tutorial by refering the instruction on Arduino Uno Q - OpenClaw Tutorial

Project Ideas

The web server is a building block for many useful projects on Arduino UNO Q:

  • Sensor Dashboard: Connect a DHT22 or BMP280 to the MCU and display temperature, humidity, and pressure on a styled web page that auto-refreshes every few seconds
  • Relay Control Panel: Add buttons on the web page to switch relays on and off — useful for controlling lights, fans, or pumps from a phone while at home
  • Data Logger Viewer: Log sensor readings to a CSV file on the Linux side and add a /history endpoint that returns the last 100 readings as an HTML table
  • Garage Door Opener: Expose a "trigger" Bridge function that pulses a relay for one second — add a single big button on the web page to open or close the garage door from your phone
  • Plant Watering Monitor: Read a soil moisture sensor on the MCU, display the moisture level on the web page with a progress bar, and show a warning banner when the soil is too dry

Challenge Yourself

Ready to go further with the web server on Arduino UNO Q? Try these challenges:

  • Easy: Create a styled index.html file on your computer that fetches from http://<ARDUINO_UNO_Q_IP>:7000/api/status and displays uptime and LED state with a clean layout using Bootstrap or custom CSS.
  • Medium: Add a /api/history endpoint to the Python WebUI script that reads the last 10 Bridge values from an in-memory list and returns them as a JSON array — then display the history on the HTML page as a table.
  • Advanced: Add a /api/led/toggle POST endpoint to the WebUI script that reads the current LED state via Bridge and calls either led_on or led_off to flip it — then add a single toggle button to the HTML page.

Learn More

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!