Controlá Home Assistant sin internet con Meshtastic: SenseCAP T1000-E y P1 Pro

Si se corta internet, controlar nuestra casa a distancia parece completamente imposible. Pero, ¿y si armamos un plan B con Meshtastic para seguir hablando con Home Assistant?
Para este proyecto usé dos nodos totalmente distintos pero que se complementan muy bien: el SenseCAP Solar Node P1-Pro, que queda fijo en casa, y el SenseCAP T1000-E, una tarjeta portátil que puedo llevar encima.
La idea es simple: poder consultar el estado de casa, controlar algunos dispositivos y recibir avisos importantes incluso cuando no hay internet ni señal celular.
Primero que nada, gracias a la gente de OpenELAB por enviarme los nodos para hacer este proyecto. Es una tienda donde pueden encontrar estos equipos y un montón de productos de electrónica, microcontroladores y dispositivos para proyectos de casa inteligente.
Meshtastic explicado simple
Meshtastic es un sistema de comunicación basado en LoRa que no necesita internet ni señal celular. Los nodos se comunican entre sí y forman una red tipo malla: si dos equipos no llegan de manera directa, otros nodos pueden ayudar a repetir el mensaje hasta destino.
No sirve solamente para mandar texto. También permite compartir la ubicación del nodo y, gracias a la integración con Home Assistant, usar esos mensajes para disparar acciones o recibir información de nuestra casa.
Ya había probado Meshtastic en el Cosquín Rock, donde lo usé para comunicarme en un lugar con miles de personas y señal celular prácticamente inexistente. Esta vez quería ir un poco más lejos: conectarlo con mi casa.
Seeed Studio SenseCAP T1000-E

| Especificación | SenseCAP T1000-E |
|---|---|
| Procesador | Nordic nRF52840 |
| LoRa | Semtech LR1110 |
| Posicionamiento | GPS integrado |
| Batería | 700 mAh |
| Carga | Conector magnético USB |
| Protección | IP65 |
| Dimensiones | 85 × 55 × 6,5 mm |
| Peso | 32 g |
| Sensores | Luz, temperatura, 3 axis |
Lo que más me sorprendió es el tamaño. Comparado con el Heltec T114 que usé en el Cosquín, y más todavía si contamos la batería que le había agregado, esto se puede llevar a cualquier lado sin llamar la atención.
Seeed Studio SenseCAP Solar Node P1-Pro

