Hechos son amores…

Hoy no voy a escribir de informática, lo siento, quizá para la próxima entrada me encuentre con ánimo, pero hoy voy a daros la turra sobre política, así que el que no le interese que vaya abandonando la página. Uno tiene su corazoncito y hay cosas que mejor dejar por escrito antes de que se te pudran dentro, así que hoy vamos a hablar un poco sobre «hechos e intenciones» en la política española.

Cuando le hago una pregunta a mi mujer, p.ej. «¿Qué tiempo hará en la calle?» en lugar de darme una respuesta a la pregunta su cerebro se pone a maquinar a toda prisa buscando la intención con la que le he hecho la pregunta para contraatacar con otra pregunta destinada a desactivar cualquier posible intención, por ejemplo, «¿Es que quieres irte de juerga?» y adornarlo con un posible reproche «con la de cosas que hay por hacer en casa». En ningún caso se plantea decirme, «pues hace frío» o un simple «ni idea», por lo que finalmente estas cosas se las termino preguntando a Alexa que, al menos, no me juzga por mis preguntas (al menos todavía no).

En la política española estamos llegando a unos niveles tales que ya la polarización no nos permite pensar sobre cualquier tema que haya planteado el «contrario» sino que, hoy más que nunca, antes de reaccionar siempre intentamos buscar las intenciones del otro con esa afirmación. Y en ese escenario termina pasando que las intenciones (reales o inventadas) se ponen al mismo nivel que los hechos (si, esas cosas que pasan y que no se puede decir que no han pasado).

Por ejemplo, es un hecho que hay decenas de miles de muertos en Gaza, asesinados por el ejército Israelí. Una hecho incontestable, corroborable y sin lugar a dudas (las cifras podrán bailar, pero nadie puede dudar de ello). Pero si alguien lo menciona automáticamente se cree que la intención es perjudicar a los judíos y se le tacha de antisemita. Es cierto que esto es un éxito de los sionistas, conseguir que la suya sea una guerra religiosa, pero también es cierto que todos los que compran ese argumento no se han parado a pensar ni siquiera un momento si no sería más sencillo arreglar los problemas con unos cuantos miles de muertos menos.

Si en cualquier país civilizado del mundo se descubriese que un cargo público estuviese viviendo en una casa pagada íntegramente con los resultados de un delito fiscal cuando menos el partido de dicho cargo público le pediría explicaciones. En España dicho partido estaba entretenido atacando con argumentos espúreos a la mujer del presidente del gobierno y porteriormente a la fiscalía del estado. Porque el hecho de que el delito sea cierto, que el afectado lo haya confesado y que cientos de miles de euros se hubiesen camuflado en facturas falsas parece que carece de envergadura comparado con la intención de quien lo denunció (malos malísimos que quieren dejar mal a una de los nuestros). La borregada sacó las palomitas y, en lugar de exigir conocer el origen de la fortuna de alguien que, antes de conocer a la presidenta era un mero técnico sanitario, y después compra en efectivo áticos en un Madrid de precios imposibles, se dedica a mirar si el correo que MAR ya envió a los periódicos antes fue filtrado por el fiscal o por el propio PSOE. Vamos, que se está juzgando al mismo nivel el delito real con las intenciones de los que denunciaron el delito.

Si esta actitud de aborregamiento (paso de llamarlo polarización) es preocupante, lo es mucho más cuando afecta a la vida y muerte de muchísimas personas. No solo las 7291 que Ayuso mandó abandonar a una muerte inhumana sino a las más de 200 que se llevó la DANA en Valencia mientras el presidente de la comunidad estaba viviendo la vida loca, ignorando sus deberes por los que cobra de los ciudadanos y luego gestionando lo peor posible esta desgracia. Eso si, como decía cierto alcalde «seremos fascistas pero sabemos gestionar» otra obra maestra del surrealismo, los miles de millones que se van a dejar los ciudadanos, via impuestos, en ayudar a los afectados por la DANA se van a repartir entre empresarios afines que, obviamente, pasarán a financiar las futuras campañas electorales del partido o, incluso, a darle una buena puerta giratoria al inútil de Mazón. Pero esos son hechos y no importan, lo importante es la intención. Si criticas a Mazón es que tu intención es meterte con la derecha y ellos ya se defienden solos. Son unos maestros porque luego echan balones fuera atacando al estado central por no arrebatarles las competencias y así queda todo nivelado.

