← Índice del curso

Internet de las Cosas · Tema 43

Actualización remota (OTA)

Una placa instalada necesita recibir correcciones sin volver al banco de trabajo. Pero descargar un archivo no alcanza: debe corresponder al equipo, llegar completo, superar las verificaciones y entrar en ejecución en un momento previsto. Vamos a separar esas etapas y observar qué pasa si la red o la alimentación interrumpen el proceso.

01 · El software también tiene mantenimiento

Actualizar un dispositivo instalado

OTA, de Over The Air, denomina la actualización que llega mediante una conexión de comunicación, sin conectar el programador local para cada cambio. En este curso nos interesa el firmware de aplicación que una placa recibe por la red.

Los temas 41 y 42 aportan confianza en el servicio, identidad del dispositivo y permisos. OTA utiliza esos controles para obtener una publicación autorizada y compatible. La identidad de un equipo no concede por sí sola permiso para distribuir firmware a otros, y su credencial operativa no debe convertirse en la clave de firma de versiones.

Resultados diferentes dentro de una actualización.
ResultadoQué demuestraQué falta
Publicación anunciadaHay una versión candidataAutorización, compatibilidad y descarga
Descarga al 100 %Se recibió la cantidad de bytes esperadaVerificación de imagen y procedencia
Imagen verificadaSuperó las comprobaciones configuradasSelección y arranque controlados
Imagen seleccionadaSe preparó el próximo arranqueEjecutar y evaluar la nueva aplicación
Nueva versión ejecutándoseEl equipo inició ese firmwareComprobar salud y confirmar su aceptación

El tema 44 profundiza firmas, particiones, confirmación de salud y reversión. Aquí presentamos su función dentro del proceso y trabajamos sobre la descarga y sus transiciones. El laboratorio es una simulación: no descarga binarios, no escribe flash y no reinicia la computadora.

02 · Publicación, distribución y aplicación

Quién publica y quién decide

Una actualización involucra varias responsabilidades. El equipo de producto construye y prueba un artefacto; un proceso autorizado lo publica; un servicio lo distribuye; y cada dispositivo aplica las políticas de compatibilidad, confianza y operación local antes de usarlo.

Responsabilidades que pueden vivir en servicios distintos.
ActorResponsabilidad
Construcción y publicaciónProducir un artefacto identificable, probarlo y autorizar su entrega
Catálogo o servicio de versionesInformar qué publicación corresponde a cada producto o grupo
Repositorio de imágenesEntregar los bytes del artefacto sin cambiarlos bajo la misma identidad
Gestor OTA del equipoDescargar, comprobar y preparar el arranque con recursos acotados
Bootloader y aplicación nuevaArrancar bajo la política prevista y evaluar el resultado

Consulta y notificación

Un equipo puede consultar periódicamente el catálogo o recibir un aviso por su canal de gestión. El aviso puede contener una referencia de publicación, pero no obliga a aceptar cualquier URL o binario incluido en un mensaje. El dispositivo vuelve a verificar autorización, destino y compatibilidad.

Un mecanismo de consulta necesita intervalo, variación entre equipos y esperas tras fallos. Una notificación necesita autenticación, límites e idempotencia. Recibir dos veces el anuncio de build 2 no debería abrir dos descargas simultáneas.

La arquitectura de actualización de firmware de RFC 9019 describe funciones y manifiestos para IoT, independientes del transporte. Sirve como referencia de arquitectura; el JSON y la máquina de estados de esta lección son ejemplos propios, no una implementación de SUIT.

Publicaciones reproducibles para la operación

Conviene conservar la relación entre código, configuración de compilación, bibliotecas, hardware y artefacto publicado. El nombre latest.bin puede ser una referencia de catálogo, pero no es suficiente como identidad inmutable de los bytes que ya está descargando un equipo.

03 · Elegir antes de transferir

Describir una publicación

Los metadatos de publicación permiten decidir si vale la pena descargar. Deben vincular la identidad del artefacto con las condiciones que lo hacen aplicable. La confianza en esos metadatos forma parte de la política: un atacante no debe poder sustituir a la vez imagen y descripción para conseguir su aceptación.

{
  "release_id": "demo-A-build2",
  "producto": "placaA",
  "revision_hw": "A1",
  "build": 2,
  "esquema_datos": 1,
  "tamano_bytes": 8192,
  "sha256": "dc404a613fedaeb54034514bc6505f56b933caa5250299ba7d094377a51caa46",
  "url": "https://firmware.curso.example/releases/demo-A-build2.bin"
}

