adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.1k stars 1.22k forks source link

Hard crash running httpserver on ESP32S2 #8674

Open efc opened 11 months ago

efc commented 11 months ago

CircuitPython version

Adafruit CircuitPython 8.2.8 on 2023-11-16; Adafruit QT Py ESP32S2 with ESP32S2

Code/REPL

# SPDX-FileCopyrightText: 2022 Dan Halbert for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense

import board
import neopixel
import mdns
import socketpool
import wifi

from adafruit_httpserver import Server, Route, as_route, Request, Response, GET, POST

mdns_server = mdns.Server(wifi.radio)
mdns_server.hostname = "efcx"
mdns_server.advertise_service(service_type="_http", protocol="_tcp", port=80)

pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, "/static", debug=True)

pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)

@server.route("/")
def base(request: Request):
    """
    Serve a default static plain text message.
    """
    return Response(request, "Hello from the CircuitPython HTTP Server!")

# This is the simplest way to register a route. It uses the Server object in current scope.
@server.route("/change-neopixel-color", GET)
def change_neopixel_color_handler_query_params(request: Request):
    """Changes the color of the built-in NeoPixel using query/GET params."""

    # e.g. /change-neopixel-color?r=255&g=0&b=0

    r = request.query_params.get("r") or 0
    g = request.query_params.get("g") or 0
    b = request.query_params.get("b") or 0

    pixel.fill((int(r), int(g), int(b)))

    return Response(request, f"Changed NeoPixel to color ({r}, {g}, {b})")

# This is another way to register a route. It uses the decorator that converts the function into
# a Route object that can be imported and registered later.
@as_route("/change-neopixel-color/form-data", POST)
def change_neopixel_color_handler_post_form_data(request: Request):
    """Changes the color of the built-in NeoPixel using POST form data."""

    data = request.form_data  # e.g. r=255&g=0&b=0 or r=255\r\nb=0\r\ng=0
    r, g, b = data.get("r", 0), data.get("g", 0), data.get("b", 0)

    pixel.fill((int(r), int(g), int(b)))

    return Response(request, f"Changed NeoPixel to color ({r}, {g}, {b})")

def change_neopixel_color_handler_post_json(request: Request):
    """Changes the color of the built-in NeoPixel using JSON POST body."""

    data = request.json()  # e.g {"r": 255, "g": 0, "b": 0}
    r, g, b = data.get("r", 0), data.get("g", 0), data.get("b", 0)

    pixel.fill((r, g, b))

    return Response(request, f"Changed NeoPixel to color ({r}, {g}, {b})")

# You can always manually create a Route object and import or register it later.
# Using this approach you can also use the same handler for multiple routes.
post_json_route = Route(
    "/change-neopixel-color/json", POST, change_neopixel_color_handler_post_json
)

def change_neopixel_color_handler_url_params(
    request: Request, r: str = "0", g: str = "0", b: str = "0"
):
    """Changes the color of the built-in NeoPixel using URL params."""

    # e.g. /change-neopixel-color/255/0/0

    pixel.fill((int(r), int(g), int(b)))

    return Response(request, f"Changed NeoPixel to color ({r}, {g}, {b})")

# Registering Route objects
server.add_routes(
    [
        change_neopixel_color_handler_post_form_data,
        post_json_route,
        # You can also register a inline created Route object
        Route(
            path="/change-neopixel-color/<r>/<g>/<b>",
            methods=GET,
            handler=change_neopixel_color_handler_url_params,
        ),
    ]
)

server.serve_forever(str(wifi.radio.ipv4_address))

Behavior

Auto-reload is off.
Running in safe mode! Not running saved code.

You are in safe mode because:
CircuitPython core code crashed hard. Whoops!
Hard fault: memory access or instruction error.

NeoPixel on board flashing three times every second or so.

Description

I left this code (pretty much just sample code) running on the board overnight while attached to my computer via USB. I was able to visit the website a few times (just going to root). But in the morning I found the board in safe mode with this message on the serial panel of mu.

Additional information

I have no idea if the httpserver just crashed after some period of time or if the Mac did something to kill the attached board.

Generally, how long and adafruit_httpserver servers run without cycling the board? If the board does need to be reset every now and then for stability, how often do you recommend doing that?

dhalbert commented 11 months ago

You may not be able to try this, but do you see the same crashes when you don't use mDNS?

efc commented 11 months ago

Good question, Dan. Since this crash took overnight to manifest, it will take a while to test this out.

I plan to try running this code as-is, but attaching the board to a power supply instead of a computer to see if that resolves the problem. If it does not, then I will try removing the mDNS element from the code. That is a "would-be-nice" but not essential element of the project I envision, so I can live without it.

efc commented 11 months ago

OK, I have run two tests with the board plugged into a power supply instead of the computer. Both failed.

In the first test I left the code as it was with mDNS above. This test ran for about 36 hours with a loop on another computer using the board's webserver to change the neopixel color every minute.

In the second test I commented out the mDNS lines. This one failed after about 90 minutes.

I doubt that the length of time between failures indicates very much, but the failure itself is a concern. In both cases the board failed with the same triple flashing of the neopixel I describe above, which I believe indicates it entered safe mode.

I am not terribly surprised that the adafruit_httpserver is not very robust. I have had similar issues with past approaches to running web servers on microcontrollers. I guess in addition to just raising the issue and hoping the software can be improved, I am wondering two things: (1) Is there anything else I can do to produce more helpful diagnostic information for anyone who might be able to improve the server, and (2) can anyone suggest code I could add to either sense failure and restart the server or to cycle the server or microcontroller in some way that might at least bring it back to life after a failure?

anecdata commented 11 months ago

The most immediate help is to use a safemode.py file: https://learn.adafruit.com/circuitpython-safe-mode

There are many other things that can be done for general robustness of circuit python projects. The Adafruit Discord server is a great place to ask about circuit python usage and customization.

efc commented 11 months ago

@anecdata, thank you for suggesting safemode.py. I have created the following safemode.py script and the devices I am testing have now been up for nearly 3 days. In those three days I have detected at least 4 crashes into safe mode, but the device has reset itself and continued on with the task.

import microcontroller
microcontroller.reset()

I would love for the wifi tools to be more stable, but this workaround at least keeps us up and on the network. Thank you!

seandepagnier commented 8 months ago

I managed to hit "Hard fault: memory access or instruction error." after only a few minutes playing with the httpserver examples. I dont have more details at this time, just that I was using the pico w.