Pi Pico W adafruit_httpserver hangs on broken pipe exception?

CircuitPython on hardware including Adafruit's boards, and CircuitPython libraries using Blinka on host computers.

Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.
User avatar
BooleanMattock
 
Posts: 49
Joined: Wed Nov 16, 2022 8:14 am

Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by BooleanMattock »

I have an http server running a very simple website via WiFi on a Pi Pico W.

The code is derived from https://learn.adafruit.com/pico-w-http- ... cuitpython with tweaks and updates. The server is updated by regular calls to server.poll().

Every now and again (typically when two on-screen buttons are pressed in quick succession), the whole system locks up. I can interrupt the code with Control-C in the Mu Serial window, and then a get a traceback that says the code was in the middle of handling a broken pipe exception, code 32.

Traceback (most recent call last):
File "code.py", line 330, in <module>
File "adafruit_httpserver/server.py", line 163, in poll
File "code.py", line 256, in buttonpress
File "adafruit_httpserver/response.py", line 165, in send
File "adafruit_httpserver/response.py", line 141, in _send_headers
File "adafruit_httpserver/response.py", line 242, in _send_bytes

Any thoughts as to what is going on? I have a try...except around the code to reset the Pico in the case of an exception, but the code never reaches it. Looks like there might be an exception handler in response.py that is hanging?

Any thoughts most appreciated, I'm confused. Thank you for your help. Feel free to ask clarifying questions!

User avatar
mikeysklar
 
Posts: 13824
Joined: Mon Aug 01, 2016 8:10 pm

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by mikeysklar »

Since the buttonpress appears to be triggering the hang are you able to slow down the button detection code slightly to prevent this? Go ahead and paste your version of the modified http server.

User avatar
BooleanMattock
 
Posts: 49
Joined: Wed Nov 16, 2022 8:14 am

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by BooleanMattock »

mikeysklar wrote: Wed Jan 18, 2023 6:03 pm Since the buttonpress appears to be triggering the hang are you able to slow down the button detection code slightly to prevent this? Go ahead and paste your version of the modified http server.
Thank you! See below. This isn't all the code, I did my best to excerpt the relevant parts (some parts I don't want to share on a public forum) and avoid red herrings.

I tried slowing down the polling of the server by adding a time.sleep() in the main loop, which seemed to make things worse. I think you're suggesting the opposite - slowing down buttonpress() ?

Code: Select all

print("Connecting to WiFi")

try:
    #  set static IP address
    ipv4 =  ipaddress.IPv4Address(os.getenv('WIFI_ADDRESS'))
    netmask =  ipaddress.IPv4Address(os.getenv('WIFI_NETMASK'))
    gateway =  ipaddress.IPv4Address(os.getenv('WIFI_GATEWAY'))
    wifi.radio.set_ipv4_address(ipv4=ipv4,netmask=netmask,gateway=gateway)
    #  connect to your SSID
    wifi.radio.connect(os.getenv('WIFI_SSID'), os.getenv('WIFI_PASSWORD'))
    print("Connected to WiFi")
except OSError:
    # if the server fails to begin, it's probably because the wifi is not available
    # go to sleep and wait for user to restart the pico w
    print("Error connecting to network")
    time.sleep(2)
    print("going to sleep..")
    # Exit the program, and then deep sleep until the user wakes us.
    pin_alarm = alarm.pin.PinAlarm(pin=board.GP15, value=False, pull=True)
    alarm.exit_and_deep_sleep_until_alarms(pin_alarm)

pool = socketpool.SocketPool(wifi.radio)
server = HTTPServer(pool)
server.request_buffer_size = 16384

# Constants for code
HARSH = "Harsh"
MILD = "Mild"
DISABLED = -100
runMode = MILD
#  variables for HTML
passName = os.getenv('PASS_NAME')
# Countdown time in minutes
countdownTime = 5
# Keep a track of when we last did something - sleep if idle too long
lastTimeActive = time.monotonic()
IDLE_THRESHOLD_SECONDS = os.getenv("IDLE_THRESHOLD_SECONDS")

