¿Y si un mensaje secreto solo apareciera cuando entras a la pieza correcta, cuando se prende la luz, o cuando llegas físicamente a un punto exacto del mapa? Eso es justo lo que arma este proyecto: un cofre digital que descifra texto o imágenes usando la lectura de un sensor ambiental como llave. Si el sensor no marca el valor justo, en la pantalla solo ves caracteres revueltos, como ruido. Cuando la condición se cumple, el secreto se revela.

En este tutorial vas a entender cómo se construye un candado por sensores con CircuitPython sobre una placa Feather con pantalla TFT. Vas a ver tres formas de generar la llave (luz medida en lux, temperatura o humedad, y coordenadas GPS), cómo se cifra el contenido, y por qué la criptografía elegida hace que el resultado mal descifrado se vea misterioso en vez de simplemente roto. Es un proyecto de electronica con un trasfondo de seguridad real, ideal para regalar un acertijo, montar una búsqueda del tesoro tipo geocaching, o decorar con un mensaje que solo se lee bajo cierta condición.

Montaje completo: sensor de luz MAX44009 sobre la placa ESP32 S3 TFT mostrando texto cifrado en la pantalla

La idea detrás del candado

El truco no es esconder el archivo, sino cifrarlo con una clave que nace de una medición del mundo físico. El programa toma la lectura del sensor, le calcula un rango (por ejemplo 200 a 300 lux), y de ese rango deriva una llave criptográfica con el algoritmo SHA 256. Mientras la lectura caiga dentro de la banda correcta, la llave generada es la misma con la que se cifró, así que el descifrado funciona. Fuera de esa banda, la llave es distinta y el contenido sale revuelto.

Acá hay un detalle elegante que vale la pena entender: el proyecto usa dos algoritmos distintos según lo que estés ocultando.

Para los mensajes de texto se usa una versión del cifrado Vigenère, extendida para cubrir todos los caracteres ASCII imprimibles (del 32 al 126, desde el espacio hasta la tilde) en vez de solo las letras A a la Z. ¿Por qué Vigenère y no algo más moderno? Porque cuando descifras con la llave equivocada, Vigenère no devuelve un error: devuelve texto revuelto pero legible. Eso es perfecto acá, porque deja ver caracteres raros en pantalla como una pista de que hay un secreto escondido esperando. La llave de Vigenère puede tener cualquier largo, pero debe usar el mismo alfabeto que el mensaje. Como SHA 256 entrega bytes crudos que no siempre son imprimibles, el proyecto usa la representación en base64 del hash como llave. Una llave generada se ve así de larga: PdToaSE3R9V9ClRENwNMhYV+ihPv5NXt05kQo8VrbH8=.

Para las imágenes la historia cambia, porque un archivo de imagen contiene datos binarios crudos, no solo texto imprimible. Ahí entra AES, un cifrado moderno pensado para datos arbitrarios. El detalle fino es que no se cifra el archivo entero: se cifran solo los píxeles, dejando intactos los metadatos (tipo de codificación, tamaño, paleta de colores). Si cifraras todo, el archivo quedaría corrupto y la aplicación mostraría solo un error. Cifrando únicamente los píxeles, la pantalla muestra una imagen revuelta que parece estática de TV cuando la llave está mala, en vez de quedar completamente rota. La variante de AES usada acepta una llave de 256 bits, exactamente lo que produce SHA 256, así que los bytes crudos del hash se usan directo como llave, sin convertirlos a base64.

Imagen secreta revelada como ruido de píxeles tipo estática cuando la llave del sensor no coincide

Qué necesitas para armarlo

El proyecto original corre sobre el ecosistema Feather de Adafruit, donde las placas y los módulos se apilan sin soldar gracias a un conector estándar. La placa principal trae la pantalla TFT a color integrada, y cada sensor se conecta por el bus I2C usando un cable STEMMA QT o, en el caso del GPS, apilándose como FeatherWing.

Estos son los componentes según la variante de sensor que elijas:

  • Placa Feather ESP32 S3 con pantalla TFT (microcontrolador + display a color, el cerebro del proyecto).
  • Sensor de luz MAX44009 para la variante de lux (rango ultra amplio, de 0.045 a 188.000 lux).
  • Sensor de temperatura, humedad y CO2 STCC4 y SHT41 para la variante ambiental.
  • GPS FeatherWing para la variante de coordenadas geográficas.
  • FeatherWing Doubler (placa que permite apilar dos FeatherWings lado a lado).
  • Cable USB tipo A a tipo C para alimentar y programar la placa.
  • Cable STEMMA QT / Qwiic de 4 pines para conectar el sensor I2C.

No necesitas las tres variantes a la vez: eliges UN tipo de sensor y armas esa versión. El cable USB y la placa con pantalla son los únicos comunes a todas.

Paso 1: instalar CircuitPython y las librerías

