Skip to content

jrgdiaz/Weather-Micropython-HackConRD2025

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

title date author
Programación de un mapa meteorológico LED con MicroPython en un ESP32C3
Feb 28, 2025
Jorge Diaz

Si estas en HackConRD 2025, y te gusta este proyecto (o el CTF) considera dejarme una ⭐

Sí quieres revisitar el badge pasado:

considera dejar una ⭐ ahi también.

Haz creado algo tú con el badge de HackConRD2025 y deseas compartirlo? Tenemos un lugar para esas ideas:

You can find an English translation for this tutorial in the following link

Leave a ⭐ if you like the project

Programación de un mapa meteorológico LED con MicroPython en un ESP32C3

Resumen

En esta publicación, demostraremos cómo programar un mapa del clima con LED utilizando un microcontrolador ESP32C3 con MicroPython y datos de la API de OpenWeatherMap.

image

Foto de FotosMalasPeroMias

Servidor de CTF IoT de HackConRD 2025 Badge

Comenzando con el desarrollo de IoT y MicroPython

En este proyecto, mostraremos información meteorológica en el mapa de la RD🇩🇴. Además, este proyecto servirá como el badge para HackConRD 2025, donde organizaremos un CTF enfocado en la ingeniería inversa de este dispositivo IoT temático.

Hardware y Software

El ESP32C3 es un microcontrolador potente y rentable con Wi-Fi y Bluetooth integrados, lo que lo convierte en una excelente opción para proyectos de IoT. Una aplicación clave del IoT es el monitoreo del clima en tiempo real, donde los microcontroladores recopilan datos ambientales de sensores o, en nuestro caso, de fuentes de Internet.



Placa ESP32C3

Para construir este proyecto, necesitarás:

  • ✅ Un microcontrolador ESP32C3
  • Una conexión Wi-Fi
  • ✅ Firmware de MicroPython instalado en el ESP32C3
  • Una clave API de OpenWeatherMap (Te explico como conseguir una mas abajo)

Sin más demora, comencemos a implementar el proyecto.

Configuración del entorno de desarrollo

Primero, instala rshell puedes conocer más sobre el proyecto aquí: https://pypi.org/project/rshell/

python3 -m pip install rshell

Conecta tu badge con un cable USB-C e inicia una conexión serial:

⚠️ El puerto serial puede aparecer con diferentes nombres dependiendo del sistema operativo en Windows, es COM4 por ejemplo. ⚠️
⚠️ 🔊 Si identificas una manera de borrar la FLASH. Pués, te recomiendo que no lo hagas! 🔊⚠️

/Library/Frameworks/Python.framework/Versions/3.11/bin/rshell -p <tu-puerto-serial> -b 115200

Escribe el código que se ejecutará en el chip del microcontrolador

En tu editor de código favorito, comienza importando las librerias necesarias para ejecutar el proyecto:

import machine, neopixel
from machine import Pin
import asyncio
import struct
import time
import network
import urequests
from micropython import const

Luego escribe una función que se conecte a un punto de acceso Wi-Fi:

def connect_wifi(ssid, password):
    connected = False
    try:
        wlan = network.WLAN(network.STA_IF)
    except Exception as e:
        print(e)
        return connected     
    wlan.active(True)
    wlan.config(txpower=8) #needed due to a hardware bug see: https://roryhay.es/blog/esp32-c3-super-mini-flaw
    try:
        wlan.connect(ssid, password)
    except Exception as e:
        if 'Wifi Internal Error' == str(e):
            print(e)
            pass
    while not wlan.isconnected():
        time.sleep(3) 
    print("Connected to Wi-Fi:", wlan.ifconfig())
    connected = True
    return connected

La parte principal del código es traducir los datos meteorológicos al color del LED en la placa, aquí está el código para eso:

def fetch_weather_for_city(city, api_key):
    BASE_URL = "http://api.openweathermap.org/data/2.5/weather"
    url = f"{BASE_URL}?q={city}&appid={api_key}&units=metric"
    try:
        response = urequests.get(url)
        if response.status_code in (200, 202):
            data = response.json()
            response.close()
            return data
        else:
            print(f"Error fetching weather for {city}: {response.status_code}")
            response.close()
    except Exception as e:
        print("Weather fetch error:", e)
    return None

def parse_city_weather(data):
    if not data:
        return (20, 0)  # Default values
    temp = data["main"]["temp"]
    rain_chance = min(int(data.get("rain", {}).get("1h", 0) * 20), 100)
    return (temp, rain_chance)

def weather_to_color(temperature, precipitation_chance):
    BRIGHTNESS = 0.05
    precipitation_chance = min(precipitation_chance, 100)
    
    if precipitation_chance > 50:
        return (0, 0, int((precipitation_chance / 100) * 255 * BRIGHTNESS))
    elif temperature > 30:
        return (int(min((temperature / 50) * 255, 255) * BRIGHTNESS), 0, 0)
    elif 25 <= temperature <= 30 and precipitation_chance <= 20:
        return (int(255 * BRIGHTNESS), int(255 * BRIGHTNESS), 0)
    else:
        return (0, int(max((1 - (temperature / 30)) * 255, 0) * BRIGHTNESS), 0)    