Es una publicación ficticia. El dominio .example no ofrece un servicio del curso. La huella corresponde a los datos sintéticos del ejemplo Python, no a un firmware grabable. El formato no incluye una envoltura de firma y no debe tratarse como un protocolo listo para producción.

Compatibilidad más allá del nombre

Dos placas con el mismo microcontrolador pueden tener sensores, salidas, tamaños de flash y cableados distintos. Una imagen válida para una podría controlar incorrectamente la otra. También se revisan dependencias de bootloader, memoria, configuración persistida y esquema de datos.

Un firmware puede arrancar y aun así interpretar mal datos escritos por la versión anterior. La migración necesita una estrategia que contemple interrupciones y la posibilidad de ejecutar una versión previa. No se modifican a ciegas todas las estructuras persistidas al ver un número de versión diferente.

Número de build y política de versiones

El modelo usa enteros de build para simplificar: solo admite un valor mayor que el ejecutado. No compara cadenas como "1.10" y "1.9" lexicográficamente. Una política real puede autorizar reinstalaciones o regresos concretos, y distinguir versión comercial, build y nivel mínimo de seguridad.

Rechazar una versión anterior en la aplicación no equivale a implementar protección de retroceso resistente a manipulación. Esa protección y su relación con la reversión se desarrollan en el tema siguiente.

04 · Mantener una aplicación ejecutable

Dónde se escribe la imagen

Un diseño habitual descarga la aplicación nueva en un destino inactivo mientras sigue ejecutando la actual. Después de completar las comprobaciones prepara la selección para el próximo arranque. Así, una descarga parcial no reemplaza directamente el código que el equipo necesita para seguir funcionando.

Transferencia a un destino inactivoEl repositorio envía bloques al destino inactivo. La aplicación actual continúa ejecutándose. Después de verificar la imagen se registra la selección para el próximo arranque. Descargar no cambia todavía la aplicación en ejecución.Repositorio autorizadoimagen por bloquesDestino inactivorecibir y verificarsin reemplazar la actualAplicación actualmantiene control localhasta el reinicio previstoSelección de próximo arranquesolo después de verificar

En ESP-IDF, la OTA de aplicación utiliza slots y datos de selección de arranque. La documentación de OTA distingue el tratamiento de aplicación del de bootloader, tabla de particiones y otras particiones. La resistencia a cortes de una ruta no se extrapola a todas las escrituras de flash.

El tamaño que importa

Se compara el tamaño final de la imagen con la capacidad del destino, incluyendo lo que requiera su formato. Tener espacio libre en RAM o en un sistema de archivos no demuestra que el slot de aplicación sea suficiente. Una imagen comprimida pequeña puede necesitar un destino mayor al descomprimirse.

El modelo usa imágenes de 8192 bytes y un destino de 12288 bytes para que las cuentas sean fáciles. Son tamaños didácticos: no representan el tamaño normal de un firmware ESP32 ni constituyen una tabla de particiones utilizable.

La aplicación en ejecución, el gestor de descarga y el bootloader deben compartir una política coherente. No se cambia una dirección de escritura a mano para hacer entrar una imagen más grande. La disposición de particiones se decide antes del despliegue y se verifica con el artefacto real.

05 · Recursos acotados y progreso verificable

Descargar por bloques

La imagen completa no necesita caber en RAM. El receptor puede usar un buffer acotado: recibe una parte, comprueba el resultado de la lectura, la entrega a la API de escritura y confirma el avance según las garantías del almacenamiento.

Fijar publicación y tamaño esperado
Abrir una transferencia autenticada
Mientras falten bytes:
  leer hasta el tamaño del buffer, con plazo
  comprobar error, fin prematuro y límites
  escribir con la API OTA y comprobar su resultado
  avanzar solo por los bytes confirmados
Comprobar tamaño final, imagen y procedencia
Preparar el próximo arranque bajo la política local

Una lectura puede entregar menos bytes que el tamaño solicitado sin completar el archivo. También puede terminar antes de tiempo. No se suma el tamaño del buffer si solo se recibieron algunos bytes, ni se considera completo un archivo porque el servidor cerró la conexión.

progreso = bytes confirmados / tamaño esperado × 100
8192 bytes ÷ 2048 bytes por bloque = 4 bloques completos

El 100 % del laboratorio significa transferencia completa, no imagen aprobada. El modelo recibe bloques exactos de hasta 2048 bytes; una implementación real admite lecturas parciales y valida límites de longitud. Si el tamaño no es múltiplo del buffer, el último bloque contiene solo el resto.

