ESP32 MicroPython: HTTP Webserver with Picoweb

The objective of this post is to explain how to install Picoweb, a HTTP Micro web framework for MicroPython. The tests were performed using a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board.


The objective of this post is to explain how to install Picoweb, a Micro web framework for MicroPython. This framework allows us to create an HTTP server on the ESP32, without needing to worry about the low level details.

Note that at the time of writing Picoweb was not officially supported on the ESP32, and thus this post presents a workaround to install it and make it work.

The tests were performed using uPyCraft, a MicroPython IDE. You can check how to use uPyCraft in this previous post. In particular, we will be uploading a lot of scripts to the ESP32 file system, which is very simple with uPyCraft. You can check here how to do it.

The tests were performed using a DFRobot’s ESP-WROOM-32 device integrated in a ESP32 FireBeetle board. The pictures shown through the tutorial are from the tests on the ESP32.

If you prefer, you can check a video version of this tutorial on my YouTube channel.


Update: The library can easily be installed with using upip. To do so, we simply need to import upip and give the following command (after connecting to a WiFi network):

import upip

This command will install the Picoweb module and all the dependencies. Thus, you can skip the “Resolving the dependencies” and Installing Picoweb” sections. I will leave them just for reference.

Thank you very much to Siva Kumar for pointing this out. You can check an awesome article about Picoweb from him here.

Connecting to WiFi

In order for us to be able to complete this tutorial, the ESP32 needs to be previously connected to WiFi, to both install some libraries needed and then to run the HTTP Web server.

Connecting to a WiFi network on MicroPython was covered in this previous post, so I will not be covering in detail the procedure needed. Also, on this other tutorial, there’s an explanation on how to create a script for automatically connect to WiFi on the ESP32, using MicroPython.

Since we are using uPyCraft, we can use the code bellow to write a script and run it on the ESP32, in order to automatically connect to the WiFi. Note that you need to change the two first variables to hold your WiFi network credentials.

import network

def connect():
  ssid = "yourNetworkName"
  password =  "yourNetworkPassword"

  station = network.WLAN(network.STA_IF)

  if station.isconnected() == True:
      print("Already connected")
  station.connect(ssid, password)

  while station.isconnected() == False:

  print("Connection successful")

After uploading this file, you can automatically connect to the WiFi just by importing the module and calling its connect function.

Note that the name of the module to import corresponds to the name you have given to the file. In my case, I’ve named it wifiConnect. On the version of uPyCraft used in this tutorial (v0.22), we don’t need to put the .py at the end of the file, since the IDE appends it automatically. This however may change in future versions, and the .py extension is needed for us to be able to import the module.

import wifiConnect

If you are having troubles finding the uploaded file, just import the os module and run the listdir method. This should show you the uploaded file on the file system.

import os

Upon a successful connection, a list of IP addresses should be printed on the MicroPython command line, as shown in figure 1. You should save them since we will need them later.

ESP32 MicroPython WiFi connect get IP

Figure 1 – IP addresses printed upon successful connection to WiFi.

Resolving the dependencies

Deprecated: Check a simpler way to install Picoweb and its dependencies at the end of the introductory section.

In order to be able to use Picoweb on the ESP32, we will need to resolve some of its dependencies, namely some modules that don’t come by default with the MicroPython installation.

So, we will need to install the uasyncio and pkg_resources libraries. Fortunately, we can install them using upip, a MicroPython package installer.

So, we start by importing upip and then installing the mentioned modules. Please note that this will not work if the ESP32 is not connected to the WiFi, which we have done in the previous section. The installation is done by calling the install method of the upip module, passing as input a string describing the module we want to install.

import upip

Please note that the installation may take a while. When everything finishes, a new folder called lib should be available at our file system. You can confirm that with the following commands:

import os

You can check the expected result at figure 2, which shows the newly created library folder.

ESP32 MciroPython install with upip.png

Figure 2 – New library folder for MicroPython Modules.

You can further explore the contents of this library by using the chdir method of the os module to navigate inside it, but for now we will confirm a successful installation by trying to import both modules.

import uasyncio
import pkg_resources

If no error is thrown when executing the 2 previous commands, the the installation has concluded without problems.

As a final note for this section, it’s important to know that Picoweb as a soft dependency to a library called utemplate. This is a soft dependency since as long as we don’t use the template functionalities, the library is not imported, and thus no error occurs.

