Servidor Web en Esp32-Cam con MicroPython




Presentación


El presente proyecto continuando sobre modos de programacion alternativa al propuesto por el IDE de Arduino que permitiera superar la molesta necesidad de que ante la menor modificacion hubiera subir todo el proyecto completo a la placa para poder probar su funcionamiento esta desarrollado tambien en MicroPython y utilizando el Thonny como IDE

En este proyecto empezamos a darle uso a la Camara Ov2640 que viene integrada a la placa Esp32-Cam .

El proyecto consiste en montar en la placa Esp32-Cam un pequeño servidor web consistente en una pagina principal que mediante un botón permite obtener una foto a través de la cámara la que luego se visualizara en la pantalla

Una vez arrancada la placa y al ingresar a traves del navegador se ve la siguiente pantalla:

Luego despues de hacer clic en el boton Sacar Foto y esperar unos instantes aparece la siguiente pantalla donde se puede observar la foto obtenida:

Finlmente haciendo clic en el boton Inicio regresamos a la primera pantalla



Materiales


Placa Esp32-Cam

Programador Esp32-Cam


Si bien por ser mas cómodo y práctico se ha usado el adaptador el mismo puede ser remplazado por una placa FTDI y el cableado pertinente.


Montaje del proyecto en la placa Esp8266


Para utilizar la placa Esp32-Cam con MicroPython es necesario flashear primero la misma con el firmware correspondiente. En el caso de la utilizada en este modelo se utilizo el provisto por la pagina de https://github.com/lemariva/micropython-camera-driver/tree/master/firmware

Una vez instalado el firmware en la placa utilizando la utilidad que viene provista por el IDE Thonny, o por aquella que ustedes utilicen ademas del firmware quedara grabado en la placa un archivo boot.py que debe ser reemplazo por el que se incluye en el codigo desarrollado a continuacion.


El primer problema que debi resolver en el presente proyecto fue como enviar la imagen obtenida de la camara a la pagina web, debido a la complejidad para el manejo de imagenes del servidor montado en la placa, en virtud que si bien es posible guardar las imagenes en la memoria de la placa Esp32-Cam en formato jpg despues no es posible leerlas en forma directa desde las paginas web servidas, ya que al menos por ahora no he hallado manera de direccionar el src de la etiqueta imagen sobre las imagenes guardadas. Según pude leer en algunas paginas de internet solo es posible incorporar imagenes direccionandolas sobre ubicaciones fuera de la placa.

Para ello se opto por enviar a la pagina web la variable que se optiene de la función camera.capture() que viene en el fimware cargado a la placa. Ello trajo otro inconveniente ya que en dicha variable se carga un archivo jpg que no resulta legible por el html de la pagina web donde hay que colocar la informacion en formato string. Por ello primero se convirtieron los datos al formato Base24 y luego la cadena de bit resultante se conviertio en un string. Para ello se inportó la biblioteca ubinascii> y se usaron las funciones ubinascii.b2a_base64(img) y decode respectivamente.

Codigo


El codigo se ha escrito en tres archivos boot.py, main.py y pagina.py.

El archivo boot.py ademas de contener los datos necesarios para el arranque de la placa, contiene los datos de la red wifi a la que se va a conectar los cuales deben ser agregados en el lugar indicado.

El archivo main.py es el nucleo del servidor, el nombre elegido para el mismo es a efectos que el programa se inicie automaticamente. Este archivo contiene los llamadas y respuestas a las paginas web y las funciones correspondientes al manejo de los pines de la placa.

El archivo pagina.py esta destinado a contener las distintas paginas html que seran servidas por el servidor conforme las llamadas que se realicen a traves de la funcion 'pagina_web(ped, muestra):' que se llama desde el archivo main.py. Cabe destacar que ped indica el numero de la pagina html que se debe servir y muestra se utiliza cuando es necesario pasar datos a la pagina.


boot.py



        from machine import Pin, reset
        import network
        import socket
        import time
        
        
        import esp
        esp.osdebug(None)
        
        #Recupera la memoria ocupada por objetos que no son necesarios para el programa
        import gc
        gc.collect()
        
        #Coneccion WiFi
        #completar con los datos correspondientes
        red = ' '#nombre de la red
        clave = ' ' #clave de la red
        
        indicator = Pin(0, Pin.OUT)
        wlan = network.WLAN(network.STA_IF)
        if not wlan.isconnected():
            wlan.active(True)
            wlan.connect(red, clave)
            print('Conectando a: %s' % red)
            timeout = time.ticks_ms()
            while not wlan.isconnected():
                indicator.on()
                time.sleep(0.15)
                indicator.off()
                time.sleep(0.15)
                if (time.ticks_diff (time.ticks_ms(), timeout) > 10000):
                    break
            if wlan.isconnected():
                indicator.on()
                print('Se conecto a: %s' % red)
                print('IP: %s\nSUBNET: %s\nGATEWAY: %s\nDNS: %s' % wlan.ifconfig()[0:4])
            else:
                indicator.off()
                wlan.active(False)
                print('Fallo la conexion a: %s' % ssid)
        else:
            indicator.on()
            print('Conectado a\nIP: %s\nSUBNET: %s\nGATEWAY: %s\nDNS: %s' % wlan.ifconfig())
    

