Part 5: Building a Wi-Fi Control Interface for Remote Control Robot

Part 5: Building a Wi-Fi Control Interface

Welcome to Part 5 of our Raspberry Pi Pico W Robotics Course. Today, you’ll learn how to create a robust Wi-Fi control interface to manage your robot remotely using your smartphone or laptop.

Overview

This tutorial covers everything you need to turn your Pico W into a smart, remotely controlled robot. In this guide, you will:

  • Set up a lightweight web server on your Pico W.
  • Create an interactive web interface to control motor movements.
  • Integrate ultrasonic obstacle detection with an automated emergency stop.
  • Coordinate asynchronous tasks to manage Wi-Fi, sensor data, and motor commands.

The complete Python script below combines Wi-Fi connectivity, sensor readings, Neopixel feedback, and motor control—all in one integrated block. Simply paste this modified HTML into Shopify, and you’re ready to go!

Complete Python Code


import network
import socket
import uasyncio
import time
import random
from machine import Pin, PWM, time_pulse_us

### You need neopixel.py in the same folder
from neopixel import Neopixel

### config.py should define something like: password = "mywifi_password"
import config

##############################################################################
# 0) Configuration
##############################################################################

SSID = "SMA"
PASSWORD = config.password

# For controlling the on-board LED
led = Pin("LED", Pin.OUT)

# Create global wlan object
wlan = network.WLAN(network.STA_IF)
wlan.active(True)

##############################################################################
# 1) Wi-Fi Connection Helpers
##############################################################################

def wifi_connect():
    """
    Continuously retry Wi-Fi connection until successful.
    """
    global wlan
    
    attempt = 0
    while True:
        attempt += 1
        
        if not wlan.isconnected():
            print(f"Attempt {attempt}: Connecting to Wi-Fi...")
            wlan.connect(SSID, PASSWORD)
            
            # Wait up to 10 seconds in small increments to see if it connects
            max_wait = 10
            while max_wait > 0:
                if wlan.isconnected():
                    print("Connected! Network config:", wlan.ifconfig())
                    return  # Exit the function when connected
                max_wait -= 1
                time.sleep(1)
            
            # If still not connected after 10s, wait a bit before retrying
            print("Failed to connect. Retrying in 5 seconds...")
            time.sleep(5)
        else:
            # Already connected
            print("Already connected! Network config:", wlan.ifconfig())
            return

async def wifi_monitor_task():
    """
    Periodically checks if the Wi-Fi is still connected.
    If not, it attempts to reconnect.
    """
    while True:
        if not wlan.isconnected():
            print("Wi-Fi dropped; reconnecting...")
            try:
                wifi_connect()
            except RuntimeError as e:
                print("Reconnect failed:", e)
        await uasyncio.sleep(10)

##############################################################################
# 2) Ultrasonic Sensor Setup
##############################################################################

SOUND_SPEED = 340  # m/s
TRIG_PULSE_DURATION_US = 10
TRIG_PIN = Pin(15, Pin.OUT)
ECHO_PIN = Pin(14, Pin.IN)

def get_distance_cm():
    """
    Returns distance in centimeters. Negative if reading fails.
    """
    TRIG_PIN.value(0)
    time.sleep_us(5)
    TRIG_PIN.value(1)
    time.sleep_us(TRIG_PULSE_DURATION_US)
    TRIG_PIN.value(0)

    duration = time_pulse_us(ECHO_PIN, 1, 30000)  # 30 ms max
    if duration < 0:
        # time_pulse_us returns -2 if timed out waiting for pin,
        # or -1 if timed out in the measurement
        return -1

    # Convert microseconds to distance in cm:
    distance_cm = SOUND_SPEED * duration / 20000
    return distance_cm

##############################################################################
# 3) Neopixel Setup
##############################################################################

NUMPIX = 6
PIXELS = Neopixel(NUMPIX, 0, 28)