Tiempo, RAM y energía

El buffer es una parte del consumo: también cuentan TLS, pilas de tareas, estructuras OTA y otras funciones activas. La velocidad depende de red, servidor y flash. Por ejemplo, 1 MiB a un promedio de 64 KiB/s requiere aproximadamente 16 segundos solo para transferir, sin incluir negociación, comprobaciones ni arranque.

Se fijan tiempos de espera por operación y un límite para el intento completo. Una conexión que entrega unos pocos bytes de vez en cuando no debería mantener indefinidamente al equipo en mantenimiento. Los reintentos tienen esperas y límites; la lógica de control local conserva sus plazos y estados seguros.

06 · Completo no significa autorizado

Comprobar antes de seleccionar

Las comprobaciones tienen funciones diferentes: el tamaño detecta truncamiento o exceso; un resumen criptográfico permite comparar contenido; la validación del formato comprueba una imagen aceptable para la plataforma; y la autenticación de la publicación establece su procedencia autorizada.

HTTPS protege el transporte cuando se verifica correctamente al servidor. Una firma de firmware permite comprobar una autorización sobre el artefacto bajo una política de claves. Son capas diferentes. Una huella recibida junto con un archivo de una fuente no autenticada no prueba quién autorizó ninguno de los dos.

SHA-256 por partes con Python

hashlib permite alimentar SHA-256 incrementalmente. Este ejemplo compara datos sintéticos con una huella fija del ejercicio y detecta una alteración. No verifica firmas ni simula una descarga de red.

from hashlib import sha256

imagen = bytes(range(256)) * 32  # 8192 bytes de prueba; no es firmware
esperado = "dc404a613fedaeb54034514bc6505f56b933caa5250299ba7d094377a51caa46"


def resumir(datos, bloque=2048):
    h = sha256()
    for offset in range(0, len(datos), bloque):
        h.update(datos[offset:offset + bloque])
    return h.hexdigest()


assert len(imagen) == 8192
assert resumir(imagen) == esperado
assert resumir(imagen, 1000) == esperado  # último bloque parcial
alterada = bytearray(imagen)
alterada[3000] ^= 1
assert resumir(alterada) != esperado
assert resumir(imagen[:-1]) != esperado
print("Tamaño:", len(imagen), "bytes")
print("SHA-256:", resumir(imagen))
print("5 comprobaciones superadas.")

La huella esperada es un dato conocido del ejercicio. En el sistema real se obtiene de metadatos cuya integridad y autenticidad se verifican con el mecanismo elegido. No se confía en ella solo porque tenga 64 caracteres hexadecimales.

La verificación de firma y la protección de arranque requieren configuración real de la plataforma. Por ejemplo, Secure Boot v2 verifica código firmado en dispositivos compatibles. No se activa simplemente por descargar un archivo con extensión .bin. El tema 44 desarrolla estas garantías y la recuperación de una aplicación fallida.

07 · Una biblioteca que administra la transferencia

Integración con ESP32

ESP-IDF ofrece ESP HTTPS OTA. Su API simplificada transfiere la imagen y prepara el arranque cuando termina correctamente. Por eso no debe tratarse como una función que solo descarga un archivo.

Este fragmento ilustra la llamada, con URL y CA del producto. Presupone que el coordinador ya verificó la publicación y mantiene un estado local seguro durante toda la operación, además de conectividad, hora y configuración OTA correctas:

#include <stddef.h>
#include "esp_err.h"
#include "esp_http_client.h"
#include "esp_https_ota.h"

// Fragmento ESP-IDF: no es una aplicación completa.
// El coordinador valida URL/publicación y mantiene el modo seguro.
esp_err_t preparar_ota(const char *url_autorizada, const char *ca_pem) {
    if (url_autorizada == NULL || ca_pem == NULL) {
        return ESP_ERR_INVALID_ARG;
    }
    esp_http_client_config_t http = {
        .url = url_autorizada,
        .cert_pem = ca_pem,
        .timeout_ms = 10000,
    };
    esp_https_ota_config_t ota = {
        .http_config = &http,
    };
    // Bloqueante; en éxito también selecciona el próximo arranque.
    // La firma y demás políticas dependen de la configuración del producto.
    return esp_https_ota(&ota);
}

// ESP_OK no demuestra salud de la aplicación nueva.
// El reinicio se coordina explícitamente; no se ejecuta aquí.