Y es que la intención cuenta, claro que si, pero que eso no te distraiga de los hechos. Se supone que el poder judicial está para poder determinar si los hechos son constitutivos de delito, no si las intenciones son malvadas. Parece ser que una parte del cuerpo de la judicatura ha decidido que las informaciones de pastiches como okdiario o las habladurías sin verificar pueden ser indicios suficientes para abrir casos. Pero ahora, no puedes decir que se han abierto esos casos con cierta intención (eso sería acusar de lawfare) porque entonces tu intención sería criticar a los jueces y, claro, eso ya es malísimo en si mismo. Además, ahora sale casi gratis mentir. Te puedes inventar la noticia o targiversarla sin que esto suponga el menor problema, mentir ya no está mal visto, según parece eres más listo cuantas más mentiras haces pasar por buenas…

Me disculpo solo por haber destacado aquí casos de un solo signo político. Si queréis podemos hablar de los EREs de andalucía, del caso Koldo o de cualquier otra cosa de otro signo con hechos demostrados, los efectos son los mismos. Siempre estaremos sesgados por la intención con la que hacemos los comentarios y eso ya nos descalifica y, como haría mi mujer, buscaríamos algo con lo que criticarme para neutralizar esas aviesas intenciones.

Esta dinámica tiene graves implicaciones (esto se lo he preguntado a una IA):

  • Dificulta la rendición de cuentas de los cargos públicos.
  • Obstaculiza la resolución efectiva de problemas sociales y políticos.
  • Fomenta la proliferación de noticias falsas y manipulación informativa.

Como conclusión solo puedo decir que es crucial recuperar un enfoque basado en hechos verificables en el debate político. Aunque las intenciones son relevantes, no deben eclipsar la realidad de los acontecimientos. Solo así se podrá mejorar la calidad de la discusión pública y abordar de manera efectiva los desafíos que enfrenta la sociedad española.

Generar imágenes por IA en tu propio ordenador

Ya vimos en la entrada anterior como poder tener nuestro propio chat-gpt sin pagar nada a nadie usando modelos opensource y nuestra GPU, ahora le toca el turno a la posibilidad de generar imágenes por Inteligencia Artificial mediante el mismo método, en casa, de forma privada y sin tener que pagar licencias. Al lío…

La imagen anterior ha sido generada en mi ordenador, con mi tarjeta gráfica y con un prompt muy sencillito, básicamente le he pedido un robot pintando con pinceles en la pantalla del ordenador… Y me ha salido esto (hay más parámetros, pero no he tocado nada especial). Para generar estas imágenes vamos a utilizar Stable Diffussion, que es un modelo de aprendizaje automático para generar imágenes digitales de alta calidad a partir de descripciones en lenguaje natural (wikipedia). Es de código abierto y no impone restricciones a las imágenes que produce.

Como somos hombres (y mujeres) de acción os voy a dar la receta rápida para tener stable difussion y un interfaz de usuario (automatic1111) funcionando en cuestión de minutos (bueno, esto depende de vuestra conexión a internet que hay muchos gigas que descargarse). La receta original, que os recomiendo seguir si queréis experimentar un poco más con el tema, la saqué de aquí: https://github.com/AbdBarho/stable-diffusion-webui-docker pero yo he preparado una imagen que ya tiene todo lo necesario, así que lo único que tenéis que hacer es crear un archivo docker-compose.yml con este contenido:

services:
  sd-auto:
    image: yoprogramo/sd-auto:78
    ports:
      - "7860:7860"
    volumes:
      - ./data:/data
      - ./output:/output
    stop_signal: SIGKILL
    environment:
      - CLI_ARGS=--allow-code --medvram --xformers --enable-insecure-extension-access --api
      - COMMANDLINE_ARGS=--share
    restart: unless-stopped
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              device_ids:
                - "0"
              capabilities:
                - compute
                - utility

