Interfacing

Before getting started, I found and read over this forum for networking in Unity, this tutorial for setting up a RESTful API on a Raspberry Pi Pico W, this forum for sending GET requests in Unity. Along the way I also encountered this forum for debugging networking issues in MicroPython as well as this post for fixing hotspot connectivity issues with the Raspberry Pi Pico W.

I first modified the server code from this website to the following...

import network
import socket
import time

from machine import Pin

led = Pin(15, Pin.OUT)

ssid = **SSID HERE**
password = **PASSWORD HERE**

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

html = """<!DOCTYPE html>
<html>
    <head> <title>Pico W</title> </head>
    <body> <h1>Pico W</h1>
        <p>%s</p>
    </body>
</html>
"""

max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    time.sleep(1)

if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('client connected from', addr)
        request = cl.recv(1024)
        print(request)

        request = str(request)
        print(request)
        led_on = request.find('/light/on')
        led_off = request.find('/light/off')
        print( 'led on = ' + str(led_on))
        print( 'led off = ' + str(led_off))

        #if led_on == 6:
        #    print("led on")
        #    led.value(1)
        #    stateis = "LED is ON"

        #if led_off == 6:
        #    print("led off")
        #    led.value(0)
        #    stateis = "LED is OFF"

        response = html % stateis

        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

Which gave this output.

waiting for connection...
waiting for connection...
waiting for connection...
waiting for connection...
waiting for connection...
connected
ip = 172.20.10.5
listening on ('0.0.0.0', 80)

It worked! Then I wrote this unity script based off of this website.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class CommunicationManager : MonoBehaviour
{
    public bool ToggleLED()
    {
        StartCoroutine(GetRequest("http://172.20.10.5"))
    }

    IEnumerator GetRequest(string uri)
    {
        using (UnityWebRequest webRequest = UnityWebRequest.Get(uri))
        {
            // Request and wait for the desired page.
            yield return webRequest.SendWebRequest();

            string[] pages = uri.Split('/');
            int page = pages.Length - 1;

            switch (webRequest.result)
            {
                case UnityWebRequest.Result.ConnectionError:
                case UnityWebRequest.Result.DataProcessingError:
                    Debug.LogError(pages[page] + ": Error: " + webRequest.error);
                    break;
                case UnityWebRequest.Result.ProtocolError:
                    Debug.LogError(pages[page] + ": HTTP Error: " + webRequest.error);
                    break;
                case UnityWebRequest.Result.Success:
                    Debug.Log(pages[page] + ":\nReceived: " + webRequest.downloadHandler.text);
                    break;
            }
        }
    }
}

My hotspot didn't work - David Tian let me try his, and it worked first try!

Modified Python Code

import network
import socket
import time

from machine import Pin

led = Pin(15, Pin.OUT)

ssid = **HOTSPOT SSID HERE**
password = **HOTSPOT PASSWORD HERE**

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

html = """<!DOCTYPE html>
<html>
    <head> <title>Pico W</title> </head>
    <body> <h1>Pico W</h1>
        <p>%s</p>
    </body>
</html>
"""

max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    time.sleep(1)

if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('client connected from', addr)
        request = cl.recv(1024)
        print(request)

        request = str(request)
        print(request)
        led_on = request.find('/light/on')
        led_off = request.find('/light/off')
        print( 'led on = ' + str(led_on))
        print( 'led off = ' + str(led_off))

        #if led_on == 6:
        #    print("led on")
        #    led.value(1)
        #    stateis = "LED is ON"

        #if led_off == 6:
        #    print("led off")
        #    led.value(0)
        #    stateis = "LED is OFF"

        response = html % "Recieved"

        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

Tried AP instead of STA to see if the website could be accessed from different networks, but I then wasn't able to connect to it from any network.

Also noticed that sometimes even when the connection is broken Thonny, the output won't change, so I've learned that sometimes it's necessary to rerun the server code if nothing is working.

One of my biggest learnings was that, when Wi-Fi doesn't work, to run this in Thonny terminal.

import machine;machine.reset()

Also, sometimes Thonny will output multiple red error messages and the UI will freeze, but if I wait for a minute, it will return to normal and the Wi-Fi will connect.

I also believe re-running Thonny may change the IP address, requiring it to be re-entered into the Unity code.

Here's a video demonstrating the wireless communication between Unity and the Pico (notice the output in Thonny changing once the button is pressed, revealing that a GET request was recieved and data was sent back).

New Pico code:

import network
import socket
import time

from machine import Pin

led = Pin(15, Pin.OUT)

ssid = **SSID HERE**
password = **PASSWORD HERE**

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

html = """<!DOCTYPE html>
<html>
    <head> <title>Pico W</title> </head>
    <body> <h1>Pico W</h1>
        <p>%s</p>
    </body>
</html>
"""

