Raspberry Pi Pico W as a Web Server
After the launch of the Raspberry PI Pico W on 30th Jun 2022, the first thing that we could think of to do with this great new little gadget was? Upload sensor data using the Raspberry Pi Pico W as a web server. And why Not?
The final aim of the project would be to start connecting to larger systems like Home Assistant. But first is first: Getting familiar with the IOT concepts used by the Pico W.
Hardware
We have designed a new kit to get you started with everything you need to start uploading your first sensor.
Pico W DHT22 kit (Wire colors may vary)
The kit contains:
Connection
DHT | RP Pico W |
---|---|
GND | GND |
VCC | 3.3V |
Signal | GP2 |
Software
We can start by ensuring that you have setup your RP Pico W for micro-python. If you have not done so yet, head over to our blog post Getting started with Raspberry Pi Pico before continuing here. Once you have set up your Pico and wired up your sensor we can look at the code. I will look at the DHT22 in this guide.
First we need to ensure that the Required libraries are installed in Thonny. Following a great guide provided by the Raspberry Pi Foundation: Installing Python packages. We can ensure any package not installed by default is now installed.
This can be done by selecting tools – >> manage plugins
This will bring up a Pip window:
Now we can simply search for all the packages used in our script: network, dht and machine
After searching and selecting the package, simply click on install and watch the magic happen.
The Code
Now that Thonny is fully setup and ready for use, let’s turn our attention to the code. Starting with displaying sensor data on a web page so to get an idea of how the Pico works.
# import required libraries
import network
import socket
import dht
from time import sleep
from machine import Pin
First importing all the libraries. The network module is used to configure the WiFi connection. There are two WiFi interfaces, one for the station (when the Pico W connects to a router) and one for the access point (for other devices to connect to the Pico W). A network socket is an endpoint of an interprocess communication across a computer network. The Micro-Python Standard Library has a module called socket which provides a low-level internet networking interface.
DHT (Digital Humidity & Temperature) sensors are low cost digital sensors with capacitive humidity sensors and thermistors to measure the surrounding air. They feature a chip that handles analog to digital conversion and provide a 1-wire interface. The DHT11 (blue) and DHT22 (white) sensors provide the same 1-wire interface, however, the DHT22 requires a separate object as it has more complex calculation. We chose this option as we found a much greater accuracy on them that their blue counterpart.
Finally we add sleep for some delays in our code and Pin to interact with the physical GPIO pins on the Pico.
# Pin setup
intled = machine.Pin("LED", machine.Pin.OUT)
sensor = dht.DHT22(Pin(2))
# turn the onboard LED ON to indicate startup
intled.value(1)
# wait for 3 seconds
sleep(3)
# turn the onboard LED OFF
intled.value(0)
The next step is to initialize the GPIO pins that we will be using. The internal LED has a location called “LED” and this will be setup as an output pin. I placed my sensor reading wire on GP2. We don’t need to do any setup for the DHT sensor because the library that is used here does all of that for us. Then I used the internal LED to just give some indication that the Pico has started running the code and is alive.
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# prevent the wireless chip from
# activating power-saving mode when it is idle
wlan.config(pm = 0xa11140)
# set a static IP address for Pico
# your router IP could be very different eg:
# 192.168.1.1
wlan.ifconfig(('10.0.0.94', '255.255.255.0', '10.0.0.2', '8.8.8.8'))
# enter your wifi "SSID" and "PASSWORD"
wlan.connect("mySSID", "myPASSWORD")
Next it’s time to setup the WiFi connection using the network module installed earlier. Here we are using the protocol to setup the Pico to connect to a router. So you can configure the module to connect to your own network using the STA_IF interface, and activate it. By default the wireless chip will active power-saving mode when it is idle, which might lead it to being less responsive. If you are running a server or need more responsiveness, you can change this by toggling the power mode. If you want to know more you can have a look through the datasheet. We want to ensure that the Pico always has full connectivity, so we toggle it off here.
IP Address
Then to ensure that the Pico is always on the same IP address, we can configure a static IP address. In my case since I am using an access point, I will configure using the 10.0.0.x configuration. You will most likely have a 192.168.1.x IP address, but check with your router to be sure. And choose an address that will not likely be used for DHCP connections. ie, if you have 20 devices usually connected to your router, make sure to use something in the 40 or 50 range when choosing a static IP. Finally we provide the Pico with the credentials required to connect to our network. Be sure to replace mySSID and myPASSWORD with your real network information.
def webpage(temperature, humidity):
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>Pico DHT22</title>
</head>
<body>
<h1>Pico W - DHT22</h1>
<font size="+2">
<p>Temperature: {temperature}C</p>
<p>Humidity: {humidity}%</p>
</font>
</body>
</html>
"""
return str(html)
Next we are defining a function to create our HTML string, which we will then publish to our page. Passing in the readings for temperature and humidity which we will soon start reading from our sensor. This is enclosed in triple quotes for multi-line quotes, just so get a small picture of how HTML should look. This creates a fairly simply html page, you can always start looking at doing something much more elaborate once you get comfortable with writing html. The Raspberry Pi Foundation has some nice projects to get your going with this.
# Wait for connect or fail
max_wait = 10
while max_wait > 0:
if wlan.status() < 0 or wlan.status() >= 3:
break
max_wait -= 1
print('waiting for connection...')
sleep(1)
Getting connected
Giving the Pico ample time to establish a connection to the network. This checks every second for 10 seconds, if connection has been established. Before failing in no connection can be made.
# Handle connection error
if wlan.status() != 3:
intled.value(0)
raise RuntimeError('network connection failed')
else:
print('connected')
status = wlan.ifconfig()
print( 'ip = ' + status[0] )
We also don’t want to just print something out if there is no connection, so let’s go ahead and do a little error handling here.
# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(addr)
s.listen(1)
print('listening on', addr)
Opening a socket connection. The native socket address format of the socket module is an opaque data type returned by getaddrinfo function, which must be used to resolve textual addresses. Using getaddrinfo is the most efficient (both in terms of memory and processing power) and portable way to work with addresses. Translate the host/port argument into a sequence of 5-tuples that contain all the necessary arguments for creating a socket connected to that service. Arguments af, type, and proto (which have the same meaning as for the socket() function) can be used to filter which kind of addresses are returned. If a parameter is not specified or zero, all combinations of addresses can be returned (requiring filtering on the user side).
Using s.setsockopt set the value of the given socket option. The needed symbolic constants are defined in the socket module (SO_* etc.). Then we will bind the socket to address with s.bind. The socket must not already be bound. s.listen will enable a server to accept connections.
while True:
if wlan.status() == 3:
intled.value(1)
else:
intled.value(0)
try:
#Get the measurements from the sensor
sensor.measure()
temperature = sensor.temperature()
humidity = sensor.humidity()
print(f"Temperature: {temperature}°C Humidity: {humidity}% ")
cl, addr = s.accept()
print('client connected from', addr)
request = cl.recv(1024)
request = str(request)
print(request)
# display the webpage for the customer
html = webpage(temperature, humidity)
cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
cl.send(html)
cl.close()
except OSError as e:
cl.close()
print('connection closed')
Starting the main loop of the code, we will start reading the sensor date. Establish a connection. And publish our HTML using the string created earlier. Another small optional addition I added was using the onboard LED to indicate that the Pico is still connected to the internet. Also serving as a “sign of life” feature.
You can download the full code at our github repository.
Once all the code has been entered you can go ahead and save the code on your Pico as main.py.
main.py
If you want to run your Raspberry Pi Pico without it being attached to a computer, you need to use a USB power supply. Safe operating voltages are between 1.8V and 5.5V. To automatically run a MicroPython program, simply save it to the device with the name main.py.
In Thonny, click on the File menu and then Save as for the last program you wrote. When prompted, select ‘MicroPython device’ from the pop-up menu. Name your file main.py
You can now disconnect your Raspberry Pi Pico from your computer and use a micro USB cable to connect it to a mobile power source, such as a battery pack. Once connected, the main.py file should run automatically so you can interact with the components attached to your Raspberry Pi Pico.
Opening the page
After saving and running the page. Wait for the print statement in Thonny command line with your sensor reading and the IP address that you have provided. And you will be greeted with the sensor readings provided.
Next steps
After confirming that the sensor data is successfully being published to our page, it is time to integrate this with Home Assistant.