Lo primero es tener CircuitPython funcionando en la placa. Conecta la Feather al computador con un cable USB que transfiera datos (no uno de solo carga) y verifica que aparezca como una unidad llamada CIRCUITPY en tu explorador de archivos. Si nunca instalaste CircuitPython en esta placa, sigue la guía oficial de Adafruit para flashear el firmware más reciente.

El código del proyecto trae sus dependencias en un Project Bundle descargable. Al descomprimirlo, copia la carpeta lib completa a CIRCUITPY/lib, y copia el code.py a la raíz de la unidad. El programa arranca solo apenas guardas el archivo. Entre las librerías que importa están el manejo del display, el texto, la conversión base64 y el driver específico del sensor que uses. Por ejemplo, para la variante de luz el encabezado del code.py se ve así:

Python
# SPDX-FileCopyrightText: 2026 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT
import hashlib
import struct
import time

import board
import bitmaptools
import aesio
from displayio import Group, TileGrid, Palette, Bitmap
import supervisor
import terminalio
from adafruit_display_text.text_box import TextBox
from adafruit_display_text.bitmap_label import Label
import adafruit_binascii
from adafruit_max44009 import MAX44009
from digitalio import DigitalInOut, Direction, Pull

Fíjate en la línea from adafruit_max44009 import MAX44009: ese es el driver del sensor de luz. Si cambias de sensor, cambia ese import por el de tu módulo.

Paso 2: cifrar tu secreto en la página del encriptador

Acá viene lo entretenido. El proyecto incluye una página web que corre en tu navegador y genera el contenido cifrado listo para pegar en el código. Tiene dos pestañas: una para texto y otra para imágenes.

Página del encriptador de texto Vigenère con los campos tipo de lectura, nivel de precisión y valor del sensor

Para un mensaje de texto, completas estos campos y presionas Encrypt:

  • Reading type: el tipo de sensor que va a desbloquear el secreto (Temperature, Humidity, CO2, Lux, u Other para casos custom como GPS).
  • Precision level: un número entero que controla qué tan ancha es la banda de desbloqueo. Un 1 significa que el valor debe caer entre dos números consecutivos (por ejemplo 22 a 23). Un 10 abre un rango de 10 unidades (como 42 a 52). En GPS, este número define cuántos decimales tienen las coordenadas.
  • Sensor value: la lectura objetivo que abre el secreto. La llave se deriva de un rango que cubre ese valor según la precisión.
  • Plaintext: el mensaje que quieres ocultar.

Al presionar Encrypt aparece una caja con un objeto que contiene el mensaje cifrado más los metadatos necesarios para descifrarlo y validarlo. Copias todo eso y lo pegas dentro de la lista SEQUENCE en el code.py, en la sección de configuración del usuario cerca del inicio del archivo.

Encriptador mostrando el rango calculado 200 a 300 y la entrada SEQUENCE generada para pegar en code.py

Cada secreto cifrado se agrega como una entrada de la lista SEQUENCE. Así se ve una entrada de texto lista para pegar (este es el formato exacto que genera la página, no lo edites a mano salvo para sumar más entradas):

Python
SEQUENCE = [
    {
        "type":        "text",
        "data":        "?tH,OledH]BF9zxBSX'LUY^C7390.HFaS[GX",
        "sha256":      "583fd7f36f35c16fb4dc60edf493c74679ce6f26f928bf9995dcae85becf6962",
        "reading":     "lux",
        "precision_level": 100,
    },
    # Add more entries here...
]

Puedes ocultar varios mensajes o imágenes con valores distintos: cada uno se copia como una entrada más en la misma lista. Una vez que un secreto se revela bien, queda visible en pantalla hasta que presionas el botón BOOT de la Feather para avanzar al siguiente acertijo de la secuencia.

Paso 3: la variante de imagen

Cifrar imágenes es parecido, con un par de diferencias. En la pestaña Image arrastras un archivo (la página acepta PNG, JPEG y BMP) en lugar de escribir texto. Además aparecen dos campos nuevos:

  • Nonce / IV: requerido por AES. Lo dejas por defecto o lo cambias por cualquier texto de 16 caracteres.
  • Max palette colors: cuántos colores tendrá la paleta del Bitmap resultante. El valor por defecto 256 sirve para la mayoría de los casos.

Al presionar Encrypt & Download pasan dos cosas: se descarga una copia cifrada de la imagen con un nombre como secret_image.abmp.enc, y aparece la caja con la entrada para pegar en SEQUENCE. Importante: el valor data del objeto debe tener el nombre exacto del archivo descargado, y ese archivo cifrado lo copias a la unidad CIRCUITPY. Así se ve una entrada de imagen:

Python
SEQUENCE = [
    {
        "type":        "image",
        "data":        "secret_image.abmp.enc",
        "iv":          b"InitializationVe",
        "sha256":      "932185ffbba8b245a97a0819428d6038997e968174cadd3bd0a5bab970d0e560",
        "reading":     "temperature",
        "precision_level": 1,
    },
    # Add more entries here...
]

