Los dos primeros artículos trataron el GD6 como producto y su diseño de hardware. Este entra en lo que más le importa a los desarrolladores: cómo usar el código.
El firmware y el sistema de configuración web del GD6 son de código abierto en GitHub (github.com/ManThink/gd6-open). Este artículo recorre la estructura del repositorio, la configuración del entorno de desarrollo, la compilación del firmware, las compilaciones multirregión, el flasheo, la personalización de la interfaz web, el uso de la API REST y el canal remoto de configuración MQTT.
Descripción general del repositorio
Información básica
| Elemento |
Detalles |
| Repositorio |
https://github.com/ManThink/gd6-open |
| Descripción |
Puerta de enlace LoRaWAN basada en ESP32 |
| Lenguaje principal |
C (framework ESP-IDF) |
| Sistema de compilación |
PlatformIO + ESP-IDF |
| Licencia |
Consulte el archivo LICENSE en el repositorio |
| Versión actual |
1.0.2 (consulte version.txt) |
| Versión inicial |
2026-07-23 |
Estructura de directorios
gd6-open/
├── src/ # Main application code
│ ├── main.c # Entry point, 6-phase boot
│ ├── ethernet_w5500.c # W5500 Ethernet driver
│ ├── cellular_4g.c # 4G PPP dial-up
│ ├── net_route_mgr.c # Triple-link failover manager
│ ├── wifi_ap.c # Wi-Fi AP + STA
│ ├── web_server.c # HTTP REST API + static files
│ ├── config_rpc.c # Config core (parse → validate → persist → apply)
│ ├── auth.c # Login password + Token auth
│ ├── mqtt_bridge.c # UDP↔MQTT bridge
│ ├── remote_config.c # Remote MQTT config channel
│ ├── ota_update.c # A/B dual-partition OTA
│ ├── sys_log.c # In-memory ring buffer log
│ ├── uplink_driver.c # pktfwd / station driver selection
│ ├── periph_power.c # Peripheral power management
│ ├── status_led.c # Status LED + watchdog
│ └── factory_reset.c # Factory reset
├── components/ # Third-party components
│ ├── packet_forwarder/ # Semtech lora_pkt_fwd
│ ├── sx1302_hal/ # SX1302 HAL (Semtech v2.1.0 port)
│ ├── basicstation/ # Semtech BasicStation 2.0.6
│ └── esp_modem/ # 4G PPP modem component
├── frontend/ # Web UI source (Vite build)
├── data/ # SPIFFS filesystem image
│ ├── web/ # Web UI static files (build output)
│ ├── global_conf.json # Active radio + gateway config
│ ├── regions/ # LoRaWAN regional config templates
│ ├── eth_config.json # Ethernet config
│ ├── wifi_ap.json # Wi-Fi AP/STA config
│ ├── cellular_config.json # 4G config
│ ├── mqtt_bridge_config.json # MQTT bridge config
│ └── remote_config.json # Remote config channel settings
├── scripts/ # Build scripts
│ ├── build_regions.py # Per-region batch build
│ └── embed_defaults_copy.py # Default config sync
├── tools/ # Flasher + crypto scripts
├── hardware/ # Hardware project files
├── partitions.csv # Dual-OTA partition table
├── platformio.ini # PlatformIO configuration
├── sdkconfig.defaults # SDK configuration
└── CMakeLists.txt # Top-level CMake
Directorios clave:
src/ contiene el código principal de la aplicación. Cada .c archivo corresponde a un módulo funcional. El código se compila con advertencias -Wall estrictas.
components/ aloja componentes de terceros. El HAL y el Packet Forwarder del SX1302 están portados del código fuente oficial de Semtech; el código de referencia original se conserva en .upstream/ para comparar diferencias antes de las modificaciones.
frontend/ contiene el código fuente Vue/Vite de la interfaz web. La salida de compilación va a data/web/.
data/ es la imagen del sistema de archivos SPIFFS — se flashea en la partición de almacenamiento de configuración de la puerta de enlace.
Relación con otros repositorios de ManThink
ManThink tiene otros repositorios de código abierto en GitHub:
| Repositorio |
Propósito |
| ThinkLink |
Plataforma IoT ThinkLink |
| TKL-EB-SDK |
SDK EdgeBus para el desarrollo de dispositivos de borde |
| ThinkLink-docker |
Despliegue Docker de la plataforma ThinkLink |
El repositorio GD6 es el proyecto de código abierto del hardware de la puerta de enlace. ThinkLink es el proyecto de código abierto de la plataforma. Se pueden usar juntos, pero GD6 también funciona de forma independiente con ChirpStack, TTN y otros servidores de red de terceros.
Configuración del entorno de desarrollo
Requisitos previos
La pila de compilación del GD6 es PlatformIO + ESP-IDF. No es necesario instalar ESP-IDF por separado — PlatformIO gestiona la versión del framework automáticamente.
Necesita:
- PlatformIO IDE: extensión de VS Code o la CLI de PlatformIO Core. Consulte platformio.org/install.
- Python 3.x: requerido por PlatformIO.
- Node.js 18+: requerido si desea modificar el frontend web (
frontend/).
- Controlador USB: controlador serie USB del ESP32-S3 (normalmente CP2102 o USB CDC nativo).
Bloqueo de versión de PlatformIO
platformio.ini bloquea explícitamente la versión de la plataforma:
platform = espressif32 @ 6.13.0
Esta versión corresponde a ESP-IDF 5.5.3. Los comentarios advierten específicamente: no use espressif32sin más, ya que flotará a la última versión (7.0.x / IDF 6.0). IDF 6.0 eliminó el componente mqtt integrado, lo que provocará fallos de compilación.
Este es el primer escollo: si actualiza PlatformIO y la compilación falla, compruebe si la versión de espressif32 ha cambiado.
Clonar el código
git clone https://github.com/ManThink/gd6-open.git
cd gd6-open
Verificar el entorno
pio run # Build firmware
Si la compilación se realiza correctamente y genera firmware.bin, su entorno está listo. La primera compilación descarga el framework ESP-IDF y las dependencias, por lo que tarda algún tiempo.
Lleva esta guía a tu proyecto
ManThink ofrece productos IoT para conectar dispositivos, desplegar redes y trabajar con datos. Consulta estos recursos de producto junto con las recomendaciones técnicas del artículo:
GD61 Gateway LoRaWAN para interiores · ThinkLink
Conoce los productos de ManThink y consulta tu proyecto →
Compilación del firmware
Compilación básica
pio run # Build firmware
pio run --target upload # Flash firmware (via USB)
pio run --target uploadfs # Flash SPIFFS image
pio device monitor # Serial monitor (115200 baud)
pio run --target menuconfig # SX1302 pin / SPI configuration
pio run --target clean # Clean build/
La velocidad en baudios del puerto serie está fijada a 115200. Los platformio.ini comentarios explican: la consola de ESP-IDF usa 115200 por defecto; sin esta configuración, pio run -t monitor usaría 9600 y mostraría una salida ilegible.
Compilaciones multirregión
El código de la aplicación GD6 es independiente de la frecuencia — las diferencias regionales están enteramente en global_conf.json. Las plantillas regionales están en data/regions/ (AS923 / EU868 / US915 / AU915 / CN470 / KR920 / IN865).
El script scripts/build_regions.py proporciona compilaciones con un solo comando:
# List available regions and center frequencies
python scripts/build_regions.py --list
# Set a region as development default (overwrites data/global_conf.json)
python scripts/build_regions.py --activate EU868
pio run && pio run -t uploadfs
# Build firmware + SPIFFS for all regions (output in dist/<REGION>/)
python scripts/build_regions.py
# Build specific regions only
python scripts/build_regions.py EU868 US915 AS923
# Rebuild SPIFFS only (faster when only config changed)
python scripts/build_regions.py --fs-only AS923
Nomenclatura de artefactos de compilación (versión de version.txt):
| Archivo |
Descripción |
Desplazamiento de flasheo |
GD6-<ver>-<REGION>-full.bin |
Imagen Flash completa de 8 MiB |
0x0 |
GD6-<ver>-<REGION>-firmware.bin |
Firmware de la aplicación |
0x20000 |
GD6-<ver>-<REGION>-spiffs.bin |
Imagen SPIFFS |
0x5A0000 |
GD6-<ver>-<REGION>-global_conf.json |
Copia de configuración usada en tiempo de compilación |
— |
build_info.json |
Metadatos de compilación y tabla de desplazamientos |
— |
Nota: el hardware del front-end de RF debe coincidir con la banda objetivo. Los módulos 868 / 915 / 470 no son intercambiables — esto es una restricción física, no una limitación de software (explicado en detalle en el Artículo 2).
Flasheo del firmware
Mediante puerto serie USB
Para el flasheo inicial o la depuración de desarrollo:
# Regular flash (firmware + SPIFFS)
pio run --target upload
pio run --target uploadfs
# Erase entire flash and write full image (initial flash or recovery)
python -m esptool --chip esp32s3 erase_flash
python -m esptool --chip esp32s3 write_flash 0x0 GD6-1.0.2-EU868-full.bin
El borrado completo sobrescribe todas las particiones y los datos del dispositivo (incluido el EUI de la puerta de enlace). Haga una copia de seguridad de cualquier dato que necesite conservar primero.
Mediante OTA de la consola web
Para actualizaciones en línea de puertas de enlace desplegadas:
- Abra la consola web → página "Firmware Upgrade"
- Verifique la versión actual y la partición activa
- Seleccione un
.bin firmware que coincida con el modelo de la puerta de enlace, la versión de hardware y la región objetivo
- Espere a que finalicen la carga, la verificación y la escritura
- Siga la indicación para reiniciar; inicie sesión de nuevo y verifique la versión
La OTA utiliza el mecanismo de doble partición A/B: el nuevo firmware se escribe en la partición inactiva, se verifica y luego se cambia la partición de arranque. No actualice la página, desconecte la red ni apague durante la actualización.
Mediante el flasheador del navegador
El directorio tools/flasher/ del repositorio contiene un flasheador basado en el navegador (construido sobre la API ESP Web Serial) que permite flashear firmware directamente por USB en Chrome/Edge. Esta herramienta también integra el control de alimentación del watchdog externo ESP32-S3 (TPL5010), además de protección de aborto y reinicio por excepción.
No confunda los tipos de firmware
El manual advierte explícitamente: no cargue una imagen completa de fabricación Flash (-full.bin) como firmware OTA web. La OTA web solo acepta firmware de aplicación (-firmware.bin), porque la OTA escribe en la partición inactiva, no en toda la Flash.
Tabla de particiones y diseño de la Flash
GD6 utiliza una tabla de particiones personalizada (partitions.csv). La Flash de 8 MB se divide de la siguiente manera:
| Partición |
Tipo |
Desplazamiento |
Tamaño |
Descripción |
| nvs |
data/nvs |
0x9000 |
24 KB |
Almacenamiento no volátil (contraseñas, etc.) |
| otadata |
data/ota |
0xF000 |
8 KB |
Registro de ranura de arranque OTA |
| phy_init |
data/phy |
0x11000 |
4 KB |
Inicialización de la PHY de RF |
| ota_0 |
app |
0x20000 |
2.75 MB |
Ranura de firmware A |
| ota_1 |
app |
0x2E0000 |
2.75 MB |
Ranura de firmware B |
| storage |
data/spiffs |
0x5A0000 |
2.375 MB |
Interfaz web + archivos de configuración |
La tabla ocupa exactamente 8 MB. Las dos particiones OTA son de 2.75 MB cada una; la partición SPIFFS es de 2.375 MB. Si el firmware supera 2.75 MB, es necesario ajustar la tabla de particiones.
Personalización del sistema de configuración web
Estructura del código fuente del frontend
El código fuente de la interfaz web está en frontend/, construido con Vite:
cd frontend && npm install # First-time dependency install
cd frontend && npm run build # Build to data/web/
pio run --target uploadfs # Flash to SPIFFS
cd frontend && npm run dev # Local dev server
La salida de compilación va a data/web/ (index.html, app.js, style.css), y luego se flashea como parte de la imagen SPIFFS.
No edite data/web/ directamente
El README advierte explícitamente: no modificar data/web/* directamente — son artefactos de compilación. El enfoque correcto es modificar el código fuente en frontend/, luego ejecutar npm run build.
Si necesita cambiar el texto, los estilos o el diseño de la interfaz web:
- Modifique el código fuente en
frontend/
npm run build para generar nuevos data/web/ archivos
pio run --target uploadfs para flashear en la puerta de enlace
Depuración de desarrollo local
npm run dev inicia un servidor de desarrollo local de Vite. /api/* las solicitudes se redirigen a la puerta de enlace de destino mediante el proxy de Vite. Esto le permite modificar el código del frontend y previsualizar los cambios en tiempo real sin flashear.
Protección del directorio web/
Sobrescribir web/ archivos en línea puede dejar la consola inaccesible, y la restauración de fábrica no restaura los archivos web dañados. Si necesita actualizar la interfaz web en línea, asegúrese de tener acceso por puerto serie como método de recuperación alternativo.
Uso de la API REST
Autenticación
La API REST de GD6 utiliza autenticación por Token:
- Llame a
POST /api/login con la contraseña para obtener un Token
- Incluya el Token en el encabezado para las solicitudes posteriores
- El Token se comparte con la contraseña de inicio de sesión web — cambiar la contraseña invalida los tokens antiguos
Contraseña de inicio de sesión predeterminada: wifi!0804 (cámbiela inmediatamente después del primer inicio de sesión).
Lista de endpoints de la API
| Método |
Ruta |
Descripción |
| GET |
/api/status |
Estado de sistema/red/LoRa/SPIFFS |
| POST |
/api/login · /api/logout |
Inicio / cierre de sesión |
| PUT |
/api/password |
Cambiar contraseña |
| GET |
/api/auth_state |
Consulta del estado de autenticación |
| GET/PUT |
/api/ethernet |
Configuración de Ethernet |
| POST |
/api/ethernet/power |
Control de alimentación de Ethernet |
| GET/PUT |
/api/wifi |
Configuración de Wi-Fi |
| GET |
/api/wifi/scan |
Escaneo de Wi-Fi |
| GET/PUT |
/api/lora |
Configuración de la puerta de enlace LoRa |
| GET/PUT |
/api/cellular |
Configuración de 4G |
| GET |
/api/cellular/status |
Estado en tiempo real de 4G |
| GET/PUT |
/api/mqtt_bridge |
Configuración del puente MQTT |
| GET |
/api/mqtt_bridge/status |
Estado del puente MQTT |
| POST/DELETE |
/api/mqtt_bridge/cert |
Certificados del puente MQTT |
| GET/PUT |
/api/remote_config |
Configuración del canal de configuración remota |
| GET |
/api/remote_config/status |
Estado de la configuración remota |
| POST/DELETE |
/api/remote_config/cert |
Certificados de configuración remota |
| GET |
/api/station |
Estado de BasicStation |
| GET |
/api/logs |
Registros (incremental) |
| DELETE |
/api/logs |
Borrar registros |
| GET |
/api/logs/download |
Descargar registros |
| GET |
/api/fs |
Listado de archivos |
| POST |
/api/fs/upload |
Subir archivo |
| GET |
/api/firmware |
Información del firmware |
| POST |
/api/firmware/upload |
Carga OTA |
| POST |
/api/restart |
Reiniciar el servicio LoRa o el sistema |
Ejemplos de uso
Obtener el estado del sistema:
# Login to get Token
TOKEN=$(curl -s -X POST http://192.168.4.1/api/login \
-H "Content-Type: application/json" \
-d '{"password":"wifi!0804"}' | jq -r '.token')
# Query system status
curl -s http://192.168.4.1/api/status \
-H "Authorization: Bearer $TOKEN" | jq
Configurar Ethernet con IP estática:
curl -s -X PUT http://192.168.4.1/api/ethernet \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mode": "static",
"ip": "192.168.1.200",
"netmask": "255.255.255.0",
"gateway": "192.168.1.1",
"dns1": "8.8.8.8"
}'
Script de gestión por lotes para múltiples gateways:
#!/bin/bash
# Batch query gateway status
GATEWAYS=("192.168.1.101" "192.168.1.102" "192.168.1.103")
PASSWORD="your_password"
for GW in "${GATEWAYS[@]}"; do
TOKEN=$(curl -s -X POST "http://$GW/api/login" \
-H "Content-Type: application/json" \
-d "{\"password\":\"$PASSWORD\"}" | jq -r '.token')
echo "=== $GW ==="
curl -s "http://$GW/api/status" \
-H "Authorization: Bearer $TOKEN" | jq '{firmware: .firmware_ver, uptime: .uptime, lora: .lora_status}'
done
Canal remoto de configuración MQTT
Diferencia con MQTT Bridge
El GD6 tiene dos conexiones MQTT independientes — no las confunda:
|
MQTT Bridge |
Canal remoto de configuración MQTT |
| Propósito |
Reenvío de datos LoRaWAN |
Lectura/escritura remota de la configuración del dispositivo |
| Archivo de configuración |
mqtt_bridge_config.json |
remote_config.json |
| Módulo de código |
src/mqtt_bridge.c |
src/remote_config.c |
| Conexión |
Conexión TLS independiente |
Conexión TLS independiente |
| Autenticación |
Credenciales del broker |
Contraseña de inicio de sesión del dispositivo (autenticación RPC) |
Las dos conexiones están totalmente aisladas — no mezcle Topics ni credenciales.
Restricción de seguridad
El canal remoto tiene un diseño de seguridad crítico: el canal remoto no puede modificar su propia configuración de Broker.
Esto evita que una configuración remota errónea provoque la desconexión del dispositivo. Si el canal remoto pudiera modificar su propia dirección de Broker o sus credenciales, una sola configuración errónea podría cortar de forma permanente la capacidad de gestión remota del dispositivo.
Configuración
Configure mediante la consola web → página "Remote Config":
- Introduzca el URI del Broker, el Client ID y el usuario/contraseña
- Configure el Topic y el QoS
- Cargue los certificados TLS (si es necesario)
- Supervise el estado de la conexión
Una vez configurado, puede leer y escribir remotamente los elementos de configuración permitidos del dispositivo mediante mensajes MQTT y activar reinicios.
Archivos de configuración SPIFFS
Descripción general de los archivos de configuración
| Archivo |
Descripción |
/spiffs/global_conf.json |
Parámetros de radio SX1302 + configuración completa del gateway (incluye la selección de uplink_driver selección) |
/spiffs/eth_config.json |
Ethernet DHCP / IP estática |
/spiffs/wifi_ap.json |
Configuración Wi-Fi AP/STA |
/spiffs/cellular_config.json |
4G APN / autenticación / prioridad |
/spiffs/mqtt_bridge_config.json |
Broker / cuenta / TLS / topic del puente MQTT |
/spiffs/remote_config.json |
Broker / cuenta / TLS / topic del canal de configuración remota |
Estos archivos JSON también sirven como objetivos de restablecimiento de fábrica — están embebidos en el firmware. El restablecimiento de fábrica extrae los valores predeterminados del firmware y sobrescribe las copias en SPIFFS.
Flujo de aplicación de la configuración
Los cambios en el JSON de configuración requieren un reinicio del gateway para surtir efecto. Una vez que SPIFFS escribe en Flash, los datos son persistentes y no dependen de la RAM — sobreviven a un corte de energía, pero requieren un reinicio para cargar la nueva configuración.
Gestión de archivos mediante la consola web
La página "File System" de la consola web ofrece gestión directa de los archivos /spiffs y admite la visualización y edición del JSON de configuración. Esto abre una vía para la personalización profunda — puede modificar campos de configuración directamente, más allá de las opciones expuestas en la interfaz web.
Driver de enlace ascendente: Packet Forwarder vs Basic Station
El campo uplink_driver en global_conf.json selecciona qué driver ocupa el SX1302. Ambos son mutuamente excluyentes:
| Driver |
Valor |
Descripción |
| Packet Forwarder |
pktfwd |
Packet forwarder UDP de Semtech (predeterminado) |
| Basic Station |
station |
Semtech BasicStation 2.0.6, wss/TLS hacia LNS |
Cambiar de driver requiere reiniciar el gateway. En modo Basic Station, el plan de canales lo aprovisiona el LNS, y las credenciales/configuración se almacenan en archivos SPIFFS separados (tc.* / cups.*).
Proceso de arranque
Comprender el proceso de arranque ayuda a la resolución de problemas. app_main() (src/main.c) arranca en 6 fases:
| Fase |
Acción |
| Fase 0 |
Inicialización de NVS + bucle de eventos (LED de estado y botón de restablecimiento de fábrica listos desde el inicio) |
| Fase 1 |
Montaje de SPIFFS — un fallo de montaje deshabilita el servidor web y el packet forwarder |
| Fase 2 |
Inicio del Soft-AP Wi-Fi (asíncrono, no bloqueante) |
| Fase 3 |
El gestor de rutas inicia W5500 Ethernet → 4G PPP, con conmutación por error primario/respaldo |
| Fase 4 |
Inicio de HTTPD (API REST + archivos estáticos) + canal remoto de configuración MQTT |
| Fase 5 |
Autodiagnóstico SX1302 → inicio del packet forwarder o BasicStation según uplink_driver |
Principio de diseño: las fases posteriores toleran fallos no fatales en las fases anteriores, lo que garantiza que el dispositivo siempre pueda entrar en modo de recuperación mediante el AP Wi-Fi. Incluso si falla el montaje de SPIFFS o el autodiagnóstico SX1302, la consola web sigue siendo accesible.
Ejemplos de desarrollo secundario
Ejemplo 1: Configuración de canales personalizada
Si necesita una configuración de canales no estándar (por ejemplo, frecuencias centrales personalizadas), modifique data/global_conf.json directamente:
python scripts/build_regions.py --activate EU868
# Then edit radio_0 / radio_1 center frequencies in data/global_conf.json
pio run --target uploadfs
Ejemplo 2: Integración con un sistema de operaciones personalizado
Utilice la API REST para integrar las pasarelas GD6 en su propio sistema de operaciones:
- Consulte
/api/status periódicamente para recopilar el estado de ejecución de la pasarela
- Llame a
/api/logs para obtener los registros
- Utilice
/api/remote_config para enviar cambios de configuración por lotes
- Utilice
/api/restart para reinicios remotos
Ejemplo 3: Interfaz web personalizada
Modifique el código fuente de Vue en frontend/ para añadir páginas personalizadas o modificar las interfaces existentes, y luego npm run build + pio run --target uploadfs.
Problemas comunes
P: ¿La compilación falla con el componente mqtt no encontrado?
R: Compruebe que platform = espressif32 @ 6.13.0 en platformio.ini no haya cambiado. IDF 6.0 eliminó el componente mqtt integrado; debe fijar la versión a 6.13.0 (IDF 5.5.3).
P: ¿Salida serie ilegible tras el flasheo?
R: Confirme que la velocidad en baudios es 115200. platformio.ini establece monitor_speed = 115200.
P: ¿La carga del firmware OTA web no arranca?
R: Compruebe si cargó el firmware de la aplicación (-firmware.bin) o la imagen completa (-full.bin). La OTA web solo acepta firmware de aplicación. Si se cargó una imagen completa, espere el reinicio automático. Si sigue sin ser accesible, utilice el AP Wi-Fi o la recuperación serie controlada; no realice ciclos de encendido repetidos.
P: ¿La consola web no se abre tras el restablecimiento de fábrica?
R: El restablecimiento de fábrica no restaura el directorio web/ . Si los archivos web/ se sobrescribieron y dañaron previamente, vuelva a flashear la imagen SPIFFS por serie (pio run --target uploadfs).
P: ¿Se modificó data/ la configuración pero los cambios no surten efecto?
R: Tras modificar archivos en data/, debe ejecutar pio run --target uploadfs para flashear a SPIFFS. Tras modificar archivos SPIFFS en línea, es necesario reiniciar la pasarela para que los cambios surtan efecto.
Resumen
El repositorio de código abierto GD6 no es un shell: contiene el código fuente completo del firmware, el código fuente de la interfaz web, la cadena de herramientas de compilación y los archivos del proyecto de hardware. Desde la clonación del código hasta la compilación de un firmware utilizable, todo el proceso puede completarse en menos de 30 minutos.
Puntos clave:
- Versión de PlatformIO fijada a 6.13.0 — no actualice a la última versión
- Utilice
build_regions.py para compilaciones multirregión — no edite manualmente los campos de región global_conf.json de
- Edite
frontend/, no data/web/ — estos últimos son artefactos de compilación
- La API REST y el canal MQTT remoto proporcionan capacidades completas de gestión programática
- Las dos conexiones MQTT están totalmente aisladas — el reenvío de datos y la configuración remota no pueden mezclarse
El próximo artículo aborda el despliegue: desde el desembalaje hasta la conexión con ThinkLink / ChirpStack / TTN, recorriendo todo el proceso de despliegue paso a paso.
Enlaces relacionados
ManThink — Haciendo el desarrollo de IoT más eficiente.