La CA debe corresponder a la política del servicio y conservar una vida útil de memoria adecuada durante la llamada. La URL se valida antes de entrar; el fragmento no implementa un analizador ni admite como autorización una URL arbitraria del payload. Se revisa la política de redirecciones y destinos.

El timeout de HTTP no representa necesariamente un plazo global de toda la OTA. Se ejecuta en una tarea y bajo una coordinación que contemple recursos, vigilancia, cancelación y duración total. Un bucle de control con plazos estrictos no debe llamar directamente a esta operación bloqueante.

La API avanzada permite separar comienzo, avance y finalización. Es la opción a evaluar si se necesitan más controles durante la transferencia; deben respetarse sus estados, cierre y liberación de recursos. La finalización de la biblioteca no se sustituye por un contador de bytes propio.

El fragmento no incluye Wi-Fi, tabla de particiones, validación del manifiesto, firma, diagnóstico de arranque ni credenciales cliente. No modifica eFuses ni propone un firmware listo para producción. Se comprueban la versión de ESP-IDF y la variante de chip del proyecto.

08 · Cambiar la ejecución en un momento previsto

Reinicio y resultado operativo

La selección del candidato no cambia la instrucción que se está ejecutando. El equipo continúa con la aplicación actual hasta el reinicio. Durante ese intervalo registra el estado y coordina el mantenimiento: qué mediciones quedan pendientes, qué operaciones se cierran y qué salidas deben quedar seguras.

El estado seguro depende del producto. No siempre significa desenergizar todo; debe estar definido y probado. Tras un corte inesperado no puede depender únicamente de un mensaje de la nube: hardware y firmware de arranque deben establecer condiciones locales apropiadas.

Publicar progreso sin inventar éxito

La plataforma puede observar etapas como descargando, verificado, reinicio_programado y arrancado. Cada evento incluye equipo, publicación, versión ejecutada y resultado. Un mensaje enviado antes de reiniciar no demuestra que la versión nueva haya arrancado.

La nueva aplicación informa su propia identidad de build y realiza comprobaciones de salud. El diseño distingue un problema del servicio remoto de un fallo esencial del firmware; la confirmación no debería basarse solo en que se alcanzó la primera línea de código.

Lo que falta después de arrancar

Arrancar no garantiza funcionamiento correcto de sensores, almacenamiento o salidas. Se necesitan criterios de aceptación, límites de tiempo y recuperación si el candidato falla. El estado final del laboratorio es arrancado, con salud todavía sin evaluar.

El modelo supone que un candidato programado consigue arrancar. No simula crash, bootloader, validación de salud ni reversión. Esos mecanismos se desarrollan en el tema 44. Tampoco realiza migraciones de datos, aunque comprueba una etiqueta de esquema compatible.

09 · Recuperar una transferencia identificable

Interrupción y reanudación

Una red interrumpida no debería obligar a abandonar la versión actual. El receptor conserva un estado coherente y decide si reinicia la descarga o la reanuda. La reanudación exige más que recordar un número de bytes.

Evidencias necesarias para continuar sin mezclar imágenes.
Dato o condiciónPor qué importa
Identidad inmutable del artefactoEl contenido puede cambiar bajo una URL reutilizada
Tamaño y resumen esperados autenticadosPermiten verificar que el resultado es el candidato autorizado
Offset confirmado y escritura coherenteBytes recibidos en RAM no equivalen a bytes durables
Soporte real de rangos del servidor y clienteUna respuesta completa no debe anexarse como si fuera solo el resto
Verificación final de toda la imagenReanudar no elimina la comprobación del conjunto

Una implementación HTTP comprueba el rango, estado y longitudes de respuesta, y la identidad del recurso según el protocolo. Si no puede sostener esas garantías, descarta el intento y empieza de nuevo con una publicación autorizada. No mezcla el prefijo del build 2 con el resto del build 3.

Descargar mediante varias peticiones no implica automáticamente poder recuperar una descarga después de un reinicio. El soporte depende de biblioteca, servidor, persistencia y versión. Tampoco se guarda el offset tras cada byte sin considerar desgaste y consistencia del almacenamiento.

Cortes en momentos diferentes

El laboratorio supone que cada bloque aceptado queda confirmado de forma durable y que los metadatos de selección se actualizan como una unidad. Son abstracciones. La implementación real debe probar cortes durante borrado, escritura, actualización de metadatos y primer arranque, respetando las garantías del SDK.

