
Chismes del Lince 24: Pizarra de LEDs micro:bit (dibuja y escribe desde el móvil)
La pizarra de LEDs micro:bit es el Chisme 24: dibujas con el dedo en la pantalla del móvil y los 25 LEDs de la micro:bit se encienden igual, al momento, por Bluetooth. También puedes escribir un mensaje corto y verlo correr por la pantalla de la placa.
Hasta ahora la micro:bit nos mandaba datos al móvil: la brújula, el nivel de burbuja, los gestos de Simón dice o la estación del tiempo. Hoy es al revés: el móvil manda y la micro:bit recibe.
Antes de empezar: la pantalla de la micro:bit solo tiene 25 LEDs que se encienden o se apagan (sin colores ni brillos), así que los dibujos son de 5 × 5. Los mensajes pueden tener hasta 20 letras y la micro:bit no sabe escribir tildes ni la ñ: la página las cambia sola.
Pizarra de LEDs micro:bit: qué vamos a construir
La página tiene dos pestañas:
- Dibujar: una cuadrícula de 5 × 5 círculos. Tócalos o arrastra el dedo para encenderlos y apagarlos. Hay dibujos para empezar (corazón, carita, casa y flecha) y un botón para borrar. Al pulsar Enviar a la micro:bit, el dibujo aparece en la placa. Si envías la pizarra vacía, la micro:bit se apaga.
- Escribir: un cuadro de texto de hasta 20 letras, con mensajes de ejemplo (HOLA, TE QUIERO, LINCE, GRACIAS). Al pulsar Enviar mensaje, el texto corre por la pantalla de la micro:bit.
Lo que necesitas
- Una micro:bit V2 con su portapilas (o enchufada al USB).
- Un móvil Android con Chrome, con el Bluetooth y la ubicación encendidos. En iPhone, Safari no sirve: hay que abrir la página en el navegador Bluefy.
- El ordenador con MakeCode para programar.
Paso 1. Programa la micro:bit
Crea un proyecto nuevo en MakeCode y usa bloques. Es lo contrario de las anteriores: la micro:bit recibe en lugar de enviar.
- Añade la extensión Bluetooth (Extensiones → Bluetooth). No añadas la de Radio.
- Crea una variable nueva llamada
linea. - Monta este programa:
al iniciar
bluetooth servicio uart
bluetooth al recibir datos nueva línea()
establecer linea auart bluetooth leer hasta nueva línea()sicarácter en 0 de linea="#"entoncespara índice y de 0 a 4(cambia el nombreíndicepory)para índice x de 0 a 4(cámbialo porx)sicarácter en (1 + y × 5 + x) de linea="1"entoncesencender x x y ysi noapagar x x y y
si nomostrar texto linea

