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()