Luego, estoy suponiendo que usáis linux y tenéis la configuración de docker y de la tarjeta gráfica que ya vimos en el anterior post, solo hay que ejecutar:

docker compose up -d

Como os he dicho el proceso de descarga inicial de la imagen y del modelo van a tardar un poco (reservaos mínimo 20Gb para todo), pero si todo va bien en unos minutos podréis acceder a la url http://localhost:7860 y veréis la interfaz de AUTOMATIC1111 para stable difussion.

Inicialmente el modelo descargado es sdv1.5-pruned-emaonly que tiene sus limitaciones pero cabe en casi todas la memorias. Ya solo queda hacer la prueba, poniendo algo en el prompt y dandole a Generate.

Si no tienes demasiada memoria en tu tarjeta gráfica te saldrá algo como esto:

Pero si has sido capaz de generar una imagen, se abre todo un abanico de modelos que probar y opciones con las que trastear… El primer sitio para visitar es este:

https://civitai.com

Como tener tu propia IA en casa

Todos hemos oído y probado las bondades de chat-gpt o usado github copilot con tremendo éxito, pero estos sistemas tienen un problema principal, que son de pago. Sus modelos son cerrados y hay que pagar una licencia para poder utilizarlos en cosas útiles. Sin embargo, existe otra manera de experimentar con la Inteligencia Artificial generativa en casa, sin pagar licencias y teniendo todo el control. Solo necesitas un equipo medianamente moderno, una GPU y una cantidad de memoria abundante (o no tanta, pero podrás jugar con menos modelos). Te cuento aquí como instalar tu propio servicio de IA en tu ordenador.

Eso si, te lo cuento solo para Linux, si tienes algún otro sistema operativo de esos de juguete tendrás que buscarte la vida (te dejo enlaces para que puedas hacerlo por tu cuenta).

Como modelo de AI vamos a utilizar llama, modelo opensource de Meta y lo vamos a instalar con ollama. Hay varias guías para instalarlo directamente en tu ordenador, pero las últimas versiones de ubuntu (yo tengo la 24.04) son ciertamente reticentes a instalar paquetes python en el sistema, por lo que la solución más sencilla será usar docker para ello. Vamos a suponer que tenemos una GPU nvidia, y la porción de docker-compose necesaria para instalarte ollama sería esta:

  ollama:
    volumes:
      - ./ollama:/root/.ollama
    container_name: ollama
    pull_policy: always
    tty: true
    ports:
      - "11434:11434"
    restart: unless-stopped
    image: ollama/ollama:${OLLAMA_DOCKER_TAG-latest}
    deploy:
      resources:
        reservations:
          devices:
            - driver: ${OLLAMA_GPU_DRIVER-nvidia}
              count: ${OLLAMA_GPU_COUNT-1}
              capabilities:
                - gpu

Con esta configuración lo que hacemos es lanzar un servidor ollama accesible desde el puerto 11434 donde podemos usar el tty o el api. No voy a entrar en muchos detalles de cómo usar ollama, pero os recomiendo que le echéis un vistazo porque es la «madre del cordero» o de la llama, en este caso.

Para que esto funcione correctamente con la gpu hay que hacer un par de cositas previamente. Os recomiendo que miréis este repositorio para ver si se ha mejorado/modificado algo: https://github.com/valiantlynx/ollama-docker pero básicamente consiste en ejecutar lo siguiente:

curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \
  && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \
    sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \
    sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
sudo apt-get install -y nvidia-container-toolkit

# Configure NVIDIA Container Toolkit
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

Dado que vamos a necesitar un interfaz para gestionar los modelos y tener chats y demás lo siguiente que vamos a incluir en nuestro docker-compose es open-webui, modificamos nuestro docker-compose.yml para agregar lo siguiente (yo ya he contruido la imagen y la he subido a docker hub):

  open-webui:
    image: yoprogramo/open-webui:${WEBUI_DOCKER_TAG-latest}
    container_name: open-webui
    volumes:
      - ./open-webui:/app/backend/data
    depends_on:
      - ollama
    ports:
      - ${OPEN_WEBUI_PORT-3000}:8080
    environment:
      - 'OLLAMA_BASE_URL=http://ollama:11434'
      - 'WEBUI_SECRET_KEY='
    extra_hosts:
      - host.docker.internal:host-gateway
    restart: unless-stopped