| Especificación | SenseCAP Solar Node P1-Pro |
|---|---|
| Panel solar | 5 W |
| Placa principal | XIAO nRF52840 Plus |
| LoRa | Wio-SX1262 |
| Posicionamiento | XIAO L76K GPS |
| Baterías | 4 × 18650 de 3350 mAh |
| Autonomía sin sol | Más de dos semanas según Seeed Studio |
La idea del proyecto
La arquitectura quedó de esta manera:
[T1000-E portátil] ←→ [Red Meshtastic] ←→ [P1-Pro en casa] ←→ [Home Assistant]
- El P1-Pro queda fijo en la terraza, alimentado por el panel solar y conectado a Home Assistant.
- El T1000-E viaja conmigo y me permite enviar comandos o recibir avisos.
- Home Assistant sigue controlando localmente luces, enchufes, sensores, aires acondicionados y el resto de la casa.
Esto permite consultar estados, controlar dispositivos o recibir una alerta por movimiento o corte de internet sin depender de servicios en la nube.
Configuración inicial de los nodos
¡IMPORTANTE!
Si un nodo usa una antena externa, conectala antes de encenderlo. Transmitir sin la carga correcta puede hacer que la etapa de radiofrecuencia trabaje mal, calentarse y terminar dañado.
Primero configuré la región de radio correspondiente. En Argentina se usa la banda de 915–928 MHz y, para esta instalación, utilicé la opción de región compatible indicada para el equipo. Siempre revisen la regulación local antes de transmitir.
Después conecté el P1-Pro al WiFi, comprobé que pudiera accederse desde la interfaz web y realicé la configuración inicial del T1000-E desde el teléfono.
Una vez que ambos nodos estaban usando la misma configuración de Meshtastic, hice una prueba de mensajes entre ellos antes de sumar Home Assistant. Conviene separar los problemas: primero verificamos que la radio funcione y después agregamos el resto de las piezas.
Cómo integrar Meshtastic con Home Assistant
Como la conexión local del nodo con Home Assistant se realiza por Bluetooth, necesitamos un Bluetooth Proxy cerca del P1-Pro. En criollo, un intermediario que recibe Bluetooth y lo lleva a Home Assistant a través del WiFi.
Para eso se puede usar cualquier ESP32 compatible con ESPHome. En mi caso reutilicé el ESP32 del lavadero porque el nodo solar quedó instalado justo encima y ya tenía una conexión estable. Si no viste el video del lavadero, lo tenes acá.
[P1-Pro Solar Node] ←→ [Proxy Bluetooth] ←→ [Home Assistant]
Proxy Bluetooth con ESPHome
En este apartado les dejo el código parcial que se necesita para agregar la funcionalida a nuestro dispositivo con un ESP32 y con ESPHome instalado o directamente el código completo para un dispositivo nuevo.
ESPHome - BLE Tracker y Proxy Bluetooh - YAML
esp32_ble_tracker:
scan_parameters:
active: true
interval: 320ms
window: 300ms
bluetooth_proxy:
active: true
connection_slots: 3
ESPHome - Código completo - YAML
esphome:
name: bt-proxy
friendly_name: bt-proxy
esp32:
board: esp32-c6-devkitc-1
framework:
type: esp-idf
# Enable logging
logger:
# Enable Home Assistant API
api:
encryption:
key: "<api_key_encryption>"
ota:
- platform: esphome
password: "<ota_pwd>"
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
# Enable fallback hotspot (captive portal) in case wifi connection fails
ap:
ssid: "Bt-Proxy Fallback Hotspot"
password: "<fallback_hotspot_pwd>"
captive_portal:
esp32_ble_tracker:
scan_parameters:
active: true
interval: 320ms
window: 300ms
bluetooth_proxy:
active: true
connection_slots: 3
Meshtastic Custom Integration (HACS)
Necesitamos instalar, desde HACS, la integración custom de Meshtastic.
Cuando reiniciamos Home Assistant, nos va a aparecer el dispositvDurante la configuración activé el Web Client y el TCP Proxy, y limité la creación de destinos de notificación de mensajes directos a los nodos que realmente iba a utilizar.
Con eso Home Assistant comenzó a recibir el evento meshtastic_api_text_message y apareció la acción meshtastic.send_text, que son las dos piezas que usan las automatizaciones.
Automatizaciones en Home Assistant
ANTES DE COPIAR LAS AUTOMATIZACIONES
Los ejemplos siguientes están generalizados para no publicar los identificadores de mi instalación. Antes de usarlos, hay que reemplazar:
ID_NODO_SOLARpor el identificador del nodo conectado a Home Assistant.ID_NODO_TRACKERpor el identificador del T1000-E que enviará y recibirá mensajes.binary_sensor.internetpor el sensor que represente el estado de nuestra conexión.También es importante que las luces tengan nombres claros y estén asignadas a las áreas correctas de Home Assistant. La automatización de control usa esos nombres y áreas para saber qué dispositivo tiene que manejar.
Notificación de cambio de estado de Internet
Para saber si se cortó internet uso un sensor de tipo ping contra una IP pública. Lo agregan desde Settings → Devices and Services → Add Integration