Paso 4: conexión del sensor y prueba

Para la variante de luz, conectas el módulo MAX44009 a la Feather con un cable STEMMA QT. Como es I2C, no hay que pelear con pinout: el conector ya trae alimentación, tierra y las dos líneas de datos en el orden correcto.

Sensor de luz MAX44009 conectado a la placa Feather mediante un cable STEMMA QT de cuatro pines

En el código, el sensor se inicializa de forma muy directa una vez que el driver está importado:

Python
i2c = board.I2C()
sensor = MAX44009(i2c)
SENSOR_READ_COOLDOWN = 0.25

Con el code.py y la SEQUENCE ya cargados, el programa arranca solo. La pantalla muestra el texto revuelto y un pequeño indicador con la lectura actual del sensor y el progreso (por ejemplo 1/2). Cuando llevas la luz al rango correcto (tapando el sensor, acercando una linterna, o moviéndote a una habitación con la iluminación justa), el mensaje se ordena y aparece tu secreto. Para la precisión de lux en el MAX44009, valores de 100 o 50 funcionan bien.

Variantes y mejoras

Este proyecto es una base que se presta para muchas extensiones que el tutorial original no detalla:

  • Candado por ubicación con GPS. La variante de coordenadas convierte el dispositivo en una búsqueda del tesoro tipo geocaching: el secreto solo se descifra cuando estás físicamente en el punto del mapa. En el encriptador eliges Other como tipo de lectura, escribes gps como reading custom y pones las coordenadas con la misma cantidad de decimales que el nivel de precisión, separadas por coma y sin espacios (por ejemplo 40.656,-74.007 para precisión 3). Un nivel 2 abre un área de aproximadamente 1 kilómetro, el 3 unos 110 metros y el 4 alcanza escala de pieza (unos 11 metros).

La variante GPS revela en la pantalla Found secret spot y un codeword cuando el receptor llega a las coordenadas correctas

  • Encadenar acertijos. Como cada secreto avanza con el botón BOOT, puedes armar una secuencia de varios puzzles donde resolver el primero da la pista del segundo. Sirve para un escape room casero o un regalo con varias etapas.
  • Otros sensores. El concepto sirve para cualquier dato que el microcontrolador pueda leer: un sensor magnético que desbloquee al acercar un imán, un giroscopio que pida una orientación específica, o un sensor de proximidad. La lógica de derivar la llave desde un rango de lectura es la misma; solo cambias el driver y el atributo que lees.
  • Mensaje físico permanente. Si lo dejas alimentado de forma fija con una fuente USB y lo escondes en un objeto, tienes una decoración que solo revela su mensaje bajo la condición ambiental que tú elijas, como esos monumentos antiguos diseñados para iluminarse solo en cierto día del año.

Personalización para Chile

La placa Feather ESP32 S3 con TFT y los FeatherWings de Adafruit son específicos de ese ecosistema y hoy no están en el catálogo local, así que para la placa principal y el GPS FeatherWing tendrás que importarlos o adaptar el montaje. Lo bueno es que el proyecto es de código abierto y la lógica es portable a otras placas ESP32 con pantalla, ajustando los pines y el driver de display.

Lo que sí consigues en Chile en MechatronicStore para apoyar el armado:

  • Cable USB tipo C a tipo A (SKU B 101, aproximadamente CLP $2.190): es el cable que el tutorial usa para alimentar y programar la placa, idéntico en tecnología. Asegúrate de que sea de datos, no de solo carga.

Para los sensores, hay equivalentes locales que cumplen la misma función, aunque requieren cambiar el driver en el código (no son reemplazo directo del módulo de Adafruit):

  • Sensor de luz GY 302 con chip BH1750 (SKU GN1 6, aproximadamente CLP $3.990): mide intensidad luminosa en lux por I2C, igual que el MAX44009. Para usarlo cambiarías el import adafruit_max44009 por la librería del BH1750 y ajustarías la lectura.
  • Módulos GPS ublox NEO 6M o NEO 8M: son receptores GPS UART que entregan tramas NMEA, la misma tecnología del GPS FeatherWing. Sirven para la variante de coordenadas conectándolos por UART en vez de apilarlos. Revisa stock antes de comprar, porque rotan rápido.

La filosofía del proyecto se mantiene intacta sin importar la marca del sensor: cualquier lectura del entorno puede ser la llave.

Recursos

  • Tutorial original (inglés): Sensor Locked Secrets with CircuitPython, por Tim C para Adafruit Learning System.
  • Página del encriptador y Project Bundle: disponibles dentro del tutorial original, en las secciones Encrypting Secrets y las páginas de cada sensor (Lux Level, GPS Coordinates).
  • Documentación CircuitPython: librería adafruit_max44009 y el módulo aesio para el cifrado AES.

Tutorial inspirado en el original de Adafruit, reescrito con contexto y componentes en stock local en MechatronicStore.