Si no encuentras algún bloque, pulsa el botón JavaScript, pega esto y vuelve a Bloques; MakeCode lo convierte solo:
javascript
let linea = ""
bluetooth.startUartService()
bluetooth.onUartDataReceived(serial.delimiters(Delimiters.NewLine), function () {
linea = bluetooth.uartReadUntil(serial.delimiters(Delimiters.NewLine))
if (linea.charAt(0) == "#") {
for (let y = 0; y <= 4; y++) {
for (let x = 0; x <= 4; x++) {
if (linea.charAt(1 + y * 5 + x) == "1") {
led.plot(x, y)
} else {
led.unplot(x, y)
}
}
}
} else {
basic.showString(linea)
}
})
Paso 2. Sin emparejamiento (muy importante)
Antes de descargar, hay que cambiar un ajuste para que la página pueda conectar sin pedir códigos:
- Pulsa el engranaje ⚙️ → Configuración del proyecto.
- Marca No se requiere emparejamiento (No Pairing Required: Anyone can connect via Bluetooth).
Paso 3. Pásalo a la micro:bit
- Descarga el programa (el archivo
.hex) y cópialo a la unidad MICROBIT. - Espera a que deje de parpadear el led amarillo de atrás.
- Reinicia la micro:bit con el botón de atrás.
Si cambias algo del programa, repite los pasos 2 y 3.
Paso 4. Prepara el móvil
- Bluetooth y ubicación encendidos.
- Si en Ajustes → Bluetooth aparece “BBC micro:bit”, pulsa Olvidar. No hay que emparejarla desde ahí.
- Cierra cualquier otra app que use la micro:bit.
Paso 5. Abre la página y conecta
- Abre en Chrome, escribiendo la dirección directamente: https://montesteam.org/app/chisme-24-pizarra.html
- Pulsa Conectar micro:bit.
- Elige BBC micro:bit […] en la lista.
- Cuando la página ponga Conectado a…, ya puedes dibujar.
Si la conexión se corta, la página reintenta conectar sola hasta 20 veces. Mantén la micro:bit cerca y encendida.
Paso 6. Dibuja y escribe
- En Dibujar, toca los círculos para encenderlos (o elige uno de los dibujos de abajo) y pulsa Enviar a la micro:bit.
- En Escribir, teclea un mensaje corto y pulsa Enviar mensaje. Espera a que termine de pasar por la pantalla antes de mandar otro.
Mostrar imagen
Cómo funciona la pizarra de LEDs micro:bit
La página manda a la micro:bit una línea de texto por Bluetooth, y la micro:bit decide qué hacer según cómo empiece:
- Si empieza por
#, es un dibujo: después del#vienen 25 caracteres,1para LED encendido y0para apagado, leídos fila a fila. El corazón, por ejemplo, es#0101011111111110111000100. - Si no empieza por
#, es un mensaje, y la micro:bit lo muestra tal cual.
Por eso en el programa aparece 1 + y × 5 + x: el 1 salta el #, y × 5 salta las filas anteriores y x es la columna. Los datos viajan por Bluetooth en trozos muy pequeños (4 bytes cada vez), y el final de la línea avisa a la micro:bit de que ya puede leer.
Y como la página quita el # de los mensajes y las tildes, un mensaje nunca se confunde con un dibujo.