Al reiniciar durante descarga, el modelo queda pausado y conserva la versión actual. Si ya estaba programado, pasa a arrancado. La acción de reinicio representa un evento local posible incluso sin red: los flags de canal y condición segura no impiden un corte físico. La condición segura del ejercicio se exige al programar el cambio; el arranque seguro del producto se diseña por separado.

10 · Estados y decisiones reproducibles

Modelo ejecutable en Python

El modelo representa una placaA con esquema 1 y destino de 12288 bytes. La descarga avanza en bloques de hasta 2048 bytes. Los rechazos conservan el registro y la versión ejecutada solo cambia al reiniciar un candidato programado.

Los flags de integridad, origen y canal resumen resultados de componentes externos. Un mensaje real no se autoriza enviando origen=True. El ejemplo no calcula firmas ni escribe flash; el bloque anterior sí calcula SHA-256 sobre datos de prueba.

Python en el navegador. Ejecutá el recorrido y comparalo con el laboratorio. Ambos usan las mismas reglas; no hacen conexiones ni alteran el equipo.

from copy import deepcopy


def nuevo():
    return dict(fase="reposo", actual=1, candidato=None, recibidos=0, seleccionado=False)


def imagen(perfil):
    m = dict(id="demo-A-build2", modelo="placaA", build=2, bytes=8192, esquema=1)
    if perfil == "siguiente": m.update(id="demo-A-build3", build=3)
    if perfil == "otra_placa": m.update(id="demo-B-build2", modelo="placaB")
    if perfil == "grande": m.update(id="demo-A-grande", bytes=16384)
    if perfil == "antigua": m.update(id="demo-A-build1", build=1)
    if perfil == "otro_esquema": m.update(id="demo-A-esquema2", esquema=2)
    return m


def aplicar(estado, accion, perfil="normal", canal=True, permiso=True,
            energia=True, escritura=True, integridad=True, origen=True,
            mismaImagen=True, seguro=True):
    e = deepcopy(estado)
    def fin(ok, motivo):
        return dict(estado=e, ok=ok, motivo=motivo)
    if accion == "reiniciar":
        if e["fase"] == "programado":
            e.update(actual=e["candidato"]["build"], seleccionado=False, fase="arrancado")
            return fin(True, "Arranca el candidato en el modelo; falta evaluar salud operativa")
        if e["fase"] == "descargando": e["fase"] = "pausado"
        return fin(True, "Conserva la versión actual y el registro durable simulado")
    if accion == "interrumpir":
        if e["fase"] != "descargando": return fin(False, "Solo se interrumpe una descarga en curso")
        e["fase"] = "pausado"
        return fin(True, "Transferencia pausada; la versión actual no cambia")
    if accion == "cancelar":
        if e["fase"] not in ("descargando", "pausado", "descargado", "verificado"):
            return fin(False, "No hay una transferencia cancelable en esta etapa")
        e.update(fase="reposo", candidato=None, recibidos=0, seleccionado=False)
        return fin(True, "Descarta el candidato del modelo y conserva la versión actual")
    if accion == "iniciar":
        if e["fase"] not in ("reposo", "arrancado"): return fin(False, "Ya hay una actualización en curso")
        if not canal or not permiso: return fin(False, "Falta canal autenticado o autorización de actualización")
        if not origen: return fin(False, "Metadatos de publicación no autenticados")
        if not energia: return fin(False, "La política de alimentación no permite iniciar")
        m = imagen(perfil)
        if m["modelo"] != "placaA" or m["esquema"] != 1: return fin(False, "Modelo o esquema de datos incompatible")
        if m["build"] <= e["actual"]: return fin(False, "La política del ejercicio exige un build posterior")
        if m["bytes"] > 12288: return fin(False, "La imagen no cabe en el destino inactivo")
        e.update(candidato=m, recibidos=0, seleccionado=False, fase="descargando")
        return fin(True, "Candidato fijado; comienza la transferencia al destino inactivo")
    if accion == "bloque":
        if e["fase"] != "descargando": return fin(False, "No hay descarga habilitada para recibir bloques")
        if not canal or not energia or not escritura:
            return fin(False, "Falla canal, alimentación o escritura; no se confirma el bloque")
        e["recibidos"] += min(2048, e["candidato"]["bytes"] - e["recibidos"])
        if e["recibidos"] == e["candidato"]["bytes"]: e["fase"] = "descargado"
        return fin(True, "Bloque de hasta 2048 bytes confirmado en el registro simulado")
    if accion == "reanudar":
        if e["fase"] != "pausado": return fin(False, "La reanudación requiere una descarga pausada")
        if not canal or not permiso or not energia: return fin(False, "No se cumplen las condiciones para reanudar")
        if not mismaImagen: return fin(False, "El recurso ya no corresponde a la imagen fijada; no se mezclan bytes")
        e["fase"] = "descargando"
        return fin(True, "Reanuda desde el offset confirmado para la misma imagen")
    if accion == "validar":
        if e["fase"] != "descargado": return fin(False, "La imagen todavía no está completa")
        if not integridad or not origen: return fin(False, "Falla la integridad o la autenticidad de la publicación")
        e["fase"] = "verificado"
        return fin(True, "Comprobaciones externas del candidato superadas en el modelo")
    if accion == "programar":
        if e["fase"] != "verificado": return fin(False, "Solo se programa una imagen verificada")
        if not seguro or not energia or not escritura:
            return fin(False, "Falta condición local segura, alimentación o persistencia de la selección")
        e.update(seleccionado=True, fase="programado")
        return fin(True, "Selección de próximo arranque confirmada; aún ejecuta la versión actual")
    return fin(False, "Acción desconocida")


