1231

Experimenting with MicroPython - build a simple webserver. Nothing fancy, just enough to get it going and to improve upon.

Since the MicroPython Kickstarter campaign had promised WIZ810io network support and since I happened to have a few of these modules lying around, I decided to try to set up a simple webserver.

The first step was, of course, Google in the hope to find an all-singing, all-dancing example, but I came up empty-handed. I did find some MicroPython webserver code, but it didn’t work on my pyboard. Now, before you start shouting and making signs to attract my attention, yes, I did reprogram my pyboard (PYBv1.1) with a special firmware that includes network drivers (pybv11-network-20170322-v1.8.7-461-g58f23de.dfu using DfuSe (partnumber STSW-STM32080) from STMicroelectronics).

I had pasted the simple-enough webserver code in main.py but it didn’t work. How to go about to debug it? Actually, I don’t know what the preferred Python way is. I guess, either you add debug statements to the program or you can paste the program line-by-line into the REPL, which is what I ended up doing.

Albeit somewhat tedious, this technique quickly showed me that the call of the sendall method of my socket object was invalid (read the original code to understand what I am on about). There is no such method! Now this is strange, because the MicroPython socket documentation clearly mentions it on its website.

This took me to the MicroPython source code repository on GitHub to have a closer look at the network driver where I discovered, after a lot browsing, that the WIZnet driver does not export a sendall method at all. It doesn’t export some other standard sockets methods that work in MicroPython either and some of which even must be used. Go figure.

Anyway, even though replacing sendall by send was easy enough, it did not make my webserver work. It took another good deal of head scratching to find the culprit: a missing content-length field in the HTTP page header sent by my webserver. After adding it, my MicroPython web page finally showed up in my browser.

Putting the working code in the file main.py lead to new surprises. Initially it worked fine, but when I started improving it a bit, something went horribly wrong and it all fell over. To cut a long story short, the file system on my pyboard had somehow become corrupted and the webpage’s contents had been transformed into garbage. Also, the file main.py had become inaccessible and its size was reduced to 0. After restoring the pyboard using the methods described here, I could reinstall my little program to get the webserver up and running again.

This experience taught me to do the experimentation in the REPL instead of saving a file too often on the pyboard. Its filesystem seems to be a tad fragile. For comfortable REPL copy-pasting it is useful to have a serial terminal that supports multi-line pasting. Tera Term does this. Such a tool allows you to paste blocks of code up to points of where a manual backspace is required to decrease the indent level (for instance at the end of a while loop or if-else statement), speeding things up a bit.

Here is the code that worked for me (you can download it below):
import os
import network
import socket

# Adapt these for your network.
my_ip = '192.168.2.33'
subnet_mask = '255.255.255.0'
gateway = '192.168.2.1'
dns = '8.8.8.8' # dunno what to put here, 8.8.8.8 is WIZnet example.
# The one and only page we serve.
page_name = 'page1.htm'

# Initialize network interface.
nic = network.WIZNET5K(pyb.SPI(1),pyb.Pin.board.X5,pyb.Pin.board.X4)
nic.ifconfig((my_ip,subnet_mask,gateway,dns))
print(nic.ifconfig())
# Launch server (2x Ctrl-C gets you back into REPL mode).
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(('',80))
s.listen(0)
while True:
	conn, addr = s.accept() # addr is not used but required.
	request = conn.recv(1024) # receive HTTP request.
	# Look for the string "Val=".
	val_begin = str(request).find('Val=')
	if val_begin>0:
		# Found string "Val=", now extract value.
		pyb.LED(1).on()
		val_end = str(request).find(' ',val_begin)
		v = str(request)[val_begin+4:val_end]
		print("Val =",v)
		conn.send(v)
		# LED3 off if v<50, on otherwise.
		if int(v)<50:
			pyb.LED(3).off()
		else:
			pyb.LED(3).on()
	else:
		# String "Val=" not found, serve web page.
		pyb.LED(2).on()
		# Send HTTP header.
		conn.send('HTTP/1.1 200 OK\nConnection: close\nServer: pyboard\nContent-Type: text/html\n')
		conn.send('Content-Length: ')
		# We have to insert the size of the data we are going to send.
		conn.send(str(os.stat(page_name)[6])) # Unreadable way of getting file size.
		# Close HTTP header.
		conn.send('\n\n')
		# Send web page.
		f = open(page_name,'r')
		conn.send(f.read())
	conn.close()
	pyb.LED(1).off()
	pyb.LED(2).off()