12.6. Canales con nombre¶
El ID de canal en la cabecera de cada paquete permite que hasta 32 flujos independientes compartan el mismo transporte físico. La capa de canal convierte esos ID numéricos en puntos finales con nombre, visibles para la aplicación, a los que el código del host puede referirse mediante una cadena.
12.6.1. Los cuatro canales integrados¶
La cámara registra cuatro canales al arrancar, antes de que se ejecute cualquier código de aplicación:
stdin– bytes de script que el host envía a la cámara para ejecutar. El IDE usa este canal para enviar el script que se está editando;exec()en el SDK del host es la llamada equivalente desde un programa de Python.stdout– bytes de las llamadasprint()de la cámara y de los rastreos de excepciones no capturadas. La consola serie del IDE lee este canal.stream– el canal de vista previa en vivo. El IDE extrae fotogramas JPEG de él; cualquier script del host puede hacer lo mismo conread_frame().profile– eventos del generador de perfiles, presente solo cuando la cámara se compiló con la generación de perfiles habilitada. La mayoría de las compilaciones de lanzamiento lo omiten.
El código de aplicación rara vez necesita tocar alguno de los canales integrados; el trabajo interesante ocurre en los canales que la aplicación registra por sí misma.
12.6.2. Registrar un canal¶
Un script del lado de la cámara registra un nuevo canal llamando a protocol.register() con un nombre y un objeto backend de Python:
import json
import protocol
import time
trigger_count = 0
class StatusChannel:
def size(self):
# Refresh the snapshot on every host query.
self._buf = json.dumps({
'uptime_s': time.ticks_ms() // 1000,
'triggers': trigger_count,
}).encode()
return len(self._buf)
def read(self, offset, size):
return self._buf[offset:offset + size]
protocol.register(name='status', backend=StatusChannel())
Los métodos del objeto backend deciden lo que puede hacer el canal. Un backend con solo size y read es un canal de datos de solo lectura; añade write y se vuelve bidireccional; añade poll y el host puede preguntar si hay datos nuevos listos antes de pagar por una lectura. Muestrear los datos dentro de size es el patrón más simple cuando la carga útil es lo suficientemente pequeña para caber en un fragmento: el búfer se genera bajo demanda, nunca se almacena en caché, nunca hay condiciones de carrera. Las cargas útiles más grandes – fotogramas de imagen, trazas de sensores – necesitan un patrón de retención que mantenga el búfer hasta que el host termine su lectura de varios fragmentos, lo cual se trata con el canal de fotogramas.
Una pequeña cantidad de contabilidad ocurre automáticamente:
La biblioteca asigna el siguiente ID de canal libre (entre 0 y 31).
Los indicadores de capacidad se derivan de los métodos presentes:
CHANNEL_FLAG_READsi se defineread,CHANNEL_FLAG_WRITEsi se definewrite,CHANNEL_FLAG_LOCKsi se definenlock/unlock.Se envía un paquete de evento
CHANNEL_REGISTEREDa cualquier host conectado para que su lista de canales se actualice.
El valor de retorno es un manejador protocol.ProtocolChannel que la aplicación puede conservar. El método send_event() del manejador es el punto de enganche del lado de la cámara para decirle al host «ocurrió algo en este canal sin que cambien los datos legibles»: se disparó un activador, se presionó un botón, se alcanzó un hito de recuento de muestras.
12.6.3. Leer canales desde el host¶
El SDK del host se distribuye como el paquete openmv en PyPI (pip install openmv), construido sobre pyserial para el transporte. Su clase openmv.camera.Camera expone los canales con nombre de la cámara a través de métodos de alto nivel:
from openmv.camera import Camera
with Camera('/dev/ttyACM0', baudrate=921600) as cam:
cam.update_channels()
if cam.has_channel('status'):
size = cam.channel_size('status')
data = cam.channel_read('status', size)
Advertencia
El paquete openmv requiere CPython 3.12 o más reciente. Los intérpretes anteriores carecen de características de las que depende el SDK; instala una compilación 3.12+ antes de pip install openmv.
Algunas cosas que conviene observar sobre la configuración:
La cadena del puerto serie – aquí
/dev/ttyACM0– tiene el estiloCOM3en Windows,/dev/cu.usbmodemXXXXen macOS y/dev/ttyACM*en Linux. El número real depende del puerto con el que se enumeró la cámara.La velocidad en baudios es el valor mágico del protocolo
921600, que la pila USB-CDC de la cámara reconoce como «este cliente habla el protocolo, no el REPL». Cualquier otra velocidad recurre a una línea serie simple.El administrador de contexto
with Camera(...) as cam:abre el transporte, ejecutaPROTO_SYNC, intercambia capacidades y, al salir, cierra el puerto de forma limpia. La llamada explícita aupdate_channels()tras la entrada actualiza la lista local de canales con cualquier canal que la aplicación haya registrado después del arranque.
channel_size() y channel_read() son los métodos de trabajo principales; channel_write() envía un búfer de ida y vuelta a la cámara si el backend tiene un método write; has_channel() es la forma segura de comprobar que un nombre está registrado antes de usarlo. El nombre del canal se busca una vez para obtener el ID de canal que la cámara asignó durante register y se usa en cada paquete a partir de entonces.
Cada par channel_size() / channel_read() cuesta dos viajes de ida y vuelta: un paquete para pedir el tamaño, otro para pedir los bytes. Sobre USB-CDC ambos terminan en aproximadamente un milisegundo combinados; sobre UART el mismo intercambio tarda más en proporción a la velocidad en baudios de la línea serie. El código de aplicación que lee en un bucle cerrado debería llamar a channel_size() solo cuando el tamaño pueda cambiar realmente: para datos de tamaño fijo, el tamaño de la primera llamada puede almacenarse en caché.
12.6.4. Independencia entre canales¶
Hay tres cosas que vale la pena saber sobre cómo interactúan los canales:
Control de flujo independiente. Cada canal tiene su propio estado de lectura pendiente, sus propios datos y sus propias funciones de retorno
size/read/write. Una lectura de larga duración en el canalstreamno bloquea las lecturas en el canalconfigde la aplicación.Secuencial por canal. Dentro de un solo canal, los paquetes se entregan en orden. La capa de fiabilidad lo garantiza incluso cuando hay retransmisiones de por medio.
Transporte compartido, presupuesto de retransmisión compartido. Todos los canales comparten el único enlace físico, por lo que un torrente de tráfico en un canal ralentiza a los demás al acaparar el cable. El mecanismo
CHANNEL_LOCKpermite que un canal reserve el cable para una lectura atómica de varios paquetes; el backend opta por ello implementando las funciones de retornolock/unlock.
Un canal es la mínima superficie de contacto sobre la que un programa del host y un programa de la cámara acuerdan cooperar. El nombre, la direccionalidad (lectura o escritura o ambas), los métodos de retorno del lado de la cámara y las llamadas a métodos correspondientes del lado del host son todo el contrato.