max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    time.sleep(1)

if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('client connected from', addr)
        request = cl.recv(1024)
        request = str(request.decode())
        print(request)

        #if led_on == 6:
        #    print("led on")
        #    led.value(1)
        #    stateis = "LED is ON"

        #if led_off == 6:
        #    print("led off")
        #    led.value(0)
        #    stateis = "LED is OFF"

        response = html % "Recieved"

        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

This was the output.

client connected from ('172.20.10.8', 62113)
GET / HTTP/1.1
Host: 172.20.10.7
User-Agent: UnityPlayer/2021.3.14f1 (UnityWebRequest/1.0, libcurl/7.84.0-DEV)
Accept: */*
Accept-Encoding: deflate, gzip
X-Unity-Version: 2021.3.14f1

I tried ran type() on the request variable and also got string, so I ran this code to extract the url-path, but I'm sure this isn't a good way to do it for my final project.

import network
import socket
import time

from machine import Pin

led = Pin(15, Pin.OUT)

ssid = **SSID HERE**
password = **PASSWORD HERE**

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

html = """<!DOCTYPE html>
<html>
    <head> <title>Pico W</title> </head>
    <body> <h1>Pico W</h1>
        <p>%s</p>
    </body>
</html>
"""

max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    time.sleep(1)

if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('listening on', addr)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('client connected from', addr)
        request = cl.recv(1024)
        request = request.decode()
        print(request)

        request_path = request[request.find("GET")+3:request.find("HTTP")]

        print(request_path)

        #if led_on == 6:
        #    print("led on")
        #    led.value(1)
        #    stateis = "LED is ON"

        #if led_off == 6:
        #    print("led off")
        #    led.value(0)
        #    stateis = "LED is OFF"

        response = html % "Recieved"

        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

It worked and had an output of / when I ran the same C# code in Unity.

Now I updated the unity code and added an input field for the IP address (I imagine it will display on the e-paper display for the actual final project).

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using TMPro;

public class CommunicationManager : MonoBehaviour
{
    public TMP_InputField inputField;

    public void LEDOn()
    {
        SendRequest("on");
    }

    public void LEDOff()
    {
        SendRequest("off");
    }

    public void SendRequest(string data)
    {
        StartCoroutine(GetRequest($"http://{inputField.text}/{data}"));
    }

    IEnumerator GetRequest(string uri)
    {
        using (UnityWebRequest webRequest = UnityWebRequest.Get(uri))
        {
            // Request and wait for the desired page.
            yield return webRequest.SendWebRequest();

            string[] pages = uri.Split('/');
            int page = pages.Length - 1;

            switch (webRequest.result)
            {
                case UnityWebRequest.Result.ConnectionError:
                case UnityWebRequest.Result.DataProcessingError:
                    Debug.LogError(pages[page] + ": Error: " + webRequest.error);
                    break;
                case UnityWebRequest.Result.ProtocolError:
                    Debug.LogError(pages[page] + ": HTTP Error: " + webRequest.error);
                    break;
                case UnityWebRequest.Result.Success:
                    Debug.Log(pages[page] + ":\nReceived: " + webRequest.downloadHandler.text);
                    break;
            }
        }
    }
}

I also added two different buttons instead of one for toggling.

Here's the updated MicroPython code.

import network
import socket
import time

from machine import Pin

led = Pin(15, Pin.OUT)

ssid = **SSID HERE**
password = **PASSWORD HERE**

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

html = """<!DOCTYPE html>
<html>
    <head> <title>Pico W</title> </head>
    <body> <h1>Pico W</h1>
        <p>%s</p>
    </body>
</html>
"""

max_wait = 10
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    time.sleep(1)

if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]

s = socket.socket()
s.bind(addr)
s.listen(1)

print('listening on', addr)

on_board_led = Pin("LED", Pin.OUT)

# Listen for connections
while True:
    try:
        cl, addr = s.accept()
        print('client connected from', addr)
        request = cl.recv(1024)
        request = request.decode()
        print(request)

        request_path = request[request.find("GET")+3:request.find("HTTP")].strip()

        if "on" in request_path:
            on_board_led.on()
        elif "off" in request_path:
            on_board_led.off()

        print(request_path)

        #if led_on == 6:
        #    print("led on")
        #    led.value(1)
        #    stateis = "LED is ON"

        #if led_off == 6:
        #    print("led off")
        #    led.value(0)
        #    stateis = "LED is OFF"

        response = html % "Recieved"

        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        cl.close()
        print('connection closed')

In this video, I toggle the LED on and off as well as change the IP address (which causes it to fail) to prove that it is actually using the given IP.