# Define LED colors
YELLOW = (255, 100, 0)
ORANGE = (255, 50, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
RED = (255, 0, 0)
LAVENDER = (230, 230, 250)

def update_neopixels_with_distance(dist):
    """
    Lights up only the 1st and 4th neopixels and reduces brightness by 50%.
    """
    if dist < 0:
        color = (0, 0, 128)  # Dim BLUE
    elif dist < 10:
        color = (128, 0, 0)  # Dim RED
    elif dist < 20:
        color = (128, 50, 0)  # Dim YELLOW
    else:
        color = (0, 128, 0)  # Dim GREEN

    PIXELS.clear()  # Turn off all pixels first

    # Light up only the 1st (index 0) and 4th (index 3) NeoPixels
    PIXELS.set_pixel(0, color)
    PIXELS.set_pixel(3, color)

    PIXELS.show()

##############################################################################
# 4) Motor Setup
##############################################################################

in1 = Pin(0, Pin.OUT)
in2 = Pin(1, Pin.OUT)
ena = PWM(Pin(8))
ena.freq(1000)

in3 = Pin(2, Pin.OUT)
in4 = Pin(3, Pin.OUT)
enb = PWM(Pin(9))
enb.freq(1000)

current_duty = 0

def set_speed(percent):
    """
    percent: integer 0..100
    """
    global current_duty
    duty = int(65535 * (percent / 100))
    current_duty = duty
    ena.duty_u16(duty)
    enb.duty_u16(duty)

def stop_motors():
    in1.value(0)
    in2.value(0)
    in3.value(0)
    in4.value(0)

def forward():
    in1.value(1)
    in2.value(0)
    in3.value(1)
    in4.value(0)

def backward():
    in1.value(0)
    in2.value(1)
    in3.value(0)
    in4.value(1)

def left():
    in1.value(0)
    in2.value(1)
    in3.value(1)
    in4.value(0)

def right():
    in1.value(1)
    in2.value(0)
    in3.value(0)
    in4.value(1)

##############################################################################
# 5) Global State for Emergency Stop
##############################################################################

emergency_stop = False  # True if distance <10 cm
DIRECTION = "FORWARD"

##############################################################################
# 6) Web Page Generator
##############################################################################

def web_page():
    speed_percent = int((current_duty / 65535) * 100)
    html = f"""



  
  



  

  

Pico W Robot Control


  

Current Speed: {speed_percent}%



  


  



"""
    return html

##############################################################################
# 7) Asynchronous Tasks
##############################################################################

async def measure_distance_task():
    """
    Continuously measure distance and update neopixels.
    If distance <10 cm, set emergency_stop = True and stop motors (FORWARD).
    """
    global emergency_stop
    global DIRECTION

    while True:
        dist_cm = get_distance_cm()
        update_neopixels_with_distance(dist_cm)

        # If distance < 10 and not negative => too close => emergency stop
        if dist_cm >= 0 and dist_cm < 10 and DIRECTION == "FORWARD":
            emergency_stop = True
            stop_motors()
        else:
            emergency_stop = False

        print("Distance:", dist_cm, "cm | EMERGENCY:", emergency_stop)
        await uasyncio.sleep(0.1)

async def handle_client(reader, writer):
    """
    Handle one HTTP client request (non-blocking).
    """
    global emergency_stop
    global DIRECTION

    try:
        request = await reader.read(1024)
    except Exception as e:
        print("Error reading request:", e)
        return

    request_str = request.decode() if request else ""
    # Speed settings
    if "/?speed=25" in request_str:
        set_speed(25)
    elif "/?speed=50" in request_str:
        set_speed(50)
    elif "/?speed=75" in request_str:
        set_speed(75)
    elif "/?speed=100" in request_str:
        set_speed(100)

    # Direction logic
    if "/?action=backward" in request_str:
        DIRECTION = "BACKWARD"
    elif "/?action=forward" in request_str:
        DIRECTION = "FORWARD"

    # Movement only if no emergency stop
    if not emergency_stop:
        if "/?action=backward" in request_str:
            backward()
        elif "/?action=forward" in request_str:
            forward()
        elif "/?action=left" in request_str:
            left()
        elif "/?action=right" in request_str:
            right()
        elif "/?action=stop" in request_str:
            stop_motors()
    else:
        stop_motors()

    # (Optional) measure distance again for fresh reading
    dist_cm = get_distance_cm()
    update_neopixels_with_distance(dist_cm)

    # Send response
    resp = web_page()
    writer.write("HTTP/1.1 200 OK\r\n")
    writer.write("Content-Type: text/html\r\n")
    writer.write("Connection: close\r\n\r\n")
    writer.write(resp)
    await writer.drain()
    await writer.wait_closed()

async def webserver_task():
    """
    Start the uasyncio server on port 80.
    """
    server = await uasyncio.start_server(handle_client, "0.0.0.0", 80)
    print("Web server listening on http://{}:80".format(wlan.ifconfig()[0]))
    return server

##############################################################################
# 8) Main Entry Point
##############################################################################

async def main():
    # 1) Ensure we are connected to Wi-Fi at startup
    wifi_connect()
    # 2) Create Wi-Fi monitor task to reconnect if dropped
    uasyncio.create_task(wifi_monitor_task())
    # 3) Start distance measure task
    uasyncio.create_task(measure_distance_task())
    # 4) Start webserver
    server = await webserver_task()

    # 5) Simple blink in a loop
    while True:
        led.value(1)
        await uasyncio.sleep(0.1)  # Non-blocking
        led.value(0)
        await uasyncio.sleep(1)

import sys

try:
    uasyncio.run(main())
except Exception as e:
    # Open (or create) error.log in append mode
    with open("error.log", "a") as f:
        # Print a full traceback to the file
        sys.print_exception(e, f)
    print("An error occurred and has been logged to error.log.")
      

This comprehensive script sets up a robust control system for your Pico W robot by integrating Wi-Fi connectivity, sensor data, and motor control into one asynchronous application.

Conclusion

Congratulations on advancing your robotics project! With this control interface, your Pico W robot is now equipped for remote operation, obstacle detection, and real-time feedback. Stay tuned for more innovative projects and tutorials.

Create a free account to access full content.

All access to code and resources on ShillehTek.

Signup Now

Already a member? Sign In