async def update_weather(np, led_map, api_key):
    while True:
        weather_data = {}
        if connect_wifi("wifi (name) ssid", "password"):
            for province, led_index in led_map.items():
                weather_data = fetch_weather_for_city(urlencode(province), api_key)
                temp, rain = parse_city_weather(weather_data)
                print("[+] Province: "+str(province)+" Temperature: "+str(temp)+" Rain Chance: "+str(rain)+" %")
                np[led_index - 1] = weather_to_color(temp, rain)
                np.write()
            print("Weather updated!")
        else:
            print("Skipping update due to Wi-Fi failure")
        await asyncio.sleep(7200)  # Update every 2 hours

#handle province's special characters        
def urlencode(string):
    return string.replace(" ", "%20").replace("á", "%C3%A1").replace("é", "%C3%A9").replace("í", "%C3%AD").replace("ó", "%C3%B3").replace("ú", "%C3%BA").replace("ñ", "%C3%B1").replace(",", "%2C")

Y finalmente, nuestra función principal de entrada 'main.py'

async def main():
    NUM_LEDS = 31
    LEDS_PIN = 2
    np = neopixel.NeoPixel(Pin(LEDS_PIN), NUM_LEDS)
    PROVINCE_TO_LED_MAP = {  "Santo Domingo,do": 1,
    "San Cristobal,do": 2,
    "Bani,do": 4,
    "Azua,do": 5,
    "San Jose de Ocoa,do": 3,
    "Barahona,do": 6,
    "Pedernales,do": 7,
    "Jimani": 8,
    "Neiba": 9,
    "San Juan de la Maguana,do": 10,
    "Comendador,do": 11,
    "Villa Vasquez,do": 12,
    "Dajabon,do": 13,
    "San Fernando de Monte Cristi,do": 14,
    "Mao,do": 15,
    "Puerto Plata,do": 16,
    "Santiago de los Caballeros": 17,
    "Moca,do": 18,
    "Salcedo,do": 19,
    "Nagua,do": 24,
    "Samana,do": 25,
    "Monte Plata,do": 26,
    "Hato Mayor,do": 27,
    "El Seibo,do": 29,
    "La Romana,do": 30,
    "La Altagracia,do": 31,
    "San Pedro de Macoris": 28,
    "La Vega,do": 20,
    "Bonao,do": 21,
    "Cotui,do": 22,
    "San Francisco de Macoris": 23}
    API_KEY = "openweathermap-api-key"
    await update_weather(np, PROVINCE_TO_LED_MAP, API_KEY)

asyncio.run(main())

Pasos para obtener un API key de OpenWeatherMap API

  • Crear una cuenta en https://home.openweathermap.org/users/sign_up llenando el siguiente formulario:

    Screenshot 2025-03-10 at 1 05 38 PM

  • OpenWeatherMap nos enviará un correo para confirmar la cuenta nueva, procedemos con esto e iniciamos sesión con la cuenta creada.

  • Generar un API key nuevo

Screenshot 2025-03-10 at 1 14 21 PM

Screenshot 2025-03-10 at 1 19 46 PM

Sustituir el key y las credenciales de Wi-Fi en el código. Y con eso, ya estamos listos para proceder a cargar el código al board. Lo explicamos a continuación.

Cargar código al ESP32C3

Primero nos conectamos al board:

/Library/Frameworks/Python.framework/Versions/3.11/bin/rshell -p <tu-puerto-serial> -b 115200

Para cargar y correr código en MicroPython utilizamos dos scripts boot.py y main.py

⚠️ No debes tocar el boot.py ⚠️

boot.py se ejecuta una vez MicroPython ha cargado en el board, ESTE NO DEBE SER modificado.

✅ Modificamos en main.py ✅

Cargamos nuestro código como main.py copiandolo al board.

Este correrá cada vez que energicemos el board:

cp main.py /pyboard/main.py

Finalmente, corremos nuestro código:

Ingresa al interprete REPL y corre el main.py:

repl
import main

Visualizando datos meteorológicos

Para representar las condiciones meteorológicas, estamos utilizando una matriz de 31 LED aRGB, con cada LED correspondiente a una de las 31 provincias. Los LED cambiarán de color según las condiciones meteorológicas:



Diseño de PCB por Emeraldo Ramos

  • Rojo: Temperaturas altas
  • Amarillo: Buen tiempo
  • Verde: Condiciones frescas pero secas
  • Azul: Lluvia intensa

Esto proporciona una manera intuitiva de visualizar rápidamente los patrones climáticos en todo el país.

Releases

No releases published

Packages

No packages published

Languages