Although during the tests I’ve performed I’ve been able to manually install it (with the same method we will see below for Picoweb), I’ve not been able to use the template functionalities due to running out of memory on the ESP32. Thus, for this tutorial, we will not worry about utemplate.

Installing Picoweb

Deprecated: Check a simpler way to install Picoweb and its dependencies at the end of the introductory section.

To install Picoweb, we will basically copy the source files from the GitHub repository to our MicroPython directory. The 2 files that matter are inside the picoweb folder on the repository.

So, to maintain the folder structure, we will first create on our ESP32 MicroPython file system a folder called picoweb, as shown below.

import os

Upon the execution of the last line of code, the new directory should appear on the file system list.

Now, we will upload both the and files of the repository’s picoweb folder. As before, you can include the .py extension or not when saving the file, since in the current version of uPyCraft it will put it for you if it is not present.

You can check on figure 3 the upload of the file, with the relevant menu buttons highlighted (save file and upload file). Don’t forget to maintain the original file names in the popup.

ESP32 MicroPython uPyCraft File Upload example.png

Figure 3 – Example of uploading the file.

The file will take a while to upload, due to its size. Don’t worry if after the upload of this file a memory error is thrown on the console. You can ignore it.

After the upload, you can repeat the os.listdir() command to confirm that the files are on the file system.

Finally, we will move our files to the picoweb folder, with the commands bellow. I’m assuming the os module was already imported from the previous commands. The final command is run to confirm the files are no longer in the root directory and have been copied.

os.rename('', 'picoweb/')
os.rename('', 'picoweb/')

You should now be able to import Picoweb, without any error being thrown.

import picoweb

Hello World code

Our hello world program will be a simplified version of one of the examples available in the GitHub repository. Note that although we are analyzing the code step by step, this should be created on a script file for later uploading.

Naturally, we will start by importing the Picoweb module we have just installed.

import picoweb

Then, we will create an instance of class WebApp, that we will use latter. We can just use the __name__ macro to pass the module’s name as input.

app = picoweb.WebApp(__name__)

Now we can simply start defining the endpoints for the HTTP requests, in a Flask or Bottle frameworks style. So, to add a new route, we just use the route decorator of our app object, passing as input the URL that will trigger the execution of the function we will define next. In this case we will define the index route, which corresponds to the “/” URL.

Then, we define the function (we will call it index) that will handle this route. This function receives as input two arguments. The first one corresponds to an object of class HTTPRequest and the second one will be a StreamWriter for a socket that we will use to send our response. Note that Picoweb automatically handles the request to pass these two arguments to the function.

def index(req, resp):

Inside the function, we will start by calling the start_response method of the picoweb module, which starts writing the initial part of the HTTP response, which includes the content-type and the status code.

This function receives as input the previously mentioned stream writer object, which we received in the index function.

Note that by default the status code is 200 and the content-type is text/html, but we can pass these values as parameters. For now, we won’t pass any additional value and thus the defaults will be used.

Finally, we will use the awrite method of our stream writer to send the rest of the content. We will send a simple hello world message.

One important aspect to consider is that we use the yield from keywords before calling any of each functions. This is related to more advanced Python features that are outside of the scope of this post. You can read more about yield here. You can also read more about asynchronous Python and the yield from keywords here.

yield from picoweb.start_response(resp)
yield from resp.awrite("Hello world from picoweb running on the ESP32")

To start our server, we end the code by calling the run method on our app object. This method receives as input some important parameters, such as the host and the port where the server will be listening.

The port defaults to 8081, if we don’t pass this argument. The host defaults to if we don’t specify it. In our case, we will not specify the port, and thus use the default 8081.

Nonetheless, we will use the IP address we obtained before when connecting to the WiFi network, on a previous section. We will pass this IP as a string, in the host parameter.

Note that multiple IPs have been printed. Although this may depend on the router, the IP that we should use is the one that begins in 192 and doesn’t end in 254. Thus, in my case, it is, but yours will most likely be different. It’s important to consider that this is a local IP address, and thus you should not be able to access the server from outside your network without port forwarding the router.

Additionally, we will set the debug parameter of the run method to True, so we get some additional information printed to the console.

The final complete code, which already includes this call, can be seen bellow.

import picoweb

app = picoweb.WebApp(__name__)

def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("Hello world from picoweb running on the ESP32"), host = "")

Testing the code

To test the code, you simply need to upload the script for the ESP32. In my case, since I’m using uPyCraft, this is very easy.

