Servidor Web de video con Esp32-Cam y 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 utilizamos la Camara Ov2640 que viene integrada a la placa Esp32-Cam para capturar video que puede verse online en el servidor instalado en la misma placa .

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/lemariva/micropython-camera-driver/tree/master/firmware

Cabe destacar que en el link indicado hay dos firmware micropython_camera_feeeb5ea3_esp32_idf4_4.bin y micropython_cmake_9fef1c0bd_esp32_idf4.x_ble_camera.bin y usaremos el primero de ellos

Tambien para el correcto funcionamiento debera descargarse y descomprimirse la carpeta uasyncio.zip que debera copiarse en la placa Esp32_cam despues de instalar el 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.


Codigo


El codigo se ha escrito en dos archivos Conexion.py y main.py.

El archivo Conexion.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.

El archivo main.py es el nucleo del servidor, el nombre elegido para el mismo es a efectos que el programa se inicie automaticamente.


Conexion.py



     
from time import sleep
import network

class Conexion:

   red = "nombre de la red" 
   clave = "clave de la red"

   def __init__(mi, red='', clave=''):
      network.WLAN(network.AP_IF).active(False) # disable access point
      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
    

main.py



        import usocket as soc
        import uasyncio as sy
        import camera
        import time
        import esp
        from machine import Pin
        import gc
        from Conexion import Conexion
        
        #esp.osdebug(False)
        esp.osdebug(True)
        
        
        def iniciarcamara():
            wc = 0
            while True:
              global camara  
              camara = camera.init(0, format=camera.JPEG)
              camera.framesize(7)
              camera.quality(15)
              print("¿Cámara lista?: ", camara)
              if camara:
                break
              time.sleep(2)
              wc += 1
              if wc >= 5:
                break
        
        pag_inicio = """HTTP/1.1 200 OK
        Content-Type: text/html; charset=utf-8
        
        <html>
        <head>
        <title>Transmisión de video</title>
        </head>
        <body>
          <center>
            <h1>Transmisión de video</h1>
            
            
          </center>
        </body>
        </html>
        
        """
        
          # página de inicio para la transmisión
          # URL: /camara_web
        pag_camara = """HTTP/1.1 200 OK
        Content-Type: text/html; charset=utf-8
        
        <html>
        <head>
        <title>Transmisión de video</title>
        </head>
        <body>
          <center>
            <h1>Transmisión de video</h1>
            <img src="http://192.168.0.157:81/" width=720 height=540 />
          </center>
        </body>
        </html>
        
        """
          # transmisión en vivo
          # URL: /emision
        pag_emision = """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
        
        """
          # transmisión en vivo
          # URL: 
        pag_marco = """--frame
        Content-Type: image/jpeg
        
        """
        
          # error de solicitud
          # URL: /favicon.ico
        pag_favicon = """HTTP/1.1 404 
        
        
        """
          
          # URL: all the rest
        pag_error = """HTTP/1.1 400 Bad Request
        Content-Type: text/plain; charset=utf-8
        
        No se puede transmitir
        
        """
        
        
        
        def borrar(cs):
           
           cs.close() # flash buffer y cierre socket
           del cs
           gc.collect()
        
        def crear_cuadro():
           
           #buf = b' '
           while True:
             buf = camera.capture()
             yield buf
             del buf
             gc.collect()
        
        async def enviar_marco(pp):
           
           cs, h = pp
           while True:
              ee = ''
              try:
                 cs.send(b'%s' % h)
                 
                 cs.send(next(pic))
                 cs.send(b'\r\n')  # enviar y vaciar el búfer de envío
              except Exception as e:
                ee = str(e)
              if ee == '': 
                await sy.sleep_ms(5)  # intentar lo más rápido posible
              else:
                break
        
           borrar(cs)
           return   
        
        def servidores():
           socks = []
           # port 80 server - streaming server
           s = soc.socket(soc.AF_INET, soc.SOCK_STREAM)
           s.setsockopt(soc.SOL_SOCKET, soc.SO_REUSEADDR, 1)
           a = ('0.0.0.0', 80)
           s.bind(a)
           s.listen(3)  # at most 3 clients
           socks.append(s)
        
           # port 81 server - still picture server
           s = soc.socket(soc.AF_INET, soc.SOCK_STREAM)
           s.setsockopt(soc.SOL_SOCKET, soc.SO_REUSEADDR, 1)
           a = ('0.0.0.0', 81)
           s.bind(a)
           s.listen(3)
           socks.append(s)
        
           return socks
        
        async def puerto1(cs, rq):
        #   print("port1")
        #   print(rq[1])
        #   print("****************************")
        
           if rq[1] == '/': 
                 cs.send(b'%s' % pag_inicio)
                 
           elif rq[1] == '/pag_camara': 
                 cs.send(b'%s' % pag_camara)
                 
                 
           else:
                 cs.send(b'%s' % pag_error)
                 borrar(cs)
                 
        
        
        async def puerto2(cs, rq):
        #   print("port2")
        #   print(rq[1])
        #   print("****************************")
        
           
           if rq[1] == '/': 
                 cs.send(b'%s' % pag_emision)
                 # programar marco de envío
                 await enviar_marco([cs, pag_marco])
                 
           else:
                 cs.send(b'%s' % pag_error)
                 borrar(cs)
        
        
        
        async def srv(p):
          sa = socks[p] # servidor programado
          while True:
             ok = True
             ee = ''
             yield
             try:
                sa.settimeout(0.05) # en segundos ¡NOTA! tiempo de espera predeterminado del navegador (5-15 min)
                cs, ca = sa.accept()
                cs.settimeout(0.5) # en segundos
             except Exception as e:
                ee = str(e)
             if ee != '':
                
                pass
                ok = False
             yield
             if ok: 
                ee = ''
                try:
                   # cliente aceptado 
                   r = cs.recv(1024)
                   
                except Exception as e:
                   ee = str(e)
                if ee != '':
                   print(ee)
                   ok = False
                else:
                   ms = r.decode('utf-8')
                   if ms.find('favicon.ico') < 0:
                      rq = ms.split(' ')
        #              print("rq**********************")
        #              print(ms)
        #              print("**********************")
                      try:
                         print(rq[0], rq[1], ca)
                      except:
                         ok = False
                   else:
                      cs.send(b'%s' % pag_favicon) 
                      borrar(cs)
                      ok = False
             yield
             if ok: 
                await puertos[p](cs, rq)
        
        
        
        
        iniciarcamara()
        
        
        
        if not camara:
          print("La cámara no está lista. ¡No puede continuar!")
        else:
          conec = Conexion()
          conec.conectar()
          conec.esperar()
          wc = 0
          while not conec.wlan.isconnected():
             print("Wi-Fi no está listo. Esperar...")
             time.sleep(2)
             wc += 1
             if wc >= 5:
               break
          if wc >= 5:
             print("WIFI no está listo. ¡No puede continuar!")
          else:
             pic = crear_cuadro()
             flash_light = Pin(04,Pin.OUT)
             socks = servidores()
             puertos = [puerto1, puerto2] # 80, 81
             loop = sy.get_event_loop()
             i = 0
             for i in range(len(socks)):
        #        print("****************************") 
        #        print(i) 
                if i == 0:
                    
                   loop.create_task(srv(i)) 
                   loop.create_task(srv(i)) 
                                            
                else:
                   loop.create_task(srv(i)) 
        
        
             print("Servidor y camara funcionando!")
             loop.run_forever()
        
    

Cerrar