Servidor Web de video con Esp32-Cam y MicroPython




Presentación


Este proyecto es una versión simplificada del Servidor Web de video con Esp32-Cam para capturar video que puede verse online en el servidor instalado en la misma placa, sin utilizar la librería uasyncio .

Una vez arrancada la placa nos muestra en la consola el IP asignado y al ingresar a traves del navegador se ve la pantalla con el video que se obtiene a traves de la camara. La imagen de la pantalla es la siguiente:



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 este proyecto se utilizo el provisto por la pagina de https://github.com/shariltumin/esp32-cam-micropython-2022/blob/main/firmware.bin

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.


Codigo


El codigo se ha escrito en dos archivos conec_wifi.py, main.py, site.py y html.py.

El archivo conec_wifi.py contiene los datos necesarios para la conexión a la red wifi a la que se va a conectar los cuales deben ser agregados en el lugar indicado.

Los archivos main.py y site.py son el nucleo del servidor, el nombre elegido para el primero es a efectos que el programa se inicie automaticamente.

Finalmente el archivo html.py contiene la página html que nos permite visualizar el video de la camara.


conec_wifi.py




#Configuracion de conexion WIFI:

from time import sleep
import network
        
class conexion:
        
           red = "XXXXXXXXX" 
           clave = "xxxxxxxxx"
        
           def __init__(mi, red='', clave=''):
              network.WLAN(network.AP_IF).active(False)# Scan for available access points
              mi.wlan = network.WLAN(network.STA_IF)
              mi.wlan.active(True)
              if red == '':
                mi.red = conexion.red
                mi.clave = conexion.clave 
              else:
                mi.red = red
                mi.clave = clave
        
           def conectar(mi, red='', clave=''):
              if red != '':
                mi.red = red
                mi.clave = clave
        
              if not mi.wlan.isconnected(): 
                mi.wlan.connect(mi.red, mi.clave)
        
           def status(mi):
              if mi.wlan.isconnected():
                return mi.wlan.ifconfig()
              else:
                return ()
        
           def esperar(mi):
              cnt = 30
              while cnt > 0:
                 print("Esperando ..." )
                 # con(mi.red, mi.clave) # Connect to an red
                 if mi.wlan.isconnected():
                   print('Se conecto a: %s' % mi.red)
                   print('IP: %s\nSUBNET: %s\nGATEWAY: %s\nDNS: %s' % mi.wlan.ifconfig()[0:4])
                   cnt = 0
                 else:
                   sleep(5)
                   cnt -= 5
              return
        
           def scan(mi):
              return mi.wlan.scan()   
             


    

main.py




from machine import reset
from time import sleep
import usocket as soc
import gc

import camera
from conec_wifi import conexion
import site
        
        
def clean_up(cs):
           cs.close() 
           del cs
           gc.collect()
        
        
def server(pm):
          p=pm[0]
          ss=soc.socket(soc.AF_INET, soc.SOCK_STREAM)
          ss.setsockopt(soc.SOL_SOCKET, soc.SO_REUSEADDR, 1)
          sa = ('0.0.0.0', p)
          ss.bind(sa)
          ss.listen(1) # cantidad de clientes simultaneos
          print("Iniciar servidor", p)
        
          while True:
             ms='';rq=[]
             try:
                cs, ca = ss.accept()
             except:
                pass
             else:
                r=b'';e=''
                try:
                   r = cs.recv(1024)
                   #print("r")
                   #print(r)
                except Exception as e:
                   print(f"EX:{e}")
                   clean_up(cs)
                try:
                   ms = r.decode()
                   rq = ms.split(' ')
                except Exception as e:
                   print(f"RQ:{ms} EX:{e}")
                   clean_up(cs)
                else:
                   if len(rq)>=2:
                      print(ca, rq[:2])
                      rv,ph=rq[:2]  
                      rqp = ph.split('/')
                      pth=f'/{rqp[1]}'
                      site.app[pth](cs)
                      clean_up(cs)
                      continue
        
for i in range(5):
            cam = camera.init()
            print("Cámara esta lista?: ", cam)
            if cam: break
            else: sleep(2)