Y creamos un archivo .env con el siguiente contenido:

OLLAMA_GPU_COUNT=all
SCARF_NO_ANALYTICS=true
DO_NOT_TRACK=true
ANONYMIZED_TELEMETRY=false

Y lanzar las imágenes si todo ha ido bien:

docker compose up -d

Con esto ya tendríamos corriendo nuestro servidor ollama y open-webui en nuestro propio ordenador… Simplemente tenemos que acceder con el navegador a localhost:3000

Lo primero que tenéis que hacer, una vez creado un usuario en el sistema (si, el primer usuario que se crea es administrador) es descargarse algún modelo de IA, para eso hay que entrar en la página de administración y acceder a la opción que pone «Obtener un modelo de Ollama.com», escribir el deseado y darle al botón de la derecha para descargarlo. En la imagen por ejemplo nos descargamos el modelo llama3.1 de 70B (son cerca de 42Gb, así que deberías tener espacio de sobra).

Una vez descargado ya estamos listos para usarlo, vete a la opción «nuevo Chat», selecciona el modelo en el desplegable superior y chatea con tu nueva AI…

En próximas entregas ya entraremos en más cosas que podemos hacer con nuestra IA local, seguro que no nos deja indiferentes.

Depurando PHP con Xdebug y Docker

Hasta hace poco he estado depurando PHP al viejo estilo, poniendo error_log, print_r y demás mensajes aquí y allá. La verdad es que era algo poco dañino y que, si programas bien, tampoco da mucho trabajo. Sin embargo echaba de menos las facilidades de java en el eclipse donde podía depurar de manera increíblemente eficiente sin modificar nada de mi código. El caso es que utilizando docker como base del desarrollo/despliegue de PHP se hacía muy complicado configurar las cosas para depurar. Al final, por circunstancias que no vienen al caso, me animé a ver cómo podríamos depurar código php desplegado usando docker… Y os hago aquí un resumen:

El problema básico

Supongamos que queremos hacer un programa básico en php y desplegarlo para poder verlo en nuestro navegador. Por ahora nos vamos a limitar a este archivo

phpinfo.php

<?php
phpinfo();

Si, es un archivo muy simplón, pero primero tenemos que desplegarlo utilizando docker y verlo en un navegador. Para ello creamos un directorio para nuestro proyecto (llamemosle php-samples, por ejemplo) y dentro del mismo creamos un subdirectorio llamado web y dentro de este directorio creamos el archivo phpinfo.php con el contenido anterior.

La forma más rápida

En el directorio superior creamos un archivo llamado docker-compose.yml con este contenido:

services:
  php:
    image: yoprogramo/php8.2:1.0.2
    volumes:
        - ./web:/var/www/Website
    ports:
        - 8080:80

Ya puestos cargamos el proyecto en visual studio code y nos quedará algo así:

Desde el terminal, o desde el menú de la extensión docker si la tenéis instalada, levantamos el contenedor. Una vez levantado ya podremos acceder a la url:

http://localhost:8080/phpinfo.php

Vale, prueba superada, ya tenemos nuestro servidor ejecutando php, ahora crearemos otro archivo dentro de web con un programa en php un poco más complejo para poder depurarlo a gusto. Vamos a llamar a ese archivo index.php y le daremos este contenido:

<?php
function isPrime($num) {
    if ($num <= 1) return false;
    if ($num == 2) return true;
    if ($num % 2 == 0) return false;
    for ($i = 3; $i <= sqrt($num); $i += 2) {
        if ($num % $i == 0) return false;
    }
    return true;
}

$randomNumber = rand(0, 19);