Retos para clase
- Tus iniciales: dibújalas en la cuadrícula y mándalas a la micro:bit. ¿Se entienden en 5 × 5?
- Descifra el código: escribe en papel cuadriculado el dibujo que sale de
#0010001110101010010000100, y comprueba qué es. - Mensaje secreto: manda un mensaje a un compañero sin que vea qué escribes. ¿Cuánto tarda en leerlo?
- Animación: manda varios dibujos seguidos, uno tras otro. ¿Cuántos puedes enviar por minuto?
- Reto avanzado: ahora los LEDs solo están encendidos o apagados. ¿Cómo cambiarías el protocolo para mandar también el brillo de cada LED? (Pista: en MakeCode existe un bloque para encender un LED con un brillo concreto.)
Si algo falla
- No aparece en la lista: comprueba que está encendida, que el programa está descargado y que activaste «No se requiere emparejamiento».
- Se conecta pero no pasa nada al enviar: revisa que
bluetooth servicio uartesté en «al iniciar» y que el bloque «al recibir datos» use nueva línea. - El dibujo sale desordenado: comprueba que los nombres de las variables del bucle son
y(filas, la de fuera) yx(columnas, la de dentro), y que la cuenta es1 + y × 5 + x. - El mensaje se corta o se mezcla con otro: espera a que termine de pasar antes de enviar el siguiente.
- Aparecen letras distintas a las que escribí: la micro:bit no sabe tildes ni ñ; la página las cambia (á por a, ñ por n…). Debajo del cuadro te dice qué va a leer la micro:bit.
- Error «NotSupportedError»: olvida la micro:bit en Ajustes → Bluetooth, borra la caché de la app «Bluetooth» del sistema y reinicia el móvil y la micro:bit.
- Error «NetworkError»: comprueba «No se requiere emparejamiento», que no haya otra app conectada y reinicia la micro:bit.
- La conexión se corta: la página reintenta sola hasta 20 veces; mantén la micro:bit cerca y encendida.
- En iPhone no conecta: abre la página con Bluefy.
Preguntas frecuentes
¿Se puede dibujar en la micro:bit desde el móvil?
Sí. Con la página de este Chisme dibujas en una cuadrícula de 5 × 5 en el móvil y la micro:bit enciende los mismos LEDs por Bluetooth. Necesitas cargar antes el programa de MakeCode del Paso 1.
¿Qué móvil o navegador necesito?
Un móvil Android con Chrome (o Edge), con el Bluetooth y la ubicación encendidos. En iPhone hay que usar el navegador Bluefy, porque Safari no permite Web Bluetooth.
¿Por qué la micro:bit no escribe tildes ni la ñ?
La pantalla de LEDs de la micro:bit solo conoce los caracteres básicos. La página cambia las letras con tilde por su versión sin tilde y la ñ por n antes de enviar el mensaje.
¿Cuántas letras puede tener un mensaje?
Hasta 20. Y conviene esperar a que termine de pasar por la pantalla antes de mandar otro.
Qué hemos aprendido
Una pantalla de 25 LEDs se puede describir con 25 ceros y unos: es información digital en su forma más sencilla. Hemos visto que dos aparatos se entienden si acuerdan un protocolo (aquí, «si empieza por # es un dibujo»), que los datos se mandan en líneas con un final que avisa de que el mensaje está completo, y que dos bucles anidados recorren una cuadrícula fila a fila. Y todo esto, que es lo que hace cualquier pantalla del mundo, cabe en un programa de seis bloques.
¿Quieres seguir con la micro:bit? Mira el curso gratuito de micro:bit de MonteSTEAM y el resto de los Chismes del Lince.
Datos para WordPress
- Título de la entrada: Chismes del Lince 24: Pizarra de LEDs micro:bit (dibuja y escribe desde el móvil)
- Slug: chismes-del-lince-24-pizarra-leds-microbit
- Categorías: AIDARAC – Ciencia Ciudadana, Los Chismes del Lince, Proyecto AIDARAC (quita «Uncategorized»)
- Etiquetas: pizarra de LEDs, micro:bit, Bluetooth, Web Bluetooth, UART, makecode, programación con bloques, JavaScript, Chismes del Lince, steam, dibujar en micro:bit
- Imagen destacada:
pizarra-leds-microbit.png
Rank Math (panel SEO de la entrada)
- Palabra clave principal: pizarra de LEDs micro:bit
- Palabras clave secundarias (Rank Math gratis admite hasta 5 en total): micro:bit Bluetooth, dibujar en micro:bit desde el móvil, enviar mensajes a micro:bit
- Título SEO: Pizarra de LEDs micro:bit: dibuja y escribe desde el móvil
- Meta descripción: Pizarra de LEDs micro:bit: dibuja con el dedo en el móvil y mira cómo se enciende en la placa por Bluetooth. También puedes mandarle mensajes.
- Texto alternativo de la imagen destacada: Pizarra de LEDs micro:bit con un corazón dibujado desde el móvil
- Pestaña Social (Facebook y X): título y descripción iguales que los SEO, con la imagen destacada
- Tipo de Schema: Article
- Robots: index, follow (por defecto)
Lista de comprobación para que Rank Math dé luz verde
- La palabra clave está en el título SEO (al principio), en la meta descripción, en el slug, en el primer párrafo, en dos encabezados H2 y en el texto alternativo de las imágenes.
- Hay enlaces internos (Chismes 20, 21, 22 y 23, el curso de micro:bit y la categoría) y un enlace externo a MakeCode.
- Sube las tres imágenes con los nombres
24-pizarra.png,24-bloques.pngy24-pizarramuestra.jpegy sustituye los marcadoresSUBIR-IMAGEN-…; conserva los textos alternativos que llevan puestos. - Si usas el editor de bloques, en «Preguntas frecuentes» añade el bloque FAQ de Rank Math y pega ahí las cuatro preguntas: así Google puede mostrarlas como resultado enriquecido.
- Si la entrada final cambia mucho de tamaño, repasa la densidad de la palabra clave: Rank Math la quiere en torno al 1 %.
HTML
<!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 24 · La pizarra de LEDs</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.7rem;margin:.2rem 0 .1rem;letter-spacing:.01em}
h1 span{color:var(--norte)}
.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.alt{background:transparent;color:var(--pine)}
button:disabled{opacity:.5;cursor:not-allowed}
button:focus-visible,input:focus-visible,.led:focus-visible{outline:3px solid var(--slow);outline-offset:2px}
.fila{display:flex;gap:.5rem;justify-content:center;flex-wrap:wrap}
#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}
.seg{display:flex;gap:.4rem;margin:.6rem 0}
.seg button{flex:1;background:transparent;color:var(--pine)}
.seg button[aria-pressed="true"]{background:var(--pine);color:var(--bg)}
[hidden]{display:none!important}
.placa{width:min(88vw,21rem);margin:.4rem auto .8rem;padding:.9rem;border-radius:1.3rem;background:#0d1b17;
border:3px solid var(--pine);box-shadow:0 .6rem 1.2rem rgba(0,0,0,.25)}
#rej{display:grid;grid-template-columns:repeat(5,1fr);gap:.55rem;touch-action:none;user-select:none;-webkit-user-select:none}
.led{aspect-ratio:1;border-radius:50%;background:#22342e;box-shadow:inset 0 .15rem .3rem rgba(0,0,0,.55);cursor:pointer;transition:background .12s,box-shadow .12s}
.led.on{background:radial-gradient(circle at 35% 30%,#ffc2b9,#ff4b3a 55%,#c8321f);box-shadow:0 0 .9rem #ff4b3a,0 0 .25rem #ff9a8c inset}
#rej.flash{animation:flash .6s}
@keyframes flash{50%{filter:brightness(1.7) saturate(1.3)}}
@media (prefers-reduced-motion:reduce){#rej.flash{animation:none}}
.chips{display:flex;gap:.4rem;justify-content:center;flex-wrap:wrap;margin:.2rem 0 .7rem}
.chips button{min-height:2.6rem;padding:.4rem .8rem;font-size:.95rem}
.grande{width:100%;max-width:21rem;font-size:1.2rem;min-height:3.4rem}
input[type=text]{width:100%;max-width:21rem;font:inherit;font-size:1.3rem;text-align:center;padding:.7rem;border-radius:.9rem;
border:2px solid var(--line);background:var(--card);color:var(--ink)}
.nota{color:var(--soft);font-size:.88rem;margin:.4rem 0 .8rem;min-height:1.3em}
.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>La pizarra de <span>LEDs</span></h1>
<p class="sub">Dibuja en el móvil y mira cómo se enciende en la micro:bit.</p>
<div class="fila">
<button id="conectar">Conectar micro:bit</button>
</div>
<div id="estado" role="status">Sin conectar.</div>
<div id="ayuda"></div>
<div class="seg" role="group" aria-label="Qué quieres hacer">
<button id="t1" aria-pressed="true">Dibujar</button>
<button id="t2" aria-pressed="false">Escribir</button>
</div>
<section id="p1">
<div class="placa"><div id="rej" aria-label="Pizarra de 5 por 5 LEDs"></div></div>
<div class="chips" aria-label="Dibujos para empezar">
<button class="alt" data-p="corazon">❤️ Corazón</button>
<button class="alt" data-p="carita">😀 Carita</button>
<button class="alt" data-p="casa">🏠 Casa</button>
<button class="alt" data-p="flecha">⬆️ Flecha</button>
<button class="alt" id="borrar">🧽 Borrar</button>
</div>
<button id="envD" class="grande" disabled>Enviar a la micro:bit</button>
<p class="nota">Toca o arrastra el dedo para encender y apagar. Si envías la pizarra vacía, la micro:bit se apaga.</p>
</section>
<section id="p2" hidden>
<input type="text" id="txt" maxlength="20" placeholder="HOLA" autocomplete="off" autocapitalize="characters" aria-label="Mensaje">
<p class="nota" id="vista">Hasta 20 letras. La micro:bit no sabe escribir tildes ni la ñ: la página las cambia sola.</p>
<div class="chips" aria-label="Mensajes para empezar">
<button class="alt" data-m="HOLA">HOLA</button>
<button class="alt" data-m="TE QUIERO">TE QUIERO</button>
<button class="alt" data-m="LINCE">LINCE</button>
<button class="alt" data-m="GRACIAS">GRACIAS</button>
</div>
<button id="envM" class="grande" disabled>Enviar mensaje</button>
<p class="nota">Espera a que el mensaje termine de pasar antes de mandar otro.</p>
</section>
<details class="guia">
<summary>Guía de uso</summary>
<ol>
<li><b>Conecta</b> la micro:bit: pulsa «Conectar micro:bit» y elige BBC micro:bit.</li>
<li>En <b>Dibujar</b>, toca los círculos para encenderlos (o elige un dibujo de los de abajo) y pulsa <b>Enviar</b>.</li>
<li>En <b>Escribir</b>, teclea un mensaje corto y pulsa <b>Enviar mensaje</b>: correrá por la pantalla.</li>
<li>Si no pasa nada, mira que el programa de la micro:bit esté descargado y que hayas activado «No se requiere emparejamiento».</li>
</ol>
</details>
<p class="nota" style="margin-top:1rem">Versión 5</p>
</main>
<script>
const UART_SERVICE = "6e400001-b5a3-f393-e0a9-e50e24dcca9e";
const UART_RX = "6e400002-b5a3-f393-e0a9-e50e24dcca9e";
const $ = id => document.getElementById(id);
const espera = ms => new Promise(r => setTimeout(r, ms));
const vibra = p => { if (navigator.vibrate) navigator.vibrate(p); };
const PRE = {
corazon: "0101011111111110111000100",
carita: "0101001010000001000101110",
casa: "0010001110111111111111011",
flecha: "0010001110101010010000100"
};
let leds = Array(25).fill(0), rx = null, pintando = null, ocupado = false, conectando = false, paso = "";
/* Pizarra */
const rej = $("rej"), celdas = [];
for (let i = 0; i < 25; i++){
const c = document.createElement("div");
c.className = "led"; c.dataset.i = i; c.tabIndex = 0; c.setAttribute("role", "button");
c.setAttribute("aria-label", "Fila " + (Math.floor(i / 5) + 1) + ", columna " + (i % 5 + 1));
c.setAttribute("aria-pressed", "false");
rej.appendChild(c); celdas.push(c);
}
function pintar(){ celdas.forEach((c, i) => { c.classList.toggle("on", !!leds[i]); c.setAttribute("aria-pressed", String(!!leds[i])); }); }
function poner(i, v){ if (leds[i] === v) return; leds[i] = v; pintar(); vibra(8); }
rej.addEventListener("pointerdown", e => {
const c = e.target.closest(".led"); if (!c) return;
e.preventDefault(); try { rej.setPointerCapture(e.pointerId); } catch (_) {}
pintando = leds[+c.dataset.i] ? 0 : 1; poner(+c.dataset.i, pintando);
});
rej.addEventListener("pointermove", e => {
if (pintando === null) return;
const el = document.elementFromPoint(e.clientX, e.clientY), l = el && el.closest && el.closest(".led");
if (l) poner(+l.dataset.i, pintando);
});
["pointerup", "pointercancel"].forEach(n => rej.addEventListener(n, () => { pintando = null; }));
rej.addEventListener("keydown", e => {
const c = e.target.closest(".led");
if (c && (e.key === "Enter" || e.key === " ")){ e.preventDefault(); poner(+c.dataset.i, leds[+c.dataset.i] ? 0 : 1); }
});
document.querySelectorAll("[data-p]").forEach(b => b.onclick = () => { leds = [...PRE[b.dataset.p]].map(Number); pintar(); });
$("borrar").onclick = () => { leds.fill(0); pintar(); };
pintar();
/* Pestañas */
function pestana(n){
$("p1").hidden = n !== 1; $("p2").hidden = n !== 2;
$("t1").setAttribute("aria-pressed", String(n === 1)); $("t2").setAttribute("aria-pressed", String(n === 2));
}
$("t1").onclick = () => pestana(1); $("t2").onclick = () => pestana(2);
/* Mensajes: la micro:bit solo escribe letras sin tilde */
const limpiar = s => s.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^\x20-\x7e]/g, "").replace(/#/g, "").slice(0, 20);
$("txt").addEventListener("input", () => {
const t = limpiar($("txt").value);
$("vista").textContent = t && t !== $("txt").value ? "La micro:bit leerá: «" + t + "»" : "Hasta 20 letras. La micro:bit no sabe escribir tildes ni la ñ: la página las cambia sola.";
});
document.querySelectorAll("[data-m]").forEach(b => b.onclick = () => { $("txt").value = b.dataset.m; $("txt").dispatchEvent(new Event("input")); });
/* Envío: una línea por Bluetooth, en trozos de 20 bytes. «#» + 25 dígitos = dibujo; otra cosa = mensaje */
let tamTrozo = 4; /* bytes por escritura: empieza con 4, que es lo que envía la página 21 (CAL) y le funciona a la micro:bit */
const propsDe = c => ["read", "write", "writeWithoutResponse", "notify", "indicate"].filter(k => c.properties[k]).join(", ");
async function enviarTrozo(t){
/* Igual que la página 21: «sin respuesta» si el canal lo permite; los otros métodos, de respaldo */
const sr = rx.properties.writeWithoutResponse && typeof rx.writeValueWithoutResponse === "function";
const orden = sr ? ["writeValueWithoutResponse", "writeValueWithResponse", "writeValue"] : ["writeValueWithResponse", "writeValue"];
let ult = null;
for (const m of orden){
if (typeof rx[m] !== "function") continue;
try { await rx[m](t); return; } catch (e){ ult = e; ult.metodo = m; await espera(100); }
}
throw ult;
}
async function escribir(txt){
if (!rx) throw new Error("No se encuentra el canal para enviar datos a la micro:bit. Características: " + diag);
const bytes = new TextEncoder().encode(txt + "\n");
let pos = 0;
while (pos < bytes.length){
const t = bytes.slice(pos, pos + tamTrozo);
try { await enviarTrozo(t); pos += t.length; await espera(40); }
catch (e){
const err = new Error((e && e.message ? e.message : "fallo desconocido") + " [byte " + pos + " de " + bytes.length + ", método " + (e && e.metodo) +
", canal " + rx.uuid.slice(0, 8) + " (" + propsDe(rx) + ")]");
err.name = e && e.name ? e.name : "Error"; throw err;
}
}
}
$("envD").onclick = async () => {
if (ocupado) return;
try {
await escribir("#" + leds.join(""));
rej.classList.remove("flash"); void rej.offsetWidth; rej.classList.add("flash");
vibra([30, 40, 30]); estado("¡Enviado! Mira la micro:bit.");
} catch (e){ estado("No se pudo enviar (" + (e.name || "error") + ").", (e.message || "") + " Comprueba que la micro:bit sigue conectada."); }
};
$("envM").onclick = async () => {
if (ocupado) return;
const t = limpiar($("txt").value).trim();
if (!t){ estado("Escribe un mensaje primero."); return; }
ocupado = true; $("envM").disabled = true; $("envM").textContent = "Pasando el mensaje…";
try {
await escribir(t); vibra([30, 40, 30]); estado("¡Enviado! Mira la micro:bit.");
await espera(1200 + t.length * 650);
} catch (e){ estado("No se pudo enviar (" + (e.name || "error") + ").", (e.message || "") + " Comprueba que la micro:bit sigue conectada."); }
ocupado = false; $("envM").textContent = "Enviar mensaje"; $("envM").disabled = $("envD").disabled;
};
/* Conexión Bluetooth: el mismo código que funciona en las páginas 21, 22 y 23 */
let pasoCx = "", diag = "", dispositivo = null, reconectando = false;
function estado(t, ayuda){ if (!/^Error/.test(t)) pasoCx = t; $("estado").textContent = t; $("ayuda").textContent = ayuda || ""; }
function botones(on){ $("envD").disabled = !on; $("envM").disabled = !on; }
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 BBC micro:bit.";
else if (nombre === "NotFoundError") a = "La micro:bit no tiene el servicio UART. Revisa el bloque «bluetooth servicio uart» y copia el .hex nuevo.";
else if (nombre === "NotSupportedError") a = "No se pudo activar el envío de datos. Olvida la micro:bit en Ajustes → Bluetooth, borra la caché de la app «Bluetooth» del sistema y reinicia el móvil y la micro:bit.";
else if (nombre === "NetworkError") a = "No se pudo abrir la conexión. Comprueba «No se requiere emparejamiento» en MakeCode, que no haya otra app conectada y reinicia la micro:bit.";
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;
}
function recibir(){} /* la micro:bit no nos manda nada; activamos el canal igual que en las otras páginas */
async function enlazar(dev){
const svc = await conectarGatt(dev);
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 === "6e400003-b5a3-f393-e0a9-e50e24dcca9e") - (a.uuid === "6e400003-b5a3-f393-e0a9-e50e24dcca9e"));
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;
/* Canal por el que escribimos a la micro:bit (no corta la conexión si no se encuentra: se avisa al enviar) */
rx = chars.find(c => c.properties.write || c.properties.writeWithoutResponse) || null;
botones(true);
estado("Conectado a " + (dev.name || "micro:bit") + ". ¡A dibujar!");
}
async function reconectar(){
rx = null; botones(false);
if (reconectando) return;
reconectando = true;
for (let i = 1; i <= 20; i++){
estado("Conexión perdida. Reintentando… (" + i + " de 20)", "Mantén la micro:bit 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 micro:bit…");
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>


