Daniel D'Souza
Published © GPL3+

Control Arduino UNO Q via Smartphone Web Dashboard

Host a wireless web dashboard on the Arduino UNO Q's Linux side to control the STM32 MCU and view stats from your phone. No PC requi

IntermediateFull instructions provided1 hour4
Control Arduino UNO Q via Smartphone Web Dashboard

Things used in this project

Hardware components

Arduino UNO Q
Arduino UNO Q
×1
USB Cable, USB Type C Plug
USB Cable, USB Type C Plug
×1
Samsung smartphone
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

python.microbit.org
micro:bit python.microbit.org

Story

Read more

Code

STM32 MCU Firmware (The Muscle)

Arduino
Upload this to the STM32 side via the Arduino IDE. It listens for serial commands from the Linux MPU.
/*
 * Arduino UNO Q - STM32 Side Firmware
 * Listens for serial commands from the Linux MPU to control hardware.
 */

char command;

void setup() {
  // Initialize serial communication with the Linux MPU
  Serial.begin(115200); 
  pinMode(LED_BUILTIN, OUTPUT);
  
  // Note: You can expand this to control the 8x13 LED matrix 
  // or Qwiic modules using their respective libraries!
}

void loop() {
  // Listen for commands from the Linux Web Server
  if (Serial.available() > 0) {
    command = Serial.read();
    if (command == '1') {
      digitalWrite(LED_BUILTIN, HIGH);
      Serial.println("STM32: Hardware ON");
    } 
    else if (command == '0') {
      digitalWrite(LED_BUILTIN, LOW);
      Serial.println("STM32: Hardware OFF");
    }
  }
  
  // Send a heartbeat to Linux every 5 seconds so the dashboard knows we are alive
  static unsigned long lastBeat = 0;
  if (millis() - lastBeat > 5000) {
    Serial.println("STM32: Heartbeat OK");
    lastBeat = millis();
  }
}

Linux MPU Web Server (The Brain)

Python
Save this as dashboard.py on the Linux Debian side of the UNO Q. It hosts the web interface and bridges the gap between your phone and the STM32.
# Arduino UNO Q - Linux MPU Web Dashboard
from flask import Flask, render_template_string
import serial
import time

# Connect to the STM32 MCU via the internal serial port.
# NOTE: The exact port path (e.g., /dev/ttySTM32, /dev/ttyS1) may vary 
# depending on your specific Debian image. Check your UNO Q documentation.
ser = serial.Serial('/dev/ttySTM32', 115200, timeout=1)
time.sleep(2) # Wait for STM32 to initialize

app = Flask(__name__)

# Clean, mobile-friendly HTML Dashboard for the smartphone
HTML_DASHBOARD = """
<!DOCTYPE html>
<html>
<head>
    <title>UNO Q Dashboard</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        body { font-family: Arial, text-align: center; margin-top: 50px; background-color: #f4f4f9; }
        .btn { padding: 20px 40px; font-size: 24px; margin: 10px; border: none; border-radius: 8px; cursor: pointer; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
        .on { background-color: #28a745; color: white; }
        .off { background-color: #dc3545; color: white; }
        h1 { color: #008184; } /* Arduino Teal */
        .status { font-size: 18px; color: #555; margin-top: 20px; font-style: italic; }
    </style>
</head>
<body>
    <h1>Arduino UNO Q Control</h1>
    <p>Dual-Brain Wireless Dashboard</p>
    <form action="/on" method="POST"><button class="btn on">TURN ON</button></form>
    <form action="/off" method="POST"><button class="btn off">TURN OFF</button></form>
    <p class="status"><strong>MCU Status:</strong> {{ status }}</p>
</body>
</html>
"""

status_msg = "Waiting for data..."

@app.route('/')
def index():
    global status_msg
    # Read any pending messages from STM32 (like the heartbeat)
    if ser.in_waiting > 0:
        status_msg = ser.readline().decode('utf-8').strip()
    return render_template_string(HTML_DASHBOARD, status=status_msg)

@app.route('/on', methods=['POST'])
def turn_on():
    ser.write(b'1')
    return "<script>window.location.href='/'</script>"

@app.route('/off', methods=['POST'])
def turn_off():
    ser.write(b'0')
    return "<script>window.location.href='/'</script>"

if __name__ == '__main__':
    # Run on all interfaces (0.0.0.0) so the smartphone can access it over Wi-Fi
    app.run(host='0.0.0.0', port=5000, debug=True)

Credits

Daniel D'Souza
4 projects • 0 followers
Embedded systems & robotics engineer with 9+ yrs in IoT & hardware, building practical projects and hands-on STEM learning

Comments