echo "<table border='1'>";
echo "<tr><th>Número</th><th>Primos menores que $randomNumber</th></tr>";
echo "<tr><td>$randomNumber</td><td>";

for ($i = 0; $i < $randomNumber; $i++) {
    if (isPrime($i)) {
        echo "$i ";
    }
}

echo "</td></tr>";
echo "</table>";
?>

Ahora podemos acceder a la url index.php (o no poner ruta ya que es el archivo que se cargará por defecto)

Vale, pero todo este rollo era para poder depurar el código, para poder hacerlo tenemos que hacer tres cosas:

  • Cambiar la imagen docker por yoprogramo/php8.2:1.0.2-xdebug en el docker-compose.yml y añadir este apartado al servicio php:
extra_hosts:
  - "host.docker.internal:host-gateway"
  • Instalar el plugin «PHP Debug»
  • Configurar el visual studio code para php, para ello pinchamos en el icono de depuración (run and debug) y pinchamos donde pone «create a launch.json file)

Esto nos creará un archivo por defecto donde tendremos que modificar la configuración «Listen for Xdebug» para añadir esto:

"pathMappings": {
   "/var/www/Website": "${workspaceRoot}/web"
}

Pulsamos sobre el icono de iniciar depuración con es configuración (o pulsamos F5)

