Valentino
Valentino

Reputation: 542

using usocket seems to halt the loop (micropython)

I'm trying to code a simple program for a ESP32 board. My main program is fairly simple and it has to run on a loop. On the side, the device also needs to be able to respond to HTTP requests with a very simple response.

This is my attempt (a rework of https://randomnerdtutorials.com/micropython-esp32-esp8266-bme280-web-server/):

try:
  import usocket as socket
except:
  import socket

from micropython import const
import time

REFRESH_DELAY = const(60000) #millisecondi


def do_connect():
    import network
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('connecting to network...')
        wlan.config(dhcp_hostname=HOST)
        wlan.connect('SSID', 'PSWD')
        while not wlan.isconnected():
            pass
    print('network config:', wlan.ifconfig())

import json
import esp
esp.osdebug(None)

import gc
gc.collect()
    
do_connect()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, SENSOR_SCKT_PORT))
s.listen(5)



prevRun = 0
i = 0

while True:
    print("iteration #"+str(i))
    i += 1
    
    # run every 60 seconds
    curRun = int(round(time.time() * 1000))
    
    if curRun - prevRun >= REFRESH_DELAY:
        prevRun = curRun
        # MAIN PROGRAM
        #   ......
        # whole bunch of code
        #  ....


    # run continuously:
    try:
        if gc.mem_free() < 102000:
            gc.collect()
        conn, addr = s.accept()
        conn.settimeout(3.0)
        print('Got a connection from %s' % str(addr))
        request = conn.recv(1024)
        conn.settimeout(None)
        request = str(request)
        #print('Content = %s' % request)
        
        measurements = 'some json stuff'
        
        conn.send('HTTP/1.1 200 OK\n')
        conn.send('Content-Type: text/html\n')
        conn.send('Connection: close\n\n')
        conn.send(measurements)
        conn.close()
    
    except OSError as e:
        conn.close()
        print('Connection closed')

what happens is I only get the iteration #0, and then the while True loop halts. If I ping this server with a HTTP request, I get a correct response, AND the loop advances to iteration #1 and #2 (no idea why it thinks I pinged it with 2 requests). So it seems that socket.listen(5) is halting the while loop.

Is there any way to avoid this? Any other solution? I don't think that threading is an option here.

Upvotes: 0

Views: 801

Answers (1)

larsks
larsks

Reputation: 311328

The problem is that s.accept() is a blocking call...it won't return until it receives a connection. This is why it pauses your loop.

The easiest solution is probably to check whether or not a connection is waiting before calling s.accept(); you can do this using either select.select or select.poll. I prefer the select.poll API, which would end up looking something like this:

import esp
import gc
import json
import machine
import network
import select
import socket
import time

from micropython import const

HOST = '0.0.0.0'
SENSOR_SCKT_PORT = const(1234)
REFRESH_DELAY = const(60000)  # milliseconds


def wait_for_connection():
    print('waiting for connection...')
    wlan = network.WLAN(network.STA_IF)
    while not wlan.isconnected():
        machine.idle()
    print('...connected. network config:', wlan.ifconfig())


esp.osdebug(None)
gc.collect()

wait_for_connection()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, SENSOR_SCKT_PORT))
s.listen(5)

poll = select.poll()
poll.register(s, select.POLLIN)

prevRun = 0
i = 0

while True:
    print("iteration #"+str(i))
    i += 1

    # run every 60 seconds
    curRun = int(round(time.time() * 1000))

    if curRun - prevRun >= REFRESH_DELAY:
        prevRun = curRun
        # MAIN PROGRAM
        #   ......
        # whole bunch of code
        #  ....

    # run continuously:
    try:
        if gc.mem_free() < 102000:
            gc.collect()

        events = poll.poll(100)
        if events:
            conn, addr = s.accept()
            conn.settimeout(3.0)
            print('Got a connection from %s' % str(addr))
            request = conn.recv(1024)
            conn.settimeout(None)
            request = str(request)
            # print('Content = %s' % request)

            measurements = 'some json stuff'

            conn.send('HTTP/1.1 200 OK\n')
            conn.send('Content-Type: text/html\n')
            conn.send('Connection: close\n\n')
            conn.send(measurements)
            conn.close()
    except OSError:
        conn.close()
        print('Connection closed')

You'll note that I've taken a few liberties with your code to get it running on my device and to appease my sense of style; primarily, I've excised most of your do_connect method and put all the imports at the top of the file.

The only real changes are:

  • We create a select.poll() object:

    poll = select.poll()
    
  • We ask it to monitor the s variable for POLLIN events:

    poll.register(s, select.POLLIN)
    
  • We check if any connections are pending before attempting to handle a connection:

    events = poll.poll(100)
    if events:
        conn, addr = s.accept()
        conn.settimeout(3.0)
        [...]
    

With these changes in place, running your code and making a request looks something like this:

iteration #0
iteration #1
iteration #2
iteration #3
iteration #4
iteration #5
iteration #6
Got a connection from ('192.168.1.169', 54392)
iteration #7
iteration #8
iteration #9
iteration #10

Note that as written here, your loop will iterate at least once every 100ms (and you can control that by changing the timeout on our call to poll.poll()).


Note: the above was tested on an esp8266 device (A Wemos D1 clone) running MicroPython v1.13-268-gf7aafc062).

Upvotes: 2

Related Questions