else:
            print('Se acabó el tiempo')
            reset() 
        
if cam:
           print("Cámara lista")
           conec = conexion()
           conec.conectar()
           conec.esperar()
           for i in range(5):
               if conec.wlan.isconnected(): break
               else: print("WIFI no listo. Esperar...");sleep(2)
           else:
              print("WIFI no listo. ¡No puedo continuar!")
              reset()
        
        #**********************************************************************
        # configuración de la cámara
        
        #pixformat':    0:JPEG, 1:Escala de grises (2bytes/pixel), 2:RGB
        
        #framesize':    1:96x96, 2:160x120, 3:176x144, 4:240x176, 5:240x240
        #               6:320x240, 7:400x296, 8:480x320, 9:640x480, 10:800x600
        #               11:1024x768, 12:1280x720, 13:1280x1024, 14:1600x1200
        #               15:1920x1080, 16:720x1280, 17:864x1536, 18:2048x1536
        
       #quality:       [0,63] un número más bajo significa una calidad más alta
        
        #contrast:      [-2,2] mayor número mayor contraste
        
        #saturation:    [-2,2] mayor número mayor saturación. -2 escala de grises
        
        #brightness:    [-2,2] mayor número mayor brillo. 2 más brillantes
        
        #speffect:      0: sin efecto, 1:negativo, 2:blanco y negro, 3:rojizo,
        #               4: verdoso, 5: azul, 6: retro
        
        #whitebalance:  0:predeterminado, 1:soleado, 2:nublado, 3:oficina, 4:casa
        
        #aelevels:      [-2,2] Nivel AE: exposición automática
        
        #aecvalue:      [0,1200] Valor AEC: Control automático de exposición
        
        #agcgain:       [0,30] Ganancia AGC: Control automático de ganancia
        
        #Los valores numericos son siempre numeros enteros
        #****************************************************************************
        
        # establecer la configuración de la cámara
        
camera.framesize(8)      # frame size 480x320
camera.contrast(2)       # maximo contraste
camera.speffect(0)       # por defecto
        
        
site.ip=conec.wlan.ifconfig()[0]
site.camera=camera
       
server((80,))  # puerto 80
reset()
        
        
        
    

site.py




from html import pagina, emision

app={}
def route(p):
    def w(g):
        app[p]=g
    return w

@route('/')
def root(cs):
    p=pagina['imagen']%(f'http://{ip}/live',0) 
    ln=len(p)+2
    cs.write(b'%s\r\n\r\n%s\r\n' % (emision['imagen']%ln, p))

@route('/live')
def live(cs): 
    cs.write(b'%s\r\n\r\n' % emision['stream'])
    cs.setblocking(True)
    pic=camera.capture
    put=cs.write
    hr=emision['cuadro']
    while True:
       try:
          put(b'%s\r\n\r\n' % hr)
          put(pic())
          put(b'\r\n')
       except Exception as e:
          print(e)
          break
        
    

html.py




        pagina = {
            'imagen':'''<!DOCTYPE html>
          <html>
          <head>
          <title>ESP32 Camera</title>
          <link rel="icon" href="data:image/gif;base64,R0lGODlhAQABAAAAACwAAAAAAQABAAA=">
          </head>
          <body>
            <h1 align:center>Cámara Esp32-Cam</h1> 
            <div style="display:flex;  margin-top: 15%%; justify-content:center; align-items:center; height:600px;">
              <img src="%s" style="height:100%%; transform:rotate(%sdeg);"/>
            </div>
          </body>
          </html>
          ''',
          }
          
        emision = {
            'imagen': """HTTP/1.1 200 OK
          Content-Type: text/html; charset=utf-8
          Connection: Closed
          Content-Length: %d""",
          
            'stream': """HTTP/1.1 200 OK
          Content-Type: multipart/x-mixed-replace; boundary=frame
          Connection: keep-alive
          Cache-Control: no-cache, no-store, max-age=0, must-revalidate
          Expires: Thu, Jan 01 1970 00:00:00 GMT
          Pragma: no-cache""",
            
            'cuadro': """--frame
          Content-Type: image/jpeg""",
          }
          
        
    

Cerrar