Chismes del Lince 29: Las caricias del Lince
5 oct 2026 · @José Luis Usero Vílchez
Aquella tarde Sierra Bermeja olía a jara y a tierra caliente. El Lince de la Noche estaba sentado sobre una roca de peridotita, con la placa Heltec en el regazo y tres rollos de cable enredados en la cola.
—Llevo una hora intentando que el chisme note una caricia —resolvió, muy serio—. He pensado en un sensor de presión, una célula de carga como las de las básculas, un amplificador y, si hace falta, una galga extensiométrica pegada a un muelle.
La Gata Montesa, tumbada al sol sin mirarle, abrió un solo ojo.
—¿Y cuando alguien te toca, qué pasa en tu pelo?
—Pues… no sé. Que me hace cosquillas.
—Pasa algo más. Tu cuerpo es un conductor, y cuando te acercas a un metal le cambias la electricidad que tiene alrededor. Eso se puede medir. Y tu placa lo lleva dentro del chip desde el día en que la compraste.
El Lince miró la Heltec. Miró el montón de cables. Miró otra vez la Heltec.
—¿Me estás diciendo que llevaba un sensor de caricias y yo pensaba comprar una báscula?
—Te estoy diciendo —bostezó la Gata— que con un cable y un trozo de papel de aluminio te sobra. Anda, ve a la cocina.
Introducción
En este chisme construimos un sensor táctil ESP32 con una Heltec LoRa 32: un cable terminado en papel de aluminio que detecta caricias. Cuando acariciamos el papel, la placa lo detecta, lo muestra en su pantalla OLED y se lo cuenta al móvil por Bluetooth. En el móvil, un lince cambia de cara, ronronea y hace vibrar el teléfono.
No necesitamos ningún sensor extra: el ESP32, el chip de la Heltec, tiene sensores táctiles integrados. Lo que hacemos es aprender a usarlos, a calibrarlos y a entender qué están midiendo de verdad.
La conexión Bluetooth es la misma que ya usamos en los chismes 25 a 27, así que la novedad de hoy está en el lado de la placa: medir sin sensor externo y calibrar sobre la marcha.
Por qué este chisme
Los botones de siempre son un contacto mecánico: se aprietan o no se aprietan. Pero hoy muchas cosas se manejan con el dedo y sin apretar nada: la pantalla del móvil, los botones de un ascensor, el panel de una vitrocerámica, una lámpara que se enciende al tocar la base. Detrás de todas hay la misma idea, y con una placa de pocos euros podemos construirla y desmontarla mentalmente.
Además, este chisme enseña algo que no suele enseñarse: un sensor casi nunca da «el» valor correcto, sino un valor que hay que interpretar. El número que da el pin depende de la longitud del cable, de la humedad del aire, de si estamos descalzos o con zapatillas, y hasta de si hemos tocado antes el cargador. Por eso la placa empieza midiendo cómo es «no tocar»: sin esa referencia, el número no significa nada.
Es una buena puerta de entrada a la idea central de AIDARAC: sensor → dato → calibración → conclusión.
Cómo funciona un sensor táctil ESP32
La pregunta de la Gata era: ¿qué le pasa a la electricidad cuando un dedo se acerca a un metal? La respuesta está en la capacidad eléctrica.
Un trozo de metal aislado, como nuestro papel de aluminio, puede almacenar una pequeñísima carga eléctrica. Cuánta carga cabe para un voltaje dado es su capacidad, y se mide en faradios (en este caso, en picofaradios, billonésimas de faradio). Nuestro cuerpo es un conductor bastante bueno y está conectado, aunque sea de forma muy débil, a todo lo que nos rodea. Al acercar la mano, el papel y el cuerpo forman algo parecido a un condensador y la capacidad del conjunto aumenta.
El ESP32 mide ese aumento con un truco ingenioso. Cada pin táctil carga y descarga el metal una y otra vez, y un contador apunta cuántos ciclos completa en un tiempo fijo. Si el pin tiene más capacidad, tarda más en cargarse y completa menos ciclos. Por eso, en el ESP32 clásico, el valor que devuelve touchRead() baja cuando tocamos: sin tocar puede dar 60 o 70, y con el dedo, 20 o menos.
Hay una consecuencia importante: la placa no necesita que el dedo toque el metal. Basta con acercarse, y de ahí que se pueda «acariciar» y no solo «pulsar». Cuanto más cerca o más superficie de piel, más baja el valor, y eso nos permite medir una fuerza de caricia y no solo un sí o un no.
Y el móvil, ¿cómo se entera? La placa le envía por Bluetooth una línea de texto con la fuerza, por ejemplo F:45, cinco veces por segundo. Es la misma técnica del servicio UART de los chismes 25 a 27.
Qué necesitamos
| Elemento | Para qué sirve |
|---|---|
| Placa Heltec LoRa 32 con ESP32 clásico (o TTGO LoRa32) | Mide, muestra y envía por Bluetooth |
| Un cable de puente (dupont) | Une el papel de aluminio con el pin 13 |
| Papel de aluminio | Es la «piel» que acariciamos |
| Cinta adhesiva | Sujeta el papel al cable |
| Cable USB y Arduino IDE | Cargar el programa |
| Librerías Adafruit SSD1306 y Adafruit GFX | Dibujan en la pantalla OLED (las del chisme 25) |
| Móvil Android con Chrome | Abre la página y recibe los datos |
Una advertencia: el programa está escrito para placas con ESP32 clásico (Heltec WiFi LoRa 32 V2 o TTGO LoRa32). Las versiones con ESP32-S3, como la Heltec V3, tienen otros pines táctiles y el valor sube al tocar, así que haría falta adaptar el código. Es un buen reto para quien tenga una.
Construcción
Es el montaje más corto de la serie: un solo cable.
- Con la placa desconectada del USB, coge un cable de puente y envuelve uno de sus extremos metálicos con una bolita de papel de aluminio, apretada y sujeta con cinta. Deja el metal del cable en contacto con el aluminio.
- Conecta el otro extremo al pin GPIO 13 de la Heltec. Ese pin es el sensor táctil T4.
- No conectes nada a GND. El propio cuerpo y el aire cierran el circuito, y por eso la medida es tan sensible a lo que hay alrededor.
- Conecta la placa al ordenador y carga el programa.
- No toques el papel hasta que la pantalla deje de poner «Calibrando». En esos dos segundos la placa mide cómo es «no tocar».
Si la pantalla no se enciende, revisa los pines de la OLED (OLED_SDA, OLED_SCL y OLED_RST) al principio del programa: son los mismos del chisme 25, y en algunas placas cambian.
El código de la placa
Guárdalo como chisme-29-lince-caricias.ino, ábrelo con el Arduino IDE y elige la placa ESP32 Dev Module. No hay que cambiar nada salvo, si hace falta, los pines de la pantalla.
/*
Chisme 29 · Las caricias del Lince (Heltec / TTGO LoRa32 con ESP32)
El ESP32 tiene sensores táctiles dentro del chip. Un cable con papel de
aluminio sirve de "piel": al acariciarlo, la placa lo nota y se lo cuenta
al móvil por Bluetooth.
Librerías: Adafruit SSD1306 y Adafruit GFX (las del Chisme 25).
Placa en el Arduino IDE: "ESP32 Dev Module".
CABLEADO (un solo cable, con la placa apagada):
Cable con papel de aluminio en la punta -> GPIO 13
(no hace falta conectar nada a GND)
NO TOQUES EL PAPEL AL ENCENDER: en los primeros segundos la placa mide
cómo es "no tocar" y luego lo compara con todo lo demás.
Mensaje por Bluetooth (una línea): F:45 (fuerza de la caricia, de 0 a 100)
*/
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define PIN_TACTIL 13 // GPIO 13 = sensor táctil T4
#define OLED_SDA 21
#define OLED_SCL 22
#define OLED_RST 16 // pon -1 si tu placa no tiene reset de pantalla
#define OLED_ADDR 0x3C
#define UMBRAL 30 // desde esta fuerza (0 a 100) hay caricia
#define SERVICE_UUID "6e400001-b5a3-f393-e0a9-e50e24dcca9e"
#define UUID_TX "6e400003-b5a3-f393-e0a9-e50e24dcca9e" // la placa envía por aquí
Adafruit_SSD1306 display(128, 64, &Wire, OLED_RST);
BLECharacteristic *tx = nullptr;
volatile bool conectado = false;
float base = 60; // valor "sin tocar" (se mide al arrancar)
int fuerza = 0;
unsigned long ultimaLectura = 0, ultimoEnvio = 0;
class AlConectar : public BLEServerCallbacks {
void onConnect(BLEServer *s) { conectado = true; }
void onDisconnect(BLEServer *s) {
conectado = false;
BLEDevice::startAdvertising(); // vuelve a ser visible para reconectar
}
};
void enviar(const String &m) {
if (conectado && tx) {
String s = m + "\n";
tx->setValue((uint8_t *)s.c_str(), s.length());
tx->notify();
}
}
int leer() { // media de 5 lecturas, para quitar ruido
long suma = 0;
for (int i = 0; i < 5; i++) { suma += touchRead(PIN_TACTIL); delay(2); }
return suma / 5;
}
void pintar(const char *mensaje) {
display.clearDisplay();
display.setTextSize(1);
display.setCursor(0, 0);
display.print(conectado ? "Bluetooth: conectado" : "Bluetooth: esperando");
display.setCursor(0, 20);
display.setTextSize(2);
display.print(mensaje);
display.drawRect(0, 52, 128, 10, SSD1306_WHITE); // barra de caricia
display.fillRect(0, 52, map(fuerza, 0, 100, 0, 128), 10, SSD1306_WHITE);
display.display();
}
void setup() {
Serial.begin(115200);
Wire.begin(OLED_SDA, OLED_SCL);
if (OLED_RST >= 0) {
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW); delay(20); digitalWrite(OLED_RST, HIGH);
}
if (!display.begin(SSD1306_SWITCHCAPVCC, OLED_ADDR)) Serial.println("No se encuentra la OLED");
display.setTextColor(SSD1306_WHITE);
pintar("Calibrando");
delay(500);
long suma = 0; // mide cómo es "no tocar"
for (int i = 0; i < 30; i++) { suma += leer(); delay(20); }
base = suma / 30.0;
Serial.printf("Valor sin tocar: %.1f\n", base);
BLEDevice::init("Heltec-Lince");
BLEServer *servidor = BLEDevice::createServer();
servidor->setCallbacks(new AlConectar());
BLEService *svc = servidor->createService(SERVICE_UUID);
tx = svc->createCharacteristic(UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
tx->addDescriptor(new BLE2902());
svc->start();
BLEAdvertising *pub = BLEDevice::getAdvertising();
pub->addServiceUUID(SERVICE_UUID);
pub->setScanResponse(true);
BLEDevice::startAdvertising();
}
void loop() {
if (millis() - ultimaLectura >= 100) { // 10 lecturas por segundo
ultimaLectura = millis();
int v = leer(); // al tocar, el valor BAJA
float rango = max(base * 0.6f, 5.0f);
fuerza = constrain((int)((base - v) * 100.0f / rango), 0, 100);
if (fuerza < 10) base = base * 0.98f + v * 0.02f; // se adapta si cambia el ambiente
Serial.printf("valor=%d fuerza=%d\n", v, fuerza);
pintar(fuerza >= UMBRAL ? "Rrrrr..." : "Acaricia");
}
if (millis() - ultimoEnvio >= 200) { // 5 avisos por segundo
ultimoEnvio = millis();
enviar("F:" + String(fuerza));
}
}
Tres piezas merecen una explicación.
La calibración inicial. En setup() la placa toma 30 medidas de 5 lecturas cada una y guarda la media en base. Es su definición de «no tocar» en este cable, en esta habitación y hoy.
La fuerza. Es una regla de tres: cuánto ha bajado el valor respecto a base, dividido entre el recorrido que consideramos posible (el 60 % de base), y llevado a una escala de 0 a 100. constrain() evita que se salga de ese rango.
La adaptación lenta. Si la fuerza es menor de 10 (nadie toca), base se corrige un 2 % hacia el valor actual. Así el aparato aguanta que la humedad o la temperatura cambien poco a poco, pero no se «acostumbra» a una mano que se queda quieta, porque la mano sí supera ese 10.
La página web
Es un solo archivo HTML con su CSS y su JavaScript dentro. Guárdalo como chisme-29-lince-caricias.html y súbelo a una dirección https:// (el Bluetooth del navegador no funciona desde http://). Puedes alojarla junto al resto de aplicaciones de la serie.
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Chisme 29 · Las caricias del Lince (Heltec)</title>
<style>
:root{
--bg:#eef2ef; --ink:#10201c; --soft:#4d625b; --card:#ffffff; --line:#cfdad5;
--pine:#1f5b4a; --slow:#2563a8; --ok:#1f7a4d; --norte:#c8321f; --oro:#b8892b;
box-sizing:border-box;
padding-top:env(safe-area-inset-top,0px); padding-bottom:env(safe-area-inset-bottom,0px);
}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){
--bg:#0f1a17; --ink:#e8f1ed; --soft:#9db3aa; --card:#172622; --line:#2a3d37;
--pine:#5fc4a2; --slow:#7db3ee; --ok:#6fd89f; --norte:#ff6a55; --oro:#e0b45a;}}
:root[data-theme="dark"]{--bg:#0f1a17; --ink:#e8f1ed; --soft:#9db3aa; --card:#172622; --line:#2a3d37;
--pine:#5fc4a2; --slow:#7db3ee; --ok:#6fd89f; --norte:#ff6a55; --oro:#e0b45a;}
*{box-sizing:border-box}
html{scroll-padding-top:env(safe-area-inset-top,0px)}
body{margin:0;background:var(--bg);color:var(--ink);font:18px/1.45 "Trebuchet MS","Segoe UI",system-ui,sans-serif}
.tira{height:.55rem;background:linear-gradient(90deg,var(--oro),var(--norte),var(--pine))}
main{max-width:30rem;margin:0 auto;padding:1rem 1rem 2rem;text-align:center}
h1{font-size:1.6rem;margin:.2rem 0 .1rem}
.sub{color:var(--soft);margin:0 0 .9rem;font-size:.95rem}
button{font:inherit;font-weight:700;border:2px solid var(--pine);background:var(--pine);color:var(--bg);
border-radius:.9rem;padding:.7rem 1.1rem;min-height:3.1rem;cursor:pointer}
button:disabled{opacity:.5;cursor:not-allowed}
button:focus-visible{outline:3px solid var(--slow);outline-offset:2px}
#estado{margin:.7rem 0 .1rem;color:var(--soft);min-height:1.5em}
#ayuda{margin:0 0 .4rem;color:var(--soft);font-size:.88rem;min-height:1.1em}
.lince{font-size:7rem;line-height:1.1;margin:.6rem 0 0;transition:transform .12s}
.lince.rr{animation:ron .35s infinite alternate}
@keyframes ron{from{transform:scale(1) rotate(-2deg)}to{transform:scale(1.07) rotate(2deg)}}
@media (prefers-reduced-motion:reduce){.lince.rr{animation:none}}
#frase{font-size:1.6rem;font-weight:700;min-height:2.2rem;color:var(--pine)}
.barra{height:1.4rem;border:2px solid var(--pine);border-radius:1rem;overflow:hidden;margin:.8rem auto;max-width:21rem;background:var(--card)}
#nivel{height:100%;width:0;background:linear-gradient(90deg,var(--oro),var(--norte));transition:width .15s}
#cuenta{color:var(--soft);min-height:1.4em}
.guia{text-align:left;background:var(--card);border:1px solid var(--line);border-radius:.8rem;padding:.2rem .9rem;margin-top:1.2rem}
.guia summary{font-weight:700;cursor:pointer;padding:.6rem 0}
.guia ol{padding-left:1.3rem;margin:.2rem 0 .6rem}
.guia li{margin:.4rem 0}
</style>
</head>
<body>
<div class="tira"></div>
<main>
<h1>Las caricias del Lince 🐾</h1>
<p class="sub">Acaricia el papel de aluminio y mira cómo responde el lince.</p>
<button id="conectar">Conectar Heltec</button>
<div id="estado" role="status">Sin conectar.</div>
<div id="ayuda"></div>
<div class="lince" id="lince" aria-hidden="true">😼</div>
<div id="frase" aria-live="polite">Conecta la placa</div>
<div class="barra" aria-hidden="true"><div id="nivel"></div></div>
<div id="cuenta"></div>
<details class="guia">
<summary>Guía de uso</summary>
<ol>
<li>Envuelve la punta metálica de un cable con papel de aluminio y conecta el otro extremo al pin <b>13</b> de la Heltec.</li>
<li>Carga <b>chisme-29-lince-caricias.ino</b> con el Arduino IDE. <b>No toques el papel</b> mientras la pantalla pone «Calibrando».</li>
<li>Abre esta página con <b>Chrome</b> y pulsa <b>Conectar Heltec</b>. Elige <b>Heltec-Lince</b>.</li>
<li>Acaricia el papel de aluminio: la barra sube, el lince ronronea y el móvil vibra.</li>
</ol>
</details>
</main>
<script>
const UART_SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const UART_TX = "6e400003-b5a3-f393-e0a9-e50e24dcca9e"; // la Heltec envía por aquí
const $ = id => document.getElementById(id);
const espera = ms => new Promise(r => setTimeout(r, ms));
let dispositivo = null, conectando = false, reconectando = false, buffer = "", pasoCx = "", diag = "";
let acariciando = false, caricias = 0, ultimaVibra = 0;
const UMBRAL = 30; // el mismo que en la placa
/* Lo que manda la placa: «F:45» (fuerza de la caricia, de 0 a 100) */
function fuerzaNueva(f){
$("nivel").style.width = f + "%";
const toca = f >= UMBRAL;
if (toca && !acariciando){ caricias++; $("cuenta").textContent = "Caricias: " + caricias; }
acariciando = toca;
$("lince").textContent = f >= 70 ? "😻" : toca ? "😸" : "😼";
$("lince").classList.toggle("rr", toca);
$("frase").textContent = f >= 70 ? "¡Rrrrrrr! 💕" : toca ? "Rrrrr…" : "Acaricia al lince";
if (toca && navigator.vibrate && Date.now() - ultimaVibra > 450){ navigator.vibrate(f >= 70 ? 120 : 60); ultimaVibra = Date.now(); }
}
function linea(t){
const m = /^F:(\d{1,3})$/.exec(t);
if (m) fuerzaNueva(Math.min(100, parseInt(m[1], 10)));
}
function recibir(ev){
buffer += new TextDecoder().decode(ev.target.value);
let i;
while ((i = buffer.indexOf("\n")) >= 0){ linea(buffer.slice(0, i).trim()); buffer = buffer.slice(i + 1); }
}
/* Conexión Bluetooth (mismo esquema que las páginas 21 a 25) */
const propsDe = c => ["read", "write", "writeWithoutResponse", "notify", "indicate"].filter(k => c.properties[k]).join(", ");
function estado(t, ayuda){ if (!/^Error/.test(t)) pasoCx = t; $("estado").textContent = t; $("ayuda").textContent = ayuda || ""; }
function explicar(e){
const nombre = e && e.name ? e.name : "Error", m = e && e.message ? e.message : String(e);
let a = "";
if (nombre === "NotFoundError" && /cancel/i.test(m)) a = "No has elegido ningún dispositivo. Pulsa Conectar y elige Heltec-Lince.";
else if (nombre === "NotFoundError") a = "La placa no tiene el servicio UART. Comprueba que has cargado el programa del Chisme 29.";
else if (nombre === "NotSupportedError") a = "No se pudo activar el envío de datos. Olvida la Heltec en Ajustes → Bluetooth y reinicia el móvil y la placa.";
else if (nombre === "NetworkError") a = "No se pudo abrir la conexión. Comprueba que no haya otra app conectada y reinicia la placa (botón RST).";
else if (nombre === "SecurityError") a = "La página debe abrirse con https://.";
else if (nombre === "NotAllowedError") a = "Permiso denegado. Permite el Bluetooth y los dispositivos cercanos para el navegador.";
estado("Error (" + nombre + "): " + m, "Falló en el paso: «" + pasoCx + "». " + a + (diag ? " Características: " + diag : ""));
}
async function conectarGatt(dev){
let ultimoError;
for (let k = 1; k <= 4; k++){
try {
estado("Conectando… (intento " + k + " de 4)");
const srv = dev.gatt.connected ? dev.gatt : await dev.gatt.connect();
await espera(600);
estado("Buscando el servicio UART…");
return await srv.getPrimaryService(UART_SERVICE);
} catch (e){
ultimoError = e;
if (e.name === "NotFoundError" || e.name === "SecurityError") break;
try { if (dev.gatt.connected) dev.gatt.disconnect(); } catch (_){}
await espera(800);
}
}
throw ultimoError;
}
async function enlazar(dev){
const svc = await conectarGatt(dev);
buffer = ""; diag = "";
estado("Leyendo las características del servicio…");
const chars = await svc.getCharacteristics();
diag = chars.map(c => c.uuid.slice(0, 8) + " [" + propsDe(c) + "]").join(" · ");
const cand = chars.filter(c => c.properties.notify || c.properties.indicate)
.sort((a, b) => (b.uuid === UART_TX) - (a.uuid === UART_TX));
if (!cand.length) throw new Error("El servicio UART no tiene ninguna característica que envíe datos.");
let ch = null, ultimoErr;
for (const c of cand){
c.addEventListener("characteristicvaluechanged", recibir);
for (let i = 1; i <= 2 && !ch; i++){
try { estado("Activando notificaciones en " + c.uuid.slice(0, 8) + "… (intento " + i + ")"); await c.startNotifications(); ch = c; }
catch (e){ ultimoErr = e; await espera(1000); }
}
if (ch) break;
c.removeEventListener("characteristicvaluechanged", recibir);
}
if (!ch) throw ultimoErr;
estado("Conectado a " + (dev.name || "Heltec") + ". ¡Acaricia al lince!");
}
async function reconectar(){
if (reconectando) return;
reconectando = true;
for (let i = 1; i <= 20; i++){
estado("Conexión perdida. Reintentando… (" + i + " de 20)", "Mantén la Heltec cerca y encendida.");
await espera(3000);
try { await enlazar(dispositivo); reconectando = false; return; } catch (_) {}
}
reconectando = false;
estado("No se pudo reconectar.", "Pulsa Conectar para volver a empezar.");
}
$("conectar").onclick = async () => {
if (conectando || reconectando) return;
if (!navigator.bluetooth){ estado("Este navegador no permite Bluetooth.", "Usa Chrome o Edge (en ordenador o en Android)."); return; }
conectando = true; $("conectar").disabled = true;
try {
estado("Buscando la Heltec…");
const dev = await navigator.bluetooth.requestDevice({ acceptAllDevices: true, optionalServices: [UART_SERVICE] });
dispositivo = dev;
dev.addEventListener("gattserverdisconnected", reconectar);
await enlazar(dev);
} catch (e){ explicar(e); }
finally { conectando = false; $("conectar").disabled = false; }
};
</script>
</body>
</html>
La parte nueva de esta página es la función fuerzaNueva(). Recibe un número de 0 a 100 y decide qué hace el lince: por debajo de 30 está quieto (😼), entre 30 y 70 ronronea (😸) y por encima de 70 está encantado (😻). Cada vez que la fuerza pasa de menos de 30 a más de 30 cuenta una caricia, y vibra con navigator.vibrate() si el navegador lo permite. Todo lo que no es eso es la conexión Bluetooth de siempre, con sus reintentos y sus mensajes de error.
Cómo probarlo
- Enciende la placa sin tocar el papel y espera a que la pantalla pase de «Calibrando» a «Acaricia».
- Abre el Monitor Serie a 115200 baudios. Verás línea a línea
valor=62 fuerza=0. Ese primer número es lo que mide el pin; apunta el valor que aparece enValor sin tocar. - Pon un dedo cerca del papel, sin llegar a tocarlo. El valor baja un poco y la fuerza sube.
- Toca el papel con el dedo. El valor baja bastante más y la barra de la pantalla se llena.
- Abre la página en Chrome (Android), pulsa Conectar Heltec y elige Heltec-Lince.
- Acaricia el papel: el lince cambia de cara, el contador de caricias sube y el móvil vibra.
Si la fuerza se queda siempre en 0 aunque toques, o aparece siempre en 100, reinicia la placa con el botón RST y vuelve a esperar sin tocar durante la calibración. La mayoría de los fallos vienen de ahí.

Qué estamos midiendo realmente
El número que devuelve un sensor táctil ESP32 por el Monitor Serie no es una fuerza ni una presión. Es un contador de ciclos: cuántas veces ha cargado y descargado el pin en un tiempo fijo. No tiene unidades físicas. Lo que sí mide, de forma indirecta, es un cambio de capacidad eléctrica, y lo hace sin que podamos decir cuántos picofaradios son.
Por eso el chisme no devuelve «el valor», sino la diferencia respecto a una referencia. La fuerza de 0 a 100 es una escala que hemos inventado nosotros, y es válida solo en este cable, en este momento y con esta calibración. Dos placas, o la misma con otro cable, darán números distintos para la misma caricia.
Tres fuentes de error merecen que las nombremos:
- El ruido. Dos lecturas seguidas sin tocar no dan exactamente lo mismo. Por eso promediamos 5 lecturas y después 30 en la calibración. Promediar reduce el ruido aleatorio, pero no el error sistemático.
- La deriva. La humedad, la temperatura y la cercanía de otros objetos cambian la capacidad. Por eso
basese corrige despacio cuando nadie toca. - El umbral. Que 30 sea «caricia» y 29 no lo sea es una decisión nuestra. Si se baja, el aparato se activa con una mano cercana; si se sube, hay que apretar casi como un botón.
Hay una idea de fondo: medir no significa medir bien. El aparato mide con mucha sensibilidad, pero lo que mide depende del entorno, y la calibración es lo que convierte un número sin sentido en una medida con sentido. Y también una advertencia: este aparato detecta «algo conductor cerca». No distingue una mano de una naranja ni de un gato mojado, así que no deberíamos concluir que ha «sentido una caricia», solo que ha cambiado la capacidad.

Para el aula
Actividad 1. ¿Qué variable medimos y en qué unidad? Por parejas, el alumnado escribe qué mide touchRead() y por qué no tiene unidades. Después discuten por qué la «fuerza de 0 a 100» es una escala inventada y qué habría que hacer para convertirla en una medida física.
Actividad 2. Tabla de calibración. Cada grupo apunta 10 valores sin tocar, 10 con la mano a 5 cm, 10 a 1 cm y 10 tocando. Calculan la media y el rango de cada situación. Pregunta clave: ¿se solapan los rangos? Si se solapan, ninguna caricia se puede distinguir con seguridad.
Actividad 3. Experimento con una sola variable. Cambiar solo una cosa cada vez: longitud del cable (10, 20 y 40 cm), tamaño de la bola de aluminio, mano seca o húmeda, con o sin guante. Formular una hipótesis antes de medir y comprobarla después.
Actividad 4. Dos placas, dos resultados. Si hay dos placas, comparar el valor sin tocar de cada una. ¿Por qué son distintos? Es la ocasión de hablar de tolerancias de fabricación y de por qué calibrar cada aparato por separado.
Actividad 5. Elegir el umbral. Probar umbrales de 10, 30 y 50 con varias personas. ¿Cuál da más falsos positivos? ¿Cuál deja caricias sin detectar? Es la primera puerta hacia el concepto de falso positivo y falso negativo, que es central en IA.
Actividad 6. Dato y conclusión. Un compañero toca el papel con un lápiz, otro con una naranja. ¿Qué podemos concluir sobre lo que «nota» el sensor y qué no?
Retos
- Reto 1. Haz que funcione. Monta el cable, carga el programa y consigue que el lince ronronee.
- Reto 2. Modifica algo. Cambia
UMBRALy el texto que aparece en la pantalla. Haz que el lince tenga un cuarto estado («muy contento») por encima de 90. - Reto 3. Añade otro sensor. El ESP32 tiene varios pines táctiles (T0 a T9). Conecta dos cables con papel y haz que el lince distinga una caricia en la cabeza de otra en el lomo.
- Reto 4. Guarda los datos. Haz que la página guarde la hora de cada caricia en una lista y la pueda descargar como CSV.
- Reto 5. Representa los datos. Dibuja con esos datos una gráfica de caricias por minuto y otra de la fuerza a lo largo del tiempo.
- Reto 6. Automatiza la calibración. Añade un botón en la página que ordene a la placa volver a calibrar sin reiniciarla. Necesitarás que la placa también reciba datos por Bluetooth.
- Reto 7. Haz que otro dispositivo participe. Con dos Heltec, haz que una caricia en una placa encienda algo en la otra usando LoRa.
- Reto 8. Conviértelo en una investigación. ¿Acariciamos igual de fuerte cuando hay música que cuando hay silencio? Diseña el experimento, recoge datos de al menos 20 personas y analiza los resultados con una hoja de cálculo.
Qué hemos aprendido
El Lince quería comprar una báscula y acabó con un cable y una bola de aluminio. Hemos visto que un sensor táctil ESP32 viene dentro del chip y mide cambios de capacidad eléctrica, que el número que devuelven solo tiene sentido frente a una referencia, y que calibrar es medir primero cómo es «no pasar nada». También hemos separado lo que el aparato mide (una capacidad) de lo que nosotros queremos creer que mide (una caricia).
El próximo paso natural es guardar estas medidas, compararlas entre personas y placas, y empezar a hacer preguntas que se puedan responder con datos.
Para seguir investigando
Si quieres ver cómo se prepara la conexión Bluetooth con la placa, repasa el letrero del Lince con Heltec LoRa 32, el termómetro Bluetooth con Heltec y DHT22 y el semáforo Bluetooth con Heltec. Para conocer los detalles técnicos de los sensores táctiles del chip, la documentación oficial de Espressif sobre el sensor táctil explica cómo funciona el contador por dentro.