Con todo configurado solo nos queda reiniciar el docker con la nueva imagen y poner algún breakpoint en el código (eso se hace pinchando a la izquierda del número de línea donde queramos que se pare la ejecución, por ejemplo dentro del bucle de index.php

Ahora volvemos a cargar la página index.php y si todo ha ido bien la ejecución se parará y podremos ver en el editor los valores de las variables

Y continuar la ejecución o hacerlo paso a paso

Si queréis saber cual es el camino difícil o cómo hacerlo con vuestras propias imágenes docker, solo tenéis que preguntar…

Usando una fuente de texto en IoT

Una de las características de los dispositivos que se usan para IoT es la limitación de recursos que tiene. No tienen un sistema operativo disponible y todo lo que queramos que haga hay que programarlo prácticamente de cero. Eso incluye cosas tan peregrinas como definir las fuentes de texto que se utilizarán para escribir en pantalla. Esto es algo que, generalmente, damos por hecho en cualquier otro sistema que programamos, pero que ofrece la oportunidad de ver un poco mejor cómo funciona la informática por dentro.

En principio las pantallas en las que querramos utilizar estas fuentes (en las que queramos escribir algo) son bastante pequeñas, la que vemos en la siguiente imagen, por ejemplo, es de 128×64 pixels (SSD1306):

¿Cómo hacemos para escribir un caracter en una pantalla de estas características? Quitando los comandos propios de inicialización, limpieza y demás, lo único que hacemos es indicar qué pixel queremos que se ilumine y cual no (si hay interés ya desarrollaremos un poco más en profundidad los comandos que se le mandan) y, para ahorrar ciclos y ancho de banda, hay que enviarle los datos en bytes completos (8 bits) y cada bit representa un pixel que se ilumina o no.

Hay software ya desarrollado para convertir fuentes ttf en código c que se puede usar con ciertas librerías, pero vamos a escoger el camino difícil, supongamos que queremos utilizar los primeros 95 caracteres ASCII empezando por el espacio. Serían estos:

Quitando el 127 que no nos interesa porque no es imprimible, una vez decididos los caracteres que queremos imprimir tenemos que decidir el tamaño que queremos utilizar. En este caso queremos que tengan una altura de 8 pixels y, como van a ser de ancho fijo, tendrán una anchura de 8 pixels igualmente. Un carácter se representaría entonces de esta manera:

Y su representación en bits de la primera fila sería 00011000 o lo que es lo mismo 0x18 en hexadecimal. Esta letra, traducida a bytes quedaría: 0x18, 0x3C, 0x66, 0x66, 0x7E, 0x66,0x66,0x00 y con esta información ya sabríamos qué bytes mandar a la pantalla cuando tuviésemos que escribir la letra.

Hacer este proceso para cada letra sería muy pesado, más teniendo en cuenta que son 95 caracteres, pero como nosotros somos programadores os voy a contar un método para extraer estos datos de una mejor manera. Vamos a generar una imagen y vamos a pintar allí las letras que queremos. Para ello abriremos el programa gráfico favorito que tengamos (el mío es gimp) y crearemos una imagen justo del tamaño para que quepan todas nuestras letras (ni más ni menos). En este caso son 95*8 = 760 y 8 de altura, con el fondo negro.

Una vez creada esa imagen, creamos un texto en la fuente que queramos y escribimos los caracteres que queremos:

 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~

Ajustamos para que quepan todos (quitamos alisado y hints varios) y quedará algo así:

Luego cambiamos el tipo de imagen a indexada con 2 colores (solo blanco y negro) y lo exportamos a formato raw:

Esto lo que hace es generarnos un archivo .data que contiene 1 byte por pixel que, como hemos indexado solo contendrá ceros o unos… Y ahora toca programar.

El objetivo es obtener una ristra de bytes juntando los ceros y unos agrupándolos de 8 en ocho. Luego veremos cómo usarlos. Lo más sencillo es hacer un scrip en python, así que escribimos algo así:

def convert_to_hex(byte_data):
    # Agrupamos los datos de 8 en 8
    byte_data = [byte_data[i:i+8] for i in range(0, len(byte_data), 8)]
    # Convertimos cada byte a su representación en hexadecimal
    resu = []
    for i in range(len(byte_data)):
        byte = sum([byte_data[i][j] << j for j in range(8)])
        # Añadimos el valor en hexadecimal a la lista resu
        resu.append(f'0x{byte:02X}')

    return resu

Y ahora solo nos queda leer de un archivo y escribirlo en otro como código c:

def read_binary_file(file_path):
    with open(file_path, 'rb') as file:
        return file.read()

def write_hex_file(hex_data, output_path):
    with open(output_path, 'w') as file:
        # Separamos los valores por comas y ponemos un salto de linea cada 16 valores
        file.write ('static const char font_8x8[] = {\n\t')
        slice_size = 16
        for i in range(0, len(hex_data), slice_size):
            file.write(', '.join(hex_data[i:i+slice_size]))
            file.write(',\n\t')
        file.write('};')

Lo juntamos todo en una función principal pasándole como parámetro el archivo de entrada y el de salida:

def main(input_file, output_file):
    binary_data = read_binary_file(input_file)
    hex_data = convert_to_hex(binary_data)
    write_hex_file(hex_data, output_file)

Y ya podemos ejecutarlo, el resultado sería algo así:

static const char font_8x8[] = {
	0x00, 0x0C, 0x36, 0x36, 0x0C, 0x00, 0x1C, 0x06, 0x18, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60,
	0x3E, 0x0C, 0x1E, 0x1E, 0x38, 0x3F, 0x1C, 0x3F, 0x1E, 0x1E, 0x00, 0x00, 0x18, 0x00, 0x06, 0x1E,
	0x3E, 0x0C, 0x3F, 0x3C, 0x1F, 0x7F, 0x7F, 0x3C, 0x33, 0x1E, 0x78, 0x67, 0x0F, 0x63, 0x63, 0x1C,
	0x3F, 0x1E, 0x3F, 0x1E, 0x3F, 0x33, 0x33, 0x63, 0x63, 0x33, 0x7F, 0x1E, 0x03, 0x1E, 0x08, 0x00,
	0x0C, 0x00, 0x07, 0x00, 0x38, 0x00, 0x1C, 0x00, 0x07, 0x0C, 0x30, 0x07, 0x0E, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x18, 0x07, 0x6E, 0x00,
	0x1E, 0x36, 0x36, 0x3E, 0x63, 0x36, 0x06, 0x0C, 0x0C, 0x66, 0x0C, 0x00, 0x00, 0x00, 0x30, 0x63,
	0x0E, 0x33, 0x33, 0x3C, 0x03, 0x06, 0x33, 0x33, 0x33, 0x0C, 0x0C, 0x0C, 0x00, 0x0C, 0x33, 0x63,
	0x1E, 0x66, 0x66, 0x36, 0x46, 0x46, 0x66, 0x33, 0x0C, 0x30, 0x66, 0x06, 0x77, 0x67, 0x36, 0x66,
	0x33, 0x66, 0x33, 0x2D, 0x33, 0x33, 0x63, 0x63, 0x33, 0x63, 0x06, 0x06, 0x18, 0x1C, 0x00, 0x0C,
	0x00, 0x06, 0x00, 0x30, 0x00, 0x36, 0x00, 0x06, 0x00, 0x00, 0x06, 0x0C, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x18, 0x0C, 0x3B, 0x00, 0x1E,
	0x36, 0x7F, 0x03, 0x33, 0x1C, 0x03, 0x06, 0x18, 0x3C, 0x0C, 0x00, 0x00, 0x00, 0x18, 0x73, 0x0C,
	0x30, 0x30, 0x36, 0x1F, 0x03, 0x30, 0x33, 0x33, 0x0C, 0x0C, 0x06, 0x3F, 0x18, 0x30, 0x7B, 0x33,
	0x66, 0x03, 0x66, 0x16, 0x16, 0x03, 0x33, 0x0C, 0x30, 0x36, 0x06, 0x7F, 0x6F, 0x63, 0x66, 0x33,
	0x66, 0x07, 0x0C, 0x33, 0x33, 0x63, 0x36, 0x33, 0x31, 0x06, 0x0C, 0x18, 0x36, 0x00, 0x18, 0x1E,
	0x06, 0x1E, 0x30, 0x1E, 0x06, 0x6E, 0x36, 0x0E, 0x30, 0x66, 0x0C, 0x33, 0x1F, 0x1E, 0x3B, 0x6E,
	0x3B, 0x3E, 0x3E, 0x33, 0x33, 0x63, 0x63, 0x33, 0x3F, 0x0C, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0x00,
	0x36, 0x1E, 0x18, 0x6E, 0x00, 0x06, 0x18, 0xFF, 0x3F, 0x00, 0x3F, 0x00, 0x0C, 0x7B, 0x0C, 0x1C,
	0x1C, 0x33, 0x30, 0x1F, 0x18, 0x1E, 0x3E, 0x00, 0x00, 0x03, 0x00, 0x30, 0x18, 0x7B, 0x33, 0x3E,
	0x03, 0x66, 0x1E, 0x1E, 0x03, 0x3F, 0x0C, 0x30, 0x1E, 0x06, 0x7F, 0x7B, 0x63, 0x3E, 0x33, 0x3E,
	0x0E, 0x0C, 0x33, 0x33, 0x6B, 0x1C, 0x1E, 0x18, 0x06, 0x18, 0x18, 0x63, 0x00, 0x00, 0x30, 0x3E,
	0x33, 0x3E, 0x33, 0x0F, 0x33, 0x6E, 0x0C, 0x30, 0x36, 0x0C, 0x7F, 0x33, 0x33, 0x66, 0x33, 0x6E,
	0x03, 0x0C, 0x33, 0x33, 0x6B, 0x36, 0x33, 0x19, 0x07, 0x00, 0x38, 0x00, 0x00, 0x0C, 0x00, 0x7F,
	0x30, 0x0C, 0x3B, 0x00, 0x06, 0x18, 0x3C, 0x0C, 0x00, 0x00, 0x00, 0x06, 0x6F, 0x0C, 0x06, 0x30,
	0x7F, 0x30, 0x33, 0x0C, 0x33, 0x30, 0x00, 0x0C, 0x06, 0x00, 0x18, 0x0C, 0x7B, 0x3F, 0x66, 0x03,
	0x66, 0x16, 0x16, 0x73, 0x33, 0x0C, 0x33, 0x36, 0x46, 0x6B, 0x73, 0x63, 0x06, 0x3B, 0x36, 0x38,
	0x0C, 0x33, 0x33, 0x7F, 0x1C, 0x0C, 0x4C, 0x06, 0x30, 0x18, 0x00, 0x00, 0x00, 0x3E, 0x66, 0x03,
	0x33, 0x3F, 0x06, 0x33, 0x66, 0x0C, 0x30, 0x1E, 0x0C, 0x7F, 0x33, 0x33, 0x66, 0x33, 0x66, 0x1E,
	0x0C, 0x33, 0x33, 0x7F, 0x1C, 0x33, 0x0C, 0x0C, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x36, 0x1F,
	0x66, 0x33, 0x00, 0x0C, 0x0C, 0x66, 0x0C, 0x0C, 0x00, 0x0C, 0x03, 0x67, 0x0C, 0x33, 0x33, 0x30,
	0x33, 0x33, 0x0C, 0x33, 0x18, 0x0C, 0x0C, 0x0C, 0x3F, 0x0C, 0x00, 0x03, 0x33, 0x66, 0x66, 0x36,
	0x46, 0x06, 0x66, 0x33, 0x0C, 0x33, 0x66, 0x66, 0x63, 0x63, 0x36, 0x06, 0x1E, 0x66, 0x33, 0x0C,
	0x33, 0x1E, 0x77, 0x36, 0x0C, 0x66, 0x06, 0x60, 0x18, 0x00, 0x00, 0x00, 0x33, 0x66, 0x33, 0x33,
	0x03, 0x06, 0x3E, 0x66, 0x0C, 0x33, 0x36, 0x0C, 0x6B, 0x33, 0x33, 0x3E, 0x3E, 0x06, 0x30, 0x2C,
	0x33, 0x1E, 0x7F, 0x36, 0x3E, 0x26, 0x0C, 0x18, 0x0C, 0x00, 0x00, 0x0C, 0x00, 0x36, 0x0C, 0x63,
	0x6E, 0x00, 0x18, 0x06, 0x00, 0x00, 0x0C, 0x00, 0x0C, 0x01, 0x3E, 0x3F, 0x3F, 0x1E, 0x78, 0x1E,
	0x1E, 0x0C, 0x1E, 0x0E, 0x0C, 0x06, 0x18, 0x00, 0x06, 0x0C, 0x1E, 0x33, 0x3F, 0x3C, 0x1F, 0x7F,
	0x0F, 0x7C, 0x33, 0x1E, 0x1E, 0x67, 0x7F, 0x63, 0x63, 0x1C, 0x0F, 0x38, 0x67, 0x1E, 0x1E, 0x3F,
	0x0C, 0x63, 0x63, 0x1E, 0x7F, 0x1E, 0x40, 0x1E, 0x00, 0x00, 0x00, 0x6E, 0x3B, 0x1E, 0x6E, 0x1E,
	0x0F, 0x30, 0x67, 0x1E, 0x33, 0x67, 0x1E, 0x63, 0x33, 0x1E, 0x06, 0x30, 0x0F, 0x1F, 0x18, 0x6E,
	0x0C, 0x36, 0x63, 0x30, 0x3F, 0x38, 0x18, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x1F, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00,
	};

Y ya estaría… ¿Cómo usaríamos esto en nuestra aplicación IoT? Pues básicamente incluiríamos la cabecera font_8x8.h y cuando necesitásemos localizar un carácter ASCII simplemente tendremos que restarle 32 al caracter, multiplicarlo por el número de caracteres de nuestra fuente (95) y ese sería el primer byte del caracter dentro del array. Como vamos a necesitar todas las líneas del caracter podemos hacer una función que las recupere de esta manera:

char getcharslicefrom8x8font(char c, int rowInChar)
{
    return font_8x8[(c - 32) + (rowInChar)*95];
}

Para escribir una cadena completa en una línea habría que localizar la posición inicial de la línea, recuperar todos los bytes de cada caracter que queramos escribir y mandarlos a esa línea de la pantalla (así explicado simplificadamente).

Tenéis todo el código de la conversión de bitmap a c en este repositorio: https://github.com/yoprogramo/font_to_c

Happy coding!

UPDATE: Ya se puede encontrar la fuente así creada en los repositorios de un par de proyectos muy interesantes: https://github.com/fhoedemakers/pico-infonesPlus/ y https://github.com/fhoedemakers/pico-smsplus