running = False
expired = False
running_str = ""
remainingTime = countdownTime*60
time_str = str(countdownTime)
remainingTime_str = ""
warningTime = DISABLED

# Reset the idle count
def ResetIdle():
    global lastTimeActive
    lastTimeActive = time.monotonic()

# If we have been idle too long, go into deep sleep
# Await wake from reset stud and then do complete reset
def CheckIdleDeepSleep():
    global lastTimeActive
    timeNow = time.monotonic()
    idleTime = timeNow - lastTimeActive
#    print("Idle for ", idleTime)
    if (idleTime > IDLE_THRESHOLD_SECONDS):
        # Exit the program, and then deep sleep until the alarm wakes us.
        pin_alarm = alarm.pin.PinAlarm(pin=board.GP15, value=False, pull=True)
        alarm.exit_and_deep_sleep_until_alarms(pin_alarm)

#  font for HTML
font_family = "monospace"

#  the HTML script
#  setup as an f string
#  this way, can insert string variables from code.py directly
#  of note, use {{ and }} if something from html *actually* needs to be in brackets
#  i.e. CSS style formatting
def webpage():
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="refresh" content="10">
    <style>
    html{{font-family: {font_family}; background-color: lightgrey;
    display:inline-block; margin: 0px auto; text-align: center;}}
      h1{{color: red; width: 200; word-wrap: break-word; padding: 2vh; font-size: 20px;}}
      h2{{color: green; width: 200; word-wrap: break-word; font-size: 50px;}}
      p{{font-size: 1.5rem; width: 200; word-wrap: break-word;}}
      .button{{font-family: {font_family};display: inline-block;
      background-color: black; border: none;
      border-radius: 4px; color: white; padding: 10px 20px;
      text-decoration: none; font-size: 15px; margin: 2px; cursor: pointer;}}
      p.dotted {{margin: auto;
      width: 75%; font-size: 15px; text-align: center;}}
    </style>
    </head>
    <body>
    <title>My Project</title>
    <h1>My Project - {passName}</h1>
    <p class="dotted">Set time and then hit a Start Button:</p>
    <p class="dotted">Time set to:
    <span style="color: green;">{time_str}:00</span></p><br>
    <h2>{running_str}</h2><br>
    <form accept-charset="utf-8" method="POST">
    <button class="button" name="TIME UP" value="UP" type="submit">TIME +1 min</button></a></p></form>
    <form accept-charset="utf-8" method="POST">
    <button class="button" name="TIME DOWN" value="DOWN" type="submit">TIME -1 min</button></a></p></form>
    <form accept-charset="utf-8" method="POST">
    <button class="button" name="STOP" value="STOP" type="submit">**STOP**</button></a></p></form>
    <form accept-charset="utf-8" method="POST">
    <button class="button" name="START MILD" value="MILD" type="submit">START MILD</button></a></p></form>
    <p><form accept-charset="utf-8" method="POST">
    <button class="button" name="START HARSH" value="HARSH" type="submit">START HARSH</button></a></p></form>
    </body></html>
    """
    return html

#  route default static IP
@server.route("/")
def base(request):  # pylint: disable=unused-argument
    #  serve the HTML f string
    #  with content type text/html
    response = HTTPResponse( request )
    return response.send(content_type="text/html", body=webpage())

#  if a button is pressed on the site
@server.route("/", "POST")
def buttonpress(request):
    global countdownTime, time_str, running, running_str, remainingTime, remainingTime_str, runMode, warningTime, expired
    # If a button has been pressed, we're not idle
    ResetIdle()
    #  get the raw text
    raw_text = request.raw_request.decode("utf8")
    # print(raw_text)
    #  if the UP button was pressed
    if "UP" in raw_text:
        countdownTime = countdownTime + 1
    #  if the DOWN button was pressed
    if "DOWN" in raw_text:
        countdownTime = countdownTime - 1
    if "MILD" in raw_text:
        print("Countdown started")
        running = True
        runMode = MILD
        warningTime = DISABLED
        remainingTime = countdownTime*60
        remainingTime_str = f"{int(remainingTime/60):02}:{remainingTime%60:02}"
        running_str = f"Running({runMode}): "+remainingTime_str
    if "HARSH" in raw_text:
        print("Countdown started")
        running = True
        runMode = HARSH
        # warning time must be a multiple of 10 seconds or the count will miss it
        # (lazy coding)
        warningTime = 60
        remainingTime = countdownTime*60
        remainingTime_str = f"{int(remainingTime/60):02}:{remainingTime%60:02}"
        running_str = f"Running({runMode}): "+remainingTime_str
    if "STOP" in raw_text:
        running = False
        expired = False
        running_str = ""
    time_str = str(countdownTime)
    response = HTTPResponse( request )
    return response.send(content_type="text/html", body=webpage())

print("starting server..")
# startup the server
try:
    server.start(str(wifi.radio.ipv4_address))
    print("Listening on http://%s:80" % wifi.radio.ipv4_address)
except OSError:
    # if the server fails to begin, it's probably because the wifi is not available
    # go to sleep and wait for user to restart the pico w
    print("Error starting server")
    time.sleep(2)
    print("going to sleep..")
    # Exit the program, and then deep sleep until the user wakes us.
    pin_alarm = alarm.pin.PinAlarm(pin=board.GP15, value=False, pull=True)
    alarm.exit_and_deep_sleep_until_alarms(pin_alarm)

# Main web server loop
# Since this has to run continuously, we're lazy
# and also use it for timing before warning
while True:
    try:
        #  every 10 seconds, update countdown
        if (clock + 10) < time.monotonic():
            clock = time.monotonic()
            # force garbage collection
            gc.collect()
            print(gc.mem_free(), " bytes free")
            # If time has expired, just keep on repeating the expired
            # display until user hits Stop
            if (expired):
                TimeExpiredDisplay(runMode)
            if (running):
                # Can't be idle while timer is running
                ResetIdle()
                # Check timer for expiry
                remainingTime = remainingTime - 10
                remainingTime_str = f"{int(remainingTime/60):02}:{remainingTime%60:02}"
                running_str = f"Running({runMode}): "+remainingTime_str
                # Assume that the countdown will at some point exactly hit the
                # warning time value
                if (remainingTime == warningTime):
                    TimeWarningDisplay()
                if (remainingTime <= 0):
                    running = False
                    expired = True
                    remainingTime = 0
                    running_str = "TIME EXPIRED"
                    TimeExpiredDisplay(runMode)
        #  poll the server for incoming/outgoing requests
        server.poll()
        # Check for idle time expiry
        # If we're idle too long this doesn't return - sleep until reset
        CheckIdleDeepSleep()
    except Exception as e:
        print("Got an error")
        print(e)
        print("going to sleep..")
        # Exit the program, and then deep sleep until the user wakes us.
        pin_alarm = alarm.pin.PinAlarm(pin=board.GP15, value=False, pull=True)
        alarm.exit_and_deep_sleep_until_alarms(pin_alarm)



User avatar
mikeysklar
 
Posts: 13824
Joined: Mon Aug 01, 2016 8:10 pm

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by mikeysklar »

We have some helpful guides in regards to button detection, state change and debouncing. It might help to take a look at these which do show an example of adding in short delays for the button_pressed() function, but other built-ins to explore.

https://learn.adafruit.com/multi-taskin ... on/buttons
https://learn.adafruit.com/debouncer-li ... debouncing

User avatar
BooleanMattock
 
Posts: 49
Joined: Wed Nov 16, 2022 8:14 am

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by BooleanMattock »

mikeysklar wrote: Mon Jan 23, 2023 6:13 pm We have some helpful guides in regards to button detection, state change and debouncing. It might help to take a look at these which do show an example of adding in short delays for the button_pressed() function, but other built-ins to explore.

https://learn.adafruit.com/multi-taskin ... on/buttons
https://learn.adafruit.com/debouncer-li ... debouncing
LOL my apologies for not being clearer. The buttons in question are the ones on the web page being served by my application. If you look at the html for the webpage you'll see them. As I said, the exception occurs when two of the buttons on the webpage are pressed (clicked, tapped) in quick succession.

User avatar
mikeysklar
 
Posts: 13824
Joined: Mon Aug 01, 2016 8:10 pm

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by mikeysklar »

Are you using a web browser to connect to the httpserver? If so, which one? There is an open issue with Chrome causing hangs. If the button hang condition is happening differently or with another browser it would be a good idea to open a case about on screen buttons being pressed in quick succession. I assume you can reproduce this with the stock example code (not the modified you are using).

https://github.com/adafruit/Adafruit_Ci ... /issues/31

User avatar
BooleanMattock
 
Posts: 49
Joined: Wed Nov 16, 2022 8:14 am

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by BooleanMattock »

mikeysklar wrote: Thu Jan 26, 2023 10:15 am Are you using a web browser to connect to the httpserver? If so, which one? There is an open issue with Chrome causing hangs. If the button hang condition is happening differently or with another browser it would be a good idea to open a case about on screen buttons being pressed in quick succession. I assume you can reproduce this with the stock example code (not the modified you are using).

https://github.com/adafruit/Adafruit_Ci ... /issues/31
Good question, thank you. I took a look at that issue report.

- My issue happens independent of browser type.
- It's also not the same failure: in my case, the code gets a broken pipe exception and then appears to hang handling it, which is (I think) different from what's described in issue report #31.

The stock example code actually doesn't work with the latest library code, and also needs hardware I don't have. I will cut the web server part out of it verbatim, fix the library calls, and try running that.
Last edited by BooleanMattock on Thu Jan 26, 2023 3:21 pm, edited 1 time in total.

User avatar
mikeysklar
 
Posts: 13824
Joined: Mon Aug 01, 2016 8:10 pm

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by mikeysklar »

I can look into the stock adafruit example code not working with the Adafruit the current httpserver. Thank you for pointing that out.

https://learn.adafruit.com/pico-w-http- ... ttp-server

User avatar
BooleanMattock
 
Posts: 49
Joined: Wed Nov 16, 2022 8:14 am

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by BooleanMattock »

mikeysklar wrote: Thu Jan 26, 2023 3:19 pm I can look into the stock adafruit example code not working with the Adafruit the current httpserver. Thank you for pointing that out.

https://learn.adafruit.com/pico-w-http- ... ttp-server
Going back to look at it now, it has changed since I copied it for my project, so I think it may already have been fixed? I'll try the updated code, it might also fix my issue.

User avatar
mikeysklar
 
Posts: 13824
Joined: Mon Aug 01, 2016 8:10 pm

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by mikeysklar »

Please let me know how it goes and if the updates browser buttons respond any differently.

User avatar
BooleanMattock
 
Posts: 49
Joined: Wed Nov 16, 2022 8:14 am

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by BooleanMattock »

mikeysklar wrote: Thu Jan 26, 2023 7:21 pm Please let me know how it goes and if the updates browser buttons respond any differently.
So the example code does not show the problem.

I wondered if it might be my use of:
<meta http-equiv="refresh" content="10">
to force the webpage to update every 10 seconds? That's the main difference that I can think of between my code and the example code. But I put that line into the example code and it works fine.

Struggling to see what the difference is that breaks the code...the only clue I have is that sometimes the web server code seems slow to respond. But I can't see what makes it go slower sometimes, it's executing the same code as far as I can see.

If I could find out what the possible causes of a broken pipe exception are, I might be able to work backwards from there. Any thoughts?

User avatar
mikeysklar
 
Posts: 13824
Joined: Mon Aug 01, 2016 8:10 pm

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by mikeysklar »

There is a new release candidate for CircuitPython 8.x so you can go ahead and update to that while testing your code.

I don't know how different your code is from the example. I normally make a copy of my problematic code and strip away large sections until it stabilizes. Then review the changes for the culprit.

User avatar
BooleanMattock
 
Posts: 49
Joined: Wed Nov 16, 2022 8:14 am

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by BooleanMattock »

mikeysklar wrote: Tue Jan 31, 2023 6:21 pm
I don't know how different your code is from the example. I normally make a copy of my problematic code and strip away large sections until it stabilizes. Then review the changes for the culprit.
Hi Mike, already did that, didn't find anything, that's why I'm struggling. Code is in OP.

User avatar
BooleanMattock
 
Posts: 49
Joined: Wed Nov 16, 2022 8:14 am

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by BooleanMattock »

OK, latest update. I have now managed to make the same lockup happen on the example code.

Code enclosed below, cut down from the latest version of the example here: https://learn.adafruit.com/pico-w-http- ... ttp-server.

Using the latest release:
adafruit-circuitpython-raspberry_pi_pico_w-en_US-8.0.0-rc.2.uf2
and libraries:
adafruit-circuitpython-bundle-8.x-mpy-20230203.zip

Basically if you click repeatedly and rapidly on one of the on-screen buttons on the web page, eventually the code hangs in:
Traceback (most recent call last):
File "code.py", line 157, in <module>
File "adafruit_httpserver/server.py", line 163, in poll
File "code.py", line 128, in buttonpress
File "adafruit_httpserver/response.py", line 165, in send
File "adafruit_httpserver/response.py", line 141, in _send_headers
File "adafruit_httpserver/response.py", line 242, in _send_bytes

Code: Select all

# SPDX-FileCopyrightText: 2022 Liz Clark for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import os
import time
import ipaddress
import wifi
import socketpool
import busio
import board
import microcontroller
import displayio
import terminalio
from digitalio import DigitalInOut, Direction
from adafruit_httpserver.server import HTTPServer
from adafruit_httpserver.request import HTTPRequest
from adafruit_httpserver.response import HTTPResponse
from adafruit_httpserver.methods import HTTPMethod
from adafruit_httpserver.mime_type import MIMEType

#  onboard LED setup
led = DigitalInOut(board.LED)
led.direction = Direction.OUTPUT
led.value = False


#  connect to network
print()
print("Connecting to WiFi")

#  set static IP address
ipv4 =  ipaddress.IPv4Address("192.168.1.55")
netmask =  ipaddress.IPv4Address("255.255.255.0")
gateway =  ipaddress.IPv4Address("192.168.1.1")
wifi.radio.set_ipv4_address(ipv4=ipv4,netmask=netmask,gateway=gateway)
#  connect to your SSID
wifi.radio.connect("XXXX", "yyyyy")

print("Connected to WiFi")
pool = socketpool.SocketPool(wifi.radio)
server = HTTPServer(pool)

#  variables for HTML
#  comment/uncomment desired temp unit

#  temp_test = str(ds18.temperature)
#  unit = "C"
temp_test = "32"
unit = "F"
#  font for HTML
font_family = "monospace"

#  the HTML script
#  setup as an f string
#  this way, can insert string variables from code.py directly
#  of note, use {{ and }} if something from html *actually* needs to be in brackets
#  i.e. CSS style formatting
def webpage():
    html = f"""
    <!DOCTYPE html>
    <html>
    <head>
    <meta http-equiv="Content-type" content="text/html;charset=utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
    html{{font-family: {font_family}; background-color: lightgrey;
    display:inline-block; margin: 0px auto; text-align: center;}}
      h1{{color: deeppink; width: 200; word-wrap: break-word; padding: 2vh; font-size: 35px;}}
      p{{font-size: 1.5rem; width: 200; word-wrap: break-word;}}
      .button{{font-family: {font_family};display: inline-block;
      background-color: black; border: none;
      border-radius: 4px; color: white; padding: 16px 40px;
      text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}}
      p.dotted {{margin: auto;
      width: 75%; font-size: 25px; text-align: center;}}
    </style>
    </head>
    <body>
    <title>Pico W HTTP Server</title>
    <h1>Pico W HTTP Server</h1>
    <br>
    <p class="dotted">This is a Pico W running an HTTP server with CircuitPython.</p>
    <br>
    <p class="dotted">The current ambient temperature near the Pico W is
    <span style="color: deeppink;">{temp_test}°{unit}</span></p><br>
    <h1>Control the LED on the Pico W with these buttons:</h1><br>
    <form accept-charset="utf-8" method="POST">
    <button class="button" name="LED ON" value="ON" type="submit">LED ON</button></a></p></form>
    <p><form accept-charset="utf-8" method="POST">
    <button class="button" name="LED OFF" value="OFF" type="submit">LED OFF</button></a></p></form>
    <h1>Party?</h>
    <p><form accept-charset="utf-8" method="POST">
    <button class="button" name="party" value="party" type="submit">PARTY!</button></a></p></form>
    </body></html>
    """
    return html

#  route default static IP
@server.route("/")
def base(request: HTTPRequest):  # pylint: disable=unused-argument
    #  serve the HTML f string
    #  with content type text/html
    with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response:
        response.send(f"{webpage()}")

#  if a button is pressed on the site
@server.route("/", method=HTTPMethod.POST)
def buttonpress(request: HTTPRequest):
    #  get the raw text
    raw_text = request.raw_request.decode("utf8")
    print(raw_text)
    #  if the led on button was pressed
    if "ON" in raw_text:
        #  turn on the onboard LED
        led.value = True
    #  if the led off button was pressed
    if "OFF" in raw_text:
        #  turn the onboard LED off
        led.value = False
    #  if the party button was pressed
    if "party" in raw_text:
        #  toggle the parrot_pin value
        led.value = False

    #  reload site
    with HTTPResponse(request, content_type=MIMEType.TYPE_HTML) as response:
        response.send(f"{webpage()}")

print("starting server..")
# startup the server
try:
    server.start(str(wifi.radio.ipv4_address))
    print("Listening on http://%s:80" % wifi.radio.ipv4_address)
#  if the server fails to begin, restart the pico w
except OSError:
    time.sleep(5)
    print("restarting..")
    microcontroller.reset()

clock = time.monotonic() #  time.monotonic() holder for server ping
parrot = False #  parrot state
party = 0 #  time.monotonic() holder for party parrot
p = 0 #  index for tilegrid

while True:
    try:
        #  every 30 seconds, ping server & update temp reading
        if (clock + 30) < time.monotonic():
            clock = time.monotonic()
            #  comment/uncomment for desired units
            #  temp_test = str(ds18.temperature)
            temp_test = "31"
            temp_text_area.text = "Temperature: %s F" % temp_test

        #  poll the server for incoming/outgoing requests
        server.poll()
    # pylint: disable=broad-except
    except Exception as e:
        print(e)
        continue


User avatar
mikeysklar
 
Posts: 13824
Joined: Mon Aug 01, 2016 8:10 pm

Re: Pi Pico W adafruit_httpserver hangs on broken pipe exception?

Post by mikeysklar »

Good work. You have a reproducible bug based on Adafruit example code.

Do you mind opening an issue here with the Learning System Guides github repo?

https://github.com/adafruit/Adafruit_Le ... des/issues

You can pretty much cut and paste your last post.

Locked
Please be positive and constructive with your questions and comments.

Return to “Adafruit CircuitPython”