main.py



        import ubinascii
        import os
        import pagina
        import camera
        from time import sleep
        import machine
        #Pin del led de flash
        led = machine.Pin(4, machine.Pin.OUT)
        foto=1
        contenido = ""
        #Sacar foto
        def captura(fila):
            #Captura la imagen
            img = camera.capture()
            #desactivar cámara
            camera.deinit ()
            # conversion de la imagen para la pagina web
            conversion = ubinascii.b2a_base64(img)
            cadena = conversion.decode()
            return cadena
        def accion():
            global foto
            nombre = "foto"
            try:
                camera.init(0, format=camera.JPEG, fb_location=camera.PSRAM)
                #Establece el brillo -2 (mas bajo) y 2 (mas alto).
                camera.brightness(1)
                #Orientacion normal
                camera.flip(0)
                #Orientación normal
                camera.mirror (0)
                #Resolución
                camera.framesize(camera.FRAME_VGA)
                #contraste -2 a 2 (alto)
                camera.contrast(1)
                #saturacion -2 (baja intensidad) y 2 (alta intensidad)
                camera.saturation (0)
                #calidad 10(alta) a 63
                camera.quality(10)
                #special effects
                camera.speffect(camera.EFFECT_NONE)
                #white balance
                #WB_NONE = sin compensación
                #WB_SUNNY = compensa días soleados
                #WB_CLOUDY = compensa días nublados
                #WB_OFFICE = compensa iluminación fría
                #WB_HOME = compensa iluminación cálida
                camera.whitebalance(camera.WB_CLOUDY)
                fotograma = captura(nombre)
                foto+=1
                sleep (2)
            except Exception as err:
                print ("Error= "+str (err))
                sleep (2)
            return fotograma
        
        #Pagina Web
        def pagina_web(item, recibo):
            html = pagina.pagina(item, recibo)
            return html
        
        #Socket Configuracion
        try:
            tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            tcp_socket.bind(('', 80))
            tcp_socket.listen(5)
            time.sleep(1)
            print('Configuracion de socket exitosa\n')
        except OSError as e:
            print('Fallo configuracion de socket. Reeinicia...\n')
            time.sleep(3)
            reset()
        print('Listo...!\n********************************\n')
        while True:
            try:
                if gc.mem_free() < 102000:
                gc.collect()
                conn, addr = tcp_socket.accept()
                conn.settimeout(3.0)
                print('Nueva conexion de: %s' % str(addr[0]))
                request = conn.recv(1024)
                conn.settimeout(None)
                request = str(request)
                item = 1
                if request.find('/sacafoto') == 7:
                    contenido = accion()
                    item = 2
                conn.send('HTTP/1.1 200 OK\n')
                conn.send('Content-Type: text/html\n')
                conn.send('Connection: close\n\n')
                conn.sendall(pagina_web(item, contenido))
                conn.close()
            except OSError as e:
                conn.close()
            time.sleep(0.1)
      
    

pagina.py



        def pagina(ped, muestra):
        if ped == 1:
            pagina_html = """
                <!DOCTYPE html>
                <html>
                <head>
                    <title>MicroPython</title>
                    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
                    <meta name="viewport" content="width=device-width, initial-
    scale=1">
                        <style type="text/css">
    
                        .nav{
                            box-sizing: border-box; display: inline-block;
                            padding: 10px 0px 10px 0px; background-color:#F8DB9C;
                            width: 100%; height: auto;
                        }
                        #t1{
                            font-family: Helvetica; font-weight: bold;
                            text-align: center; font-size: 50px; color: red;
                        }
                        #t2{
                            font-family: Helvetica; font-weight: bold;
                            text-align: center; font-size: 20px; color: blakc;
                        }
                    </style>
                </head>
                <body>
                    <div class='nav'>
                        <h1 id='t1'>Servidor Web Esp32-Cam</h1>
                        <h2 id='t2'>Sacar Fotos</h2>
                    </div>
                    <div>
                        <center>
                        <br><br><br>
                        <form action="/sacafoto" method="post">
                        <button type="submit">Sacar Foto</button>
                        </form>
                        </center>
                    </div>
                </body>
                </html>
                """
            return pagina_html
    
        if ped == 2:
            linea = "'data:image/jpeg;base64," + str(muestra)
            pagina_html = """
                <!DOCTYPE html>
                <html>
                <head>
                    <title>MicroPython</title>
                    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
                    <meta name="viewport" content="width=device-width, initial-
    scale=1">
                    <style type="text/css">
                        .nav{
                            box-sizing: border-box; display: inline-block;
                            padding: 10px 0px 10px 0px; background-color:#F8DB9C;
                            width: 100%; height: auto;
                        }
                        #t1{
                            font-family: Helvetica; font-weight: bold;
                            text-align: center; font-size: 50px; color: red;
                        }
                        #ima{
                            width: 40%; height: auto;
                        }
                    </style>
                </head>
                <body>
                    <center>
                    <div class='nav'>
                        <h1 id='t1'>Servidor Web Esp32-Cam</h1>
                        <h2 id='t2'>Foto</h2>
                    </div>
                    <div>
                        <img id='ima' src=""" + linea + """'>
                    </div>
                    <br><br><br>
                    <form action="/" method="post">
                    <button type="submit">Inicio</button>
                    </form>
                    </center>
                </body>
                </html>
                """
            return pagina_html
        if ped == 0:
            pagina_html = """<html>
                <head>
                </head>
                <body align="center">
                    <br>
                    <h1>Pagina inexistente</h1>
                </body>
                </html>"""
            return pagina_html
      
    

Cerrar