estado = nuevo()
escenarios = 0


def paso(nombre, accion, esperado, **cfg):
    global estado, escenarios
    antes = deepcopy(estado)
    r = aplicar(estado, accion, **cfg)
    assert r["ok"] == esperado, nombre
    assert estado == antes, "El argumento no debe modificarse"
    if not esperado: assert r["estado"] == antes, "Rechazo sin cambios"
    estado = r["estado"]
    total = estado["candidato"]["bytes"] if estado["candidato"] else 0
    assert 0 <= estado["recibidos"] <= total
    assert estado["seleccionado"] == (estado["fase"] == "programado")
    if estado["actual"] != antes["actual"]:
        assert accion == "reiniciar" and antes["fase"] == "programado"
    escenarios += 1
    print(f'{nombre:28} {estado["fase"]:12} build={estado["actual"]} bytes={estado["recibidos"]}')


paso("Sin canal verificado", "iniciar", False, canal=False)
paso("Sin autorización", "iniciar", False, permiso=False)
paso("Publicación no autenticada", "iniciar", False, origen=False)
paso("Otra placa", "iniciar", False, perfil="otra_placa")
paso("Esquema incompatible", "iniciar", False, perfil="otro_esquema")
paso("Imagen demasiado grande", "iniciar", False, perfil="grande")
paso("Build anterior o igual", "iniciar", False, perfil="antigua")
paso("Iniciar build 2", "iniciar", True)
paso("No duplicar descarga", "iniciar", False)
paso("No validar incompleta", "validar", False)
paso("Escritura fallida", "bloque", False, escritura=False)
paso("Primer bloque", "bloque", True)
paso("Interrumpir red", "interrumpir", True)
paso("No escribir pausada", "bloque", False)
paso("Recurso modificado", "reanudar", False, mismaImagen=False)
paso("Reanudar misma imagen", "reanudar", True)
paso("Segundo bloque", "bloque", True)
paso("Corte durante descarga", "reiniciar", True)
assert estado["actual"] == 1 and estado["recibidos"] == 4096
paso("Reanudar tras corte", "reanudar", True)
paso("Tercer bloque", "bloque", True)
paso("Cuarto bloque", "bloque", True)
paso("Completa sin verificar", "programar", False)
paso("Integridad fallida", "validar", False, integridad=False)
paso("Procedencia fallida", "validar", False, origen=False)
paso("Verificación externa válida", "validar", True)
paso("Condición local insegura", "programar", False, seguro=False)
paso("Selección no persistida", "programar", False, escritura=False)
paso("Programar próximo arranque", "programar", True)
assert estado["actual"] == 1
paso("No cancelar selección", "cancelar", False)
paso("Arrancar candidato", "reiniciar", True)
assert estado["actual"] == 2
paso("No reinstalar build 2", "iniciar", False)
paso("Preparar build 3", "iniciar", True, perfil="siguiente")
paso("Cancelar candidato", "cancelar", True)
assert estado["actual"] == 2 and estado["candidato"] is None
print(f"\n{escenarios} escenarios superados. Salud y reversión: tema 44.")

Los 33 escenarios verifican transiciones, rechazos sin cambios, límites del contador y selección de arranque. Las aserciones adicionales comprueban conservación de avance, versión antes y después de reiniciar y cancelación sin cambio de firmware.

11 · De cero bytes al primer arranque

Laboratorio de transferencia OTA