Si el estado permanece caído durante un minuto, Home Assistant manda un aviso por Meshtastic. Cuando la conexión vuelve y se mantiene estable otro minuto, manda el mensaje de recuperación.
La espera evita que un microcorte de unos segundos nos llene de mensajes.
alias: Meshtastic - aviso status internet
description: Avisa por Meshtastic cuando internet se corta o vuelve.
triggers:
- trigger: state
entity_id: binary_sensor.internet
to: "off"
for:
minutes: 1
id: internet_caido
- trigger: state
entity_id: binary_sensor.internet
to: "on"
for:
minutes: 1
id: internet_ok
conditions: []
actions:
- action: meshtastic.send_text
data:
ack: false
from: "ID_NODO_SOLAR"
to: "ID_NODO_TRACKER"
text: >-
{% if trigger.id == 'internet_caido' %}
Internet caido.
{% else %}
Internet restaurado.
{% endif %}
mode: single
Saber el status de las luces encendidas
El segundo caso sirve para saber qué quedó encendido en casa. Desde el T1000-E mando solamente:
status
Home Assistant revisa todas las entidades del dominio light, se queda con las que están encendidas y las agrupa por área. Además filtra los grupos de luces para no contar dos veces el mismo dispositivo.
La respuesta tiene un formato corto, ideal para Meshtastic:
4 luces: Estudio 2, Cocina 1, Patio 1
alias: Meshtastic - status luces por zona
description: Responde cuántas luces reales hay encendidas por zona.
triggers:
- trigger: event
event_type: meshtastic_api_text_message
event_data:
data:
from: "ID_NODO_TRACKER"
to:
node: "ID_NODO_SOLAR"
channel: null
gateway: "ID_NODO_SOLAR"
conditions:
- condition: template
value_template: >-
{{ (trigger.event.data.data.message | string | lower | trim) == 'status' }}
actions:
- delay:
seconds: 2
- action: meshtastic.send_text
data:
ack: true
from: "ID_NODO_SOLAR"
to: "ID_NODO_TRACKER"
text: >-
{% set luces = namespace(total=0, zonas={}) %}
{% for luz in states.light
| selectattr('state', 'eq', 'on')
| rejectattr('attributes.entity_id', 'defined')
| list %}
{% set zona = area_name(luz.entity_id) or 'Sin zona' %}
{% set luces.total = luces.total + 1 %}
{% set luces.zonas = dict(
luces.zonas,
**{zona: (luces.zonas.get(zona, 0) + 1)}
) %}
{% endfor %}
{{ luces.total }} luces:
{% for zona, cantidad in luces.zonas.items() %}
{{ zona }} {{ cantidad }}{% if not loop.last %}, {% endif %}
{% endfor %}
mode: single
Controlar luces por zona o individuales desde Meshtastic
Esta es la más copada. Podemos controlar una luz puntual, todas las luces de una zona o directamente toda la casa.
Algunos ejemplos:
luz principal estudio on
luces cocina off
luces off
La diferencia entre luz y luces es importante:
luz <nombre> on|offbusca una entidad por su nombre completo.luces <zona> on|offactúa sobre todas las luces del área.luces on|offactúa sobre todas las luces reales de Home Assistant.
Antes de ejecutar la acción, la automatización comprueba el estado actual. Si una luz ya estaba apagada y pedimos apagarla, responde que ya estaba así. También avisa si no encuentra el nombre o la zona.
alias: Meshtastic - controlar luces
description: Controla luces por zona, todas, o una luz puntual desde Meshtastic.
triggers:
- trigger: event
event_type: meshtastic_api_text_message
event_data:
data:
from: "ID_NODO_TRACKER"
to:
node: "ID_NODO_SOLAR"
channel: null
gateway: "ID_NODO_SOLAR"
variables:
msg: "{{ trigger.event.data.data.message | string }}"
msg_norm: "{{ msg | lower | trim | replace('_', ' ') }}"
msg_parts: "{{ msg_norm.split() }}"
comando: "{{ msg_parts[0] if msg_parts | count > 0 else '' }}"
accion: "{{ msg_parts[-1] if msg_parts | count >= 2 else '' }}"
conditions:
- condition: template
value_template: >-
{{ msg_parts | count >= 2
and comando in ['luz', 'luces']
and accion in ['on', 'off'] }}
actions:
- variables:
objetivo: >-
{{ msg_parts[1:-1] | join(' ') if msg_parts | count > 2 else '' }}
luces_objetivo: >-
{% set ns = namespace(ids=[]) %}
{% for luz in states.light
| rejectattr('attributes.entity_id', 'defined')
| list %}
{% set nombre = (luz.attributes.friendly_name or luz.name or '')
| lower | trim %}
{% set zona = (area_name(luz.entity_id) or '') | lower | trim %}
{% if comando == 'luces' %}
{% if objetivo == '' or zona == objetivo %}
{% set ns.ids = ns.ids + [luz.entity_id] %}
{% endif %}
{% elif comando == 'luz' and nombre == objetivo %}
{% set ns.ids = ns.ids + [luz.entity_id] %}
{% endif %}
{% endfor %}
{{ ns.ids }}
luces_a_cambiar: >-
{% set ns = namespace(ids=[]) %}
{% for entity_id in luces_objetivo %}
{% if states(entity_id) != accion %}
{% set ns.ids = ns.ids + [entity_id] %}
{% endif %}
{% endfor %}
{{ ns.ids }}
- choose:
- conditions:
- condition: template
value_template: "{{ luces_a_cambiar | count > 0 and accion == 'on' }}"
sequence:
- action: light.turn_on
target:
entity_id: "{{ luces_a_cambiar }}"
- conditions:
- condition: template
value_template: "{{ luces_a_cambiar | count > 0 and accion == 'off' }}"
sequence:
- action: light.turn_off
target:
entity_id: "{{ luces_a_cambiar }}"
- delay:
seconds: 2
- action: meshtastic.send_text
data:
ack: false
from: "ID_NODO_SOLAR"
to: "ID_NODO_TRACKER"
text: >-
{% if luces_objetivo | count == 0 %}
No encontre {{ 'la luz' if comando == 'luz' else 'luces' }}{% if objetivo != '' %} {{ objetivo }}{% endif %}.
{% elif luces_a_cambiar | count == 0 %}
Ya estaba {{ 'encendida' if accion == 'on' else 'apagada' }}{% if comando == 'luz' %}: {{ objetivo }}{% elif objetivo != '' %}: {{ objetivo }}{% else %}: todo{% endif %}.
{% else %}
OK, {{ luces_a_cambiar | count }} {{ 'luz' if luces_a_cambiar | count == 1 else 'luces' }} {{ 'encendida' if accion == 'on' else 'apagadas' }}{% if objetivo != '' %}: {{ objetivo }}{% else %}: todo{% endif %}.
{% endif %}
mode: single
Mención para Meshtastic Argentina
Por último, los invito a que se sumen a la comunidad de Meshtastic Argentina. Tienen un grupo de Telegram (link). Ahí van a encontrar gente desparramada a lo largo de todo el territorio argentino. Desde coordinar despliegues de nodos por en distintas partes, hasta asistencia en cualquier duda o inconveniente que tengan.
Si llegaste hasta acá, no te olvides de suscribirte al canal.