Upon uploading the code, it will be automatically executed in uPyCraft. If you are using other method to upload the code, you may need to manually run the script by importing it as a module.

Upon execution, a message indicating the server is running gets printed on MicroPython serial console, as can be seen on figure 4.

ESP32 MicroPython picoweb hello world.png

Figure 4 – Running the Picoweb hello world example.

To test that the ESP32 is answering the requests, you can simply copy the http address printed on the console (it should have your ESP32 address). On uPyCraft, you need to select the portion of the text you want to copy, right click it and select copy. Be careful because a ctrl+c command will stop the execution of the server instead of copying the content.

Then, as shown in figure 5, just open a web browser and paste the http address there. You should get the hello world message we defined early. Note that this will only work if the computer is connected to the same network of the ESP32.

If you want to reach the ESP32 server from outside its WiFi network, then you need to port forward the router. This is an advanced procedure that depends on the model of the router, and thus it’s outside the scope of this post.

ESP32 MicroPython picoweb framework test hello world.png

Figure 5 – Output of the Picoweb program.

Related Posts


27 Replies to “ESP32 MicroPython: HTTP Webserver with Picoweb”

  1. Hello.

    Grate and simple manual. Thanks!

    One question/issue…

    I would like to do some kind of simple thermostat based on esp32. I would like to use “json” to comuicate (not mqtt) with user.

    So I need “two thread” program/script.

    One thread to serve html page to show temperature and switch status or set temperature (write to file) to control thermostat and second thread to chceck themperature every e.g. 5 minutes and compare it to temperature in file (some kind of cron job).

    Colud you be so kind and help me… Give some example how to do two threded app (with picoweb to serv html and get some “post” date from form). I do not want to use some kind of hub. The solution should be simple and standalone as possible.

    Best regards,

    Liked by 1 person

    1. Hi! Thanks for the feedback 🙂

      First of all and to clarify your question, JSON and MQTT are two different things than cannot be compared. MQTT is a communication protocol (like HTTP is) and JSON is a data format (like XML is).

      So JSON is not an alternative to MQTT, but rather a way of formatting information.

      Other important thing to note is that Picoweb doesn’t use MQTT at all, but rather HTTP.

      The HTML part should be pretty simple with pico web. You will need to set up a route to serve your HTML file (pretty much what is covered in this tutorial) and then other route to handle the switch/set temperature actions you mentioned.

      Going back to the JSON talk, you can use it as a data format here to send your actions to the picoweb server.

      As for the other thread to check the temperature I cannot currently help you with that since I haven’t yet explored the threading support on MicroPython.

      Many of the things that work on Python work on MicroPython the same way, so if there is already a threading module my guess is that the interface will be very similar to the Python one, to which there are plenty of tutorials around the web.

      If not, you can ask around the MicroPython forums or GitHub page for more information on if this support exists and how it works.

      Let us know if you discover more about the thread support.

      Hope this helps getting you started.

      Best regards,
      Nuno Santos


  2. Thanks for reply !

    I know that MQTT and JSON is something different… it was some kind of shortcut in my post 😉 so I’m starting “extended googling” for uasync lib.

    Best regards,


  3. upip.install(picoweb) works fine. Without doing these steps. I am using ESP32 board
    Also you can get the IP address dynamically , so that host can get the ip address directly.
    code snippet below:

    ipadd=sta_if.ifconfig(), host =ipadd[0])

    Liked by 1 person

    1. Hi! Thank you very much for sharing it, I will update the post as soon as I have some time!

      I was having some troubles with upip but I think I was trying a different command.

      Best regards,
      Nuno Santos


  4. Fantastic article, but I’m getting an error on a Wemos ESP32:

    No matter what I run, including the examples in the picoweb git repo, I get the following:

    * Running on
    Traceback (most recent call last):
    File “”, line 13, in
    File “/lib/picoweb/”, line 287, in run
    File “/lib/uasyncio/”, line 146, in run_forever
    File “/lib/uasyncio/”, line 101, in run_forever
    File “/lib/uasyncio/”, line 235, in start_server
    TypeError: function takes 2 positional arguments but 4 were given

    I’ve scoured the internets for anyone else with the same problem but coming up with nothing. Literally nothing wlll execute using picoweb. Where should I look next?

    Liked by 1 person

    1. Hi!

      Thank you very much for the feedback 🙂

      That’s a weird error, I’ve never experienced that one during my tests.

      But to be honest, I’ve not been using picoweb or MicroPython for a while, so something may have changed in the implementation of some lib used under the hood.

      I did a quick check at the commit history of the repository and it also seems some changes have been done in the past months, so another possibility is that something has changed in the API and the maintainer of the project did not have time to update the examples yet.

      Nonetheless, ff you haven’t done it yet, my suggestion is that you open a issue on the picoweb git repository, since someone else may have experienced that error and be able to help you.

      Let us know if you find the solution, so it may help others 🙂 If I happen to find any solution, I’ll share here.

      Best regards,
      Nuno Santos


  5. Hello! and thanks for this great tutorial!
    I installed picoweb following the updated method, using upip. It seemed to have been installed correctly. However, when running my appplication, I am getting this error:

    File “”, line 11, in start
    File “/lib/picoweb/”, line 283, in run
    ImportError: no module named ‘logging’

    Do you know what this could mean?



    Liked by 1 person

    1. Hi!

      You’re welcome, thanks for the feedback 🙂

      Unfortunately I’ve not been using picoweb for a while and at the time I tested I never ran into that issue.

      But it seems to be a missing dependency on a logging module that may have been added later.

      I don’t know if this logging module is some standard one that comes with MicroPython installation or a module that needs to be installed later (I’ve not been using MicroPython for a while and things have evolved a lot since then).

      My suggestion to try to solve your problem is opening a ticket on the picoweb module GitHub page:

      There someone who has already encountered the same issue may be able to help you with a solution.

      Another alternative is trying to manually install that module via upip, although I’m not sure it is available:

      From the website below, you can also get some luck with:

      Let us know if you have been able to solve it. If I come across some solution, I will share here.

      Best regards,
      Nuno Santos


      1. Hi Nuno!

        Thanks for your help! I was successful at overcoming that issue with the command:
        upip.install(‘micropython-logging’)….no more complains against the missing ‘logging’ module.

        But, instead, I am getting another error:

        Traceback (most recent call last):
        File “”, line 6, in
        File “”, line 12, in start
        File “/lib/picoweb/”, line 294, in run
        File “/lib/uasyncio/”, line 224, in get_event_loop
        File “/lib/uasyncio/”, line 21, in __init__
        File “/lib/uasyncio/”, line 30, in __init__
        AttributeError: ‘module’ object has no attribute ‘deque’

        I will continue to look at the internet for a solution!!

        BTW I used upycraft to burn the firmware, maybe the fw version is too old?

        Liked by 1 person

        1. Hi!

          I’m glad you have passed that first error 🙂

          Now it seems to be something related with uasyncio, maybe some update on those libs that you don’t have yet on your MicroPython, which are causing the missing attribute?

          That may be the case indeed! You can try to update uPyCraft to the latest version and install MicroPython again.

          I’m not sure if uPyCraft dynamically fetches a MicroPython build before burning or if it has a static MicroPython version, so it may be a good shot trying with the latest uPyCraft release.

          You can always try a manual burning of MicroPython in your ESP32, so you are sure to use the latest version available.

          I’ve written a tutorial a couple of time ago, I think the procedure still works:

          Let me know if any of these have helped.

          Best regards,
          Nuno Santos


    1. Hi! Unfortunately never had the chance to test it, but my guess is that it should work.

      I think picoweb works on a layer of abstraction that doesn’t need to know if the ESP is working as station or soft AP.

      But let us know f you have the chance to confirm it.

      Best regards,
      Nuno Santos


  6. hi,
    im trying to install picoweb but upip keep giving me error..

    >>> upip.install(‘picoweb’)
    Installing to: /lib/
    Error installing ‘picoweb’: Package not found, packages may be partially installed

    it did give me the error once saying SSL cert is not valid.,,

    any help will be great..



    1. Hi again,

      sorry I found out the problem apparently the version of micropython and upip that uPyCraft auto installs is old. so downloaded and flashed the latest one and picoweb install worked like a charm..
      thanx for the great tutorials !!
      keep it up!


      Liked by 1 person

  7. For logging bug do that in of picoweb:
    -> def run(self, host=”″, port=8081, debug=-1, lazy_init=False, log=None):
    Put debug=-1 in the headers

    For uasyncio in, do that:
    in line 60, that self.poller.unregister(sock, False) change for:
    self.poller.unregister(sock, False)
    print(‘Echec unregister’)

    et after, it works well.

    Liked by 1 person

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s