Aplicá una acción por vez. Elegí el perfil antes de iniciar: después queda fijado hasta cancelar o terminar el recorrido. Cada bloque aceptado suma 2048 bytes a una imagen de 8192. Las condiciones son verificaciones supuestas, no controles de seguridad reales.

Actualización de placaA

Estado del proceso—
Versión en ejecución—
Identidad del candidato—
Bytes confirmados / esperados—
Transferencia, no salud operativa—
Selección de próximo arranque—

Escenario listo en build 1. Iniciá una publicación compatible.

Historial de decisiones

Últimas 30 acciones. Los rechazos conservan el registro.
PasoAcciónResultadoEstado finalMotivo

Registro simulado


        

Recorrido guiado

  1. Probá un perfil de otra placa, otro esquema o demasiado grande. Se rechaza antes de recibir bytes. Volvé a build 2 compatible e iniciá.
  2. Intentá verificar o programar antes de completar la imagen: se rechaza y build 1 sigue en ejecución.
  3. Recibí un bloque. El avance es 2048/8192, o 25 %. Desactivá escritura e intentá otro: no se confirma más progreso.
  4. Restaurá escritura e interrumpí. La transferencia queda pausada. Con «misma imagen» desactivada, reanudar se rechaza; se necesita recuperar el recurso correcto o cancelar y empezar otra publicación.
  5. Reactivá esa condición y reanudá. Recibí otro bloque y simulá reinicio: se conserva build 1 y el avance de 4096 bytes, con transferencia pausada.
  6. Reanudá y recibí dos bloques más. El 100 % deja el estado descargado; todavía no habilita programar.
  7. Desactivá integridad e intentá verificar. El candidato no se acepta. En un equipo real se diagnostica y obtiene una imagen correcta; marcar un flag no repara sus bytes.
  8. Restaurá las condiciones para representar un resultado externo válido y verificá. Quitá condición local segura y probá programar: se rechaza. Restaurala y programá.
  9. Observá que sigue build 1 aun con el candidato seleccionado. Al reiniciar, pasa a build 2 y estado arrancado. La salud del firmware nuevo aún no está evaluada.
  10. Intentá iniciar build 2 otra vez: la política lo rechaza. Elegí build 3, iniciá y cancelá antes de seleccionar: se conserva build 2. «Nuevo escenario ficticio» vuelve al ejercicio inicial; recargar la página también pierde esta simulación.

Cancelar es una acción local del coordinador en el modelo. No acepta órdenes remotas sin autenticar en un sistema real. Tras programar el arranque el ejercicio no ofrece deshacer la selección: modificarla requiere un procedimiento específico de plataforma.

12 · Verificar lo que realmente cambió

Método y diagnóstico

  1. Definí la publicación: producto, revisiones, build, tamaño, dependencias y datos persistidos.
  2. Protegé su autorización: proceso de publicación, metadatos, imagen y claves correspondientes.
  3. Verificá el destino: particiones, capacidad y garantías ante cortes para esa operación.
  4. Acotá la transferencia: buffer, plazos, RAM, energía y reintentos.
  5. Fijá la identidad del recurso: no mezclar bytes al reanudar.
  6. Comprobá la imagen completa: tamaño, formato, integridad, compatibilidad y autenticidad.
  7. Coordiná el cambio: condición segura y selección persistida del próximo arranque.
  8. Observá la versión ejecutada: separar el anuncio previo del reporte de la aplicación nueva.
  9. Prepará salud y recuperación: criterios de aceptación y reversión del tema 44.
Diagnóstico por etapa.
SíntomaQué revisar
No encuentra una publicaciónCatálogo, identidad, permisos, grupo y compatibilidad
No abre descargaRed, DNS, TLS, reloj, URL autorizada y redirecciones
Se detiene a mitadPlazos, memoria, alimentación, flash y respuesta del servidor
Llega al 100 % y se rechazaTamaño, resumen, formato, procedencia y dependencias
Sigue informando la versión anteriorSelección, reinicio real y origen del reporte
Arranca y deja de funcionarDiagnóstico de salud, datos persistidos y recuperación prevista

Los registros incluyen publicación, etapa, bytes confirmados, build ejecutado y código de error. Evitan credenciales y URLs con tokens sensibles. La repetición del mismo error tiene límites y una política de intervención; no se reinicia en bucle intentando ocultarlo.

«Si el servidor responde 200, el firmware sirve»

El éxito HTTP no prueba formato, compatibilidad ni autorización de la imagen. Se verifican las etapas posteriores.

«El 100 % significa actualización exitosa»

Solo describe la transferencia definida. Todavía puede fallar verificación, arranque o salud.

«Uso la URL de siempre y continúo desde el offset»

El recurso podría haber cambiado. Se fija su identidad y se comprueban las condiciones de reanudación y el resultado completo.

«HTTPS reemplaza todas las comprobaciones del firmware»

Protege el canal bajo su política, pero no demuestra por sí solo compatibilidad, salud ni autorización de código frente a todos los escenarios del producto.

«Puedo actualizar cualquier partición con las mismas garantías»

Se revisan API, destino y resistencia a cortes. No se extrapola el comportamiento de slots de aplicación a bootloader o datos.

«La aplicación nueva arrancó, así que ya está confirmada»

El primer arranque es una evidencia parcial. Los criterios de salud y la confirmación pertenecen al protocolo de aceptación.

13 · Práctica guiada

Predecí antes de comprobar

1. Cuatro bloques

Una imagen ocupa 8192 bytes y cada bloque confirmado aporta 2048. ¿Qué porcentaje hay tras tres bloques y qué falta al recibir el cuarto?

Ver solución

75 % tras tres bloques. El cuarto completa la transferencia. Todavía faltan verificación, selección, arranque y evaluación de salud.

2. Otra revisión de placa

Una imagen cabe en flash, pero fue compilada para otra placa con distinto mapa de salidas. ¿Alcanza con que el microcontrolador sea igual?

Ver solución

No. Se verifica compatibilidad del producto y hardware antes de aplicar. Que el archivo entre no demuestra que controle correctamente la instalación.

3. Una huella cambiada junto con el archivo

Un origen no autenticado ofrece un binario y su SHA-256. Ambos coinciden. ¿Eso acredita al autor autorizado?

Ver solución

No. Un atacante puede sustituir ambos. Se necesita autenticar la publicación mediante el mecanismo previsto.

4. Escritura fallida

Se recibieron 2048 bytes en RAM, pero la escritura no confirmó éxito. ¿Se incrementa el offset durable?

Ver solución

No. Se informa el fallo y se recupera según la API de almacenamiento. Recibir datos no demuestra que estén persistidos coherentemente.

5. Reanudar otro contenido

Tras 4096 bytes de build 2, la misma URL ahora entrega build 3. ¿Se puede anexar su resto?

Ver solución

No. Se mantiene una identidad de artefacto fija. Si no se puede recuperar la imagen original bajo las condiciones previstas, se cancela y comienza otra transferencia autorizada.

6. Imagen seleccionada

El equipo sigue ejecutando build 1 y ya programó build 2. ¿Qué build informa como ejecutado antes de reiniciar?

Ver solución

Build 1. Puede informar además build 2 como candidato del próximo arranque, sin atribuirle ejecución todavía.

7. Último bloque parcial

Una imagen tiene 9000 bytes y el buffer admite 2048. ¿Cuántos bloques completos y cuántos bytes quedan?

Ver solución

Cuatro bloques completos, 8192 bytes, y un último de 808. El avance se cuenta por datos reales confirmados, no por capacidad del buffer.

8. Arranque sin salud

La aplicación nueva envía su build, pero no puede leer una configuración esencial. ¿El reporte de versión demuestra éxito completo?

Ver solución

No. Confirma ejecución parcial. Se aplica el diagnóstico y la política de aceptación o recuperación; el tema 44 desarrolla la confirmación y reversión.

14 · Síntesis

Ideas para recordar

  • OTA es un proceso de publicación, transferencia, verificación y arranque, con observación del resultado.
  • La imagen debe corresponder al hardware, recursos y datos del producto.
  • La transferencia se puede realizar con memoria acotada y un destino inactivo.
  • Se cuenta avance confirmado; 100 % de bytes no equivale a firmware aceptado.
  • La huella compara contenido; la procedencia necesita una política de autenticación.
  • Una reanudación conserva identidad, offset coherente y verificación final.
  • La selección prepara el próximo arranque y no cambia de inmediato el firmware ejecutado.
  • La actualización respeta estados seguros y plazos del control local.
  • Arrancar una versión no demuestra salud; la aceptación y reversión completan la operación.

¿Listo para el tema 44?

Antes de avanzar, describí qué sucede si se corta red o energía en cada etapa de tu OTA. Indicá dónde queda la aplicación actual, cómo se identifica el candidato y qué evidencia demuestra que la nueva versión funciona.

En el próximo tema, Actualizaciones seguras y reversión, profundizamos la firma del firmware, las particiones y la vuelta atrás si algo falla.