Gateway DGNWG02LM obtener token y key , e integración en Home Assistant

Hace poco cayo en mis manos un Gateway DGNWG02LM , ya tenia tres que integre en su dia y la verdad es que funcionan muy bien , tanto como luz de cortesia , para avisos visuales y avisos sonoros , y lo fui a integrar como se hacia hace unos años , pero mis coj .. veintitrés.

Antes mediante la aplicación Mi Home obtenías la KEY y la usabas directamente , pero ahora no hay forma directa de hacerlo y telita con la nueva forma de hacerlo , a ello se le suma que con los nuevos firmwares han bloqueado el puerto al que se accedia directamente para tener control via LAN , vamos una tocada de webs.

Yo uso Mi Home VEVS para saltarme las restricciones geográficas en mis dispositivos

Lo primero será sacar el token con VEVS

Ya tenemos el token , en este caso será el d3c1b247bfebdac74a25c5db89726a60

Ahora tenemos que poner nosotros una key a nuestra elección de 16 caracteres alfanumérica , lo haremos siguiendo este proyecto https://github.com/rytilahti/python-miio

Para ahorrarme problemas de versiones de Python y demás tonterias lo instalare en el docker de mi Home Assistant

pip install python-miio

Una vez instalado ejecutaremos el siguiente comando

miiocli gateway --ip 192.168.1.45 --token d3c1b247bfebdac74a25c5db89726a60 set_developer_key 0123456789012345

0123456789012345 será la key que le asignare y el Gateway lo había configurado en la IP 192.168.1.45

Si devuelve el OK entonces vamos por el buen camino

Esto serian los parámetros que le pondremos en nuestro secrets.yaml

######## GATEWAY XIAOMI 4 ######## 
gateway_4_key: 0123456789012345
gateway_4_mac: 04CF8CF69F4F

Vamos a ver como esta el tema de puertos , ejecutamos el siguiente comando

miiocli -d gateway --ip 192.168.1.45 --token d3c1b247bfebdac74a25c5db89726a60 enable_telnet

Y vemos que la respuesta nos da un error , malooooooo , muy malooooooooooooooooo

Para probar el puerto ejecutamos este comando de nmap

sudo nmap -sU 192.168.1.45 -p 9898

y vemos como nos dice que esta cerrado el puerto

No queda otra opción que abrir el cacharro , para ello seguiremos el manual de https://community.openhab.org/t/solved-openhab2-xiaomi-mi-gateway-does-not-respond/52963/171

Tiene tres tornillos en la parte inferior que debemos quitar

Estos tornillos tienen una forma bien curiosa

Una vez abierto separamos al parte del altavoz

Al no tener comunicación via wifi no nos queda mas remedio que conectarnos via UART , tendremos que soldar SOLO TRES CABLES , RX , TX y GND , no soldar la alimentación ya que tendremos que conectar el dispositivo a 220 mientras nos comunicamos con el

GND lo podemos coger de la carcasa del pulsador

Otra opción seria coger GND del conector directamente

Yo para comunicarme prefiero usar el hyperterminal de toda la vida , pero en las ultimas versiones de Windows no viene , pero lo podemos descargar de aquí mismo

Conectamos el Gateway y el adaptador USB a RS232

Los parámetros de comunicación son 115200, 8,N,1,N

Enviamos el comando  psm-set network.open_pf 3 , y lo comprobamos con el comando   psm-get network.open_pf para asegurarnos de que esta abierto

reiniciamos el gateway y si queremos opcionalmente podemos comprobar que el puerto 4321 esta abierto con el comando nmap -sU -p 4321 192.168.1.45

Al entrar en Home Assistant vemos como nos lo ha detectado ya

Ahora es donde tenemos que poner la KEY que habíamos grabado en el cacharro

Si todo es correcto nos lo reconoce al momento

Ya nos aparece el dispositivo con su MAC, en este caso seria light.gateway_light_04cf8cf69f4f

Lo ponemos bonito en el customize.yaml

light.gateway_light_04cf8cf69f4f:
  friendly_name: Luz Gateway 4
  icon: mdi:blur-radial

Ahora ya podemos empezar a trastear con el en nuestras automatizaciones , en este caso lo añadiré a la de parada de la caldera por apertura de puertas o ventanas

# ================================
# 🛑 DESACTIVACIÓN POR PUERTAS/VENTANAS
# ================================

- id: apagar_calefaccion_puertas_balcones_abiertas_5min
  alias: "🚪 Apagar Calefacción - Puertas Balcones Abiertas 5min"
  description: "Apaga la calefacción si puertas/balcones están abiertos más de 5 minutos"
  initial_state: true
  mode: restart
  trigger:
    - platform: state
      entity_id:
        - binary_sensor.sensor_balcon_comedor_derecho_evento
        - binary_sensor.sensor_balcon_comedor_izquierdo_evento
        - binary_sensor.sensor_balcon_matrimonio_derecho_evento
        - binary_sensor.sensor_balcon_matrimonio_izquierdo_evento
        - binary_sensor.sensor_oriol_derecho_evento
        - binary_sensor.sensor_oriol_izquierdo_evento
      to: 'on'
      for:
        minutes: 5
  condition:
    - condition: state
      entity_id: climate.calefaccion_casa
      state: heat
  action:
    - service: climate.turn_off
      target:
        entity_id: climate.calefaccion_casa
    
    - service: notify.notif_telegram_bot
      data:
        message: |
          ❄️🚪 *Calefacción APAGADA*
          
          ⚠️ Puerta/balcón abierto >5min
          🚪 Sensor: {{ trigger.to_state.attributes.friendly_name }}
          🌡️ Temperatura: {{ states('sensor.temperatura_comedor_calibrada') }}°C

    - service: light.turn_on
      entity_id: light.gateway_light_7c49eb1d1d0a
      data:
        brightness: 255
        rgb_color: [4, 29, 247] 
    - service: light.turn_on
      entity_id: light.gateway_light_286c07f0e736
      data:
        brightness: 255
        rgb_color: [4, 29, 247]       
    - service: light.turn_on
      entity_id: light.gateway_light_286c07f0b574
      data:
        brightness: 255
        rgb_color: [4, 29, 247] 
    - service: light.turn_on
      entity_id: light.gateway_light_04cf8cf69f4f
      data:
        brightness: 255
        rgb_color: [4, 29, 247] 
   
    # TTS solo entre las 8:00 y las 23:00
    - choose:
        - conditions:
            - condition: time
              after: "08:00:00"
              before: "23:00:00"
          sequence:
            - service: tts.google_translate_say
              data:
                language: "es-es"	
                entity_id: 
                  - media_player.lenovo_smart_clock
                  - media_player.lenovo_lenovo_matrimonio
                  - media_player.googlehome2670
                message: 'Apagado de la caldera , alguna puerta abierta'   
        
        
    #Apagamos las luces azules a los quince minutos        
    - delay: 00:15:00

    - service: light.turn_off
      entity_id: light.gateway_light_7c49eb1d1d0a
    - service: light.turn_off
      entity_id: light.gateway_light_286c07f0e736        
    - service: light.turn_off
      entity_id: light.gateway_light_286c07f0b574  
    - service: light.turn_off
      entity_id: light.gateway_light_04cf8cf69f4f  

Y con esto y un bizcocho ……

Cambio de Shelly 2.5 a Sonoff MINI-ZBRBS

Al fallarme uno de los casi indestructibles Shelly 2.5 ( desde Julio del 2019 como un campeón ) decidí que empezaría a cambiar las persianas a zigbee , para ello compre par probar un par Sonoff MINI-ZBRBS

Este mini se puede integrar perfectamente en zigbee2mqtt

https://www.zigbee2mqtt.io/devices/MINI-ZBRBS.html

El factor de forma es realmente pequeño y muy compacto, con medidas de 39.5 x 33 x 16.8 mm

Aquí os dejo el manual del Sonoff MINI-ZBRBS

Externamente cuenta con un botón y un LED que realizan estas funciones según pulsemos en un tiempo y una forma concreta

  • Mantener 5 s: modo emparejamiento (3 minutos).
  • Pulsar 3 veces: cambia el tipo de interruptor externo.
  • Mantener 10 s: inicia calibración.
  • LED azul:
    • Encendido fijo: conexión normal.
    • Parpadeo lento: emparejamiento.
    • Parpadeo rápido: error de conexión.
    • Apagado: fallo o tiempo agotado.
    • “Respiración”: modo calibración.

Nada mas ponerlo en modo emparejamiento zigbee2mqtt nos lo reconoce aunque en la versión 1.4 ponga que no esta soportado

Aquí podemos ver que expone ya el estado y la posición

Nos devolverá un json del estilo a este

{
	"cover_mode": {
		"calibration": false,
		"led": false,
		"maintenance": false,
		"reversed": false
	},
	"device": {
		"dateCode": "20250711",
		"friendlyName": "zb_persiana_matrimonio",
		"hardwareVersion": 0,
		"ieeeAddr": "0x7cc6b6fffeca7d88",
		"manufacturerID": 4742,
		"manufacturerName": "SONOFF",
		"model": "MINI-ZBRBS",
		"networkAddress": 1547,
		"powerSource": "Mains (single phase)",
		"softwareBuildID": "1.0.5",
		"type": "Router",
		"zclVersion": 8
	},
	"last_seen": "2025-12-15T21:48:41+01:00",
	"linkquality": 91,
	"position": 100,
	"state": "OPEN"
}

En Home Assistant tenia estos dos sensores

- name: temperatura_persiana_comedor
  state_topic: "shellies/shellyswitch25-68DD68/temperature"
  unit_of_measurement: ??C
  icon: mdi:temperature-celsius
  force_update: true


- name: sobretemperatura_persiana_matrimonio
  state_topic: "shellies/shellyswitch25-690423/overtemperature"
  payload_on: "1"
  payload_off: "0"    
  device_class: "heat"

y con esta definición del cover

- name: window_comedor_cover
  # friendly_name: "Persiana comedor"      
  state_topic: "shellies/shellyswitch25-68DD68/roller/0"
  command_topic: "shellies/shellyswitch25-68DD68/roller/0/command"
  position_topic: "shellies/shellyswitch25-68DD68/roller/0/pos"
  set_position_topic: "shellies/shellyswitch25-68DD68/roller/0/command/pos"
  # availability_topic: "shellies/shellyswitch25-68DD68/online"
  payload_available: "true"
  payload_not_available: "false"
  # retain: true
  payload_open: "open"
  payload_close: "close"
  payload_stop: "stop"
  state_open: "open"
  state_opening: "opening"
  state_closed: "close"
  state_closing: "closing"
  state_stopped: "stop"
  position_open: 100
  position_closed: 0
  qos: 0
  retain: false
  optimistic: false    

Una vez leída la información del dispositivo en esta pagina https://www.zigbee2mqtt.io/devices/MINI-ZBRBS.html#sonoff-mini-zbrbs empezamos a definir sensores y el cover

Así quedaria la parte de los sensores y de la persiana

# Sensores
sensor:

    - state_topic: "zigbee2mqtt/zb_persiana_comedor"
      availability_topic: "zigbee2mqtt/bridge/state"
      icon: "mdi:calendar-clock"
      value_template: "{{ value_json.last_seen }}"
      name: "zb_persiana_comedor_ultima_conexion"    
      
    - name: posicion_persiana_comedor
      state_topic: "zigbee2mqtt/zb_persiana_comedor"
      value_template: "{{ value_json.position }}"
      unit_of_measurement: "%"
      icon: mdi:blinds
      force_update: true
              
binary_sensor:

    - state_topic: "zigbee2mqtt/zb_persiana_comedor/availability"       
      availability_topic:  "zigbee2mqtt/zb_persiana_comedor/availability"     
      name: zb_persiana_comedor_disponibilidad      
      device_class: "connectivity"      
      payload_on: "online"
      payload_off: "offline"  

# Cover
cover:

    - name: window_comedor_cover
      command_topic: "zigbee2mqtt/zb_persiana_comedor/set"
      state_topic: "zigbee2mqtt/zb_persiana_comedor"
      position_topic: "zigbee2mqtt/zb_persiana_comedor"
      set_position_topic: "zigbee2mqtt/zb_persiana_comedor/set"
      availability_topic: "zigbee2mqtt/bridge/state"
      payload_available: "online"
      payload_not_available: "offline"
      
      # Comandos
      payload_open: '{"state": "OPEN"}'
      payload_close: '{"state": "CLOSE"}'
      payload_stop: '{"state": "STOP"}'
      position_open: 100
      position_closed: 0
      set_position_template: '{"position": {{ position }}}'
      
      # Estados
      value_template: "{{ value_json.state }}"
      position_template: "{{ value_json.position }}"
      state_open: "OPEN"
      state_opening: "opening"
      state_closed: "CLOSE"
      state_closing: "closing"
      state_stopped: "STOP"
      
      qos: 1
      retain: false
      optimistic: false 

Añadimos un sensor calculado que nos devuelve los minutos desde la ultima conexión del dispositivo

  - platform: template
    sensors:
      zb_persiana_comedor_disponibilidad_ultima_conexion_minutos:
        value_template: >-
          {% set x1 = as_timestamp(states('sensor.zb_persiana_comedor_ultima_conexion')) %}
          {% set x2 = as_timestamp(now()) %}
          {% set time = x2 - x1 | int(0) %}
          {% set days = (time/86400) | int %}
          {% set hours = (time / 3600 % 24) | int %}
          {% set minutes = (((time / 3600) % 1) * 60) | int %}
          {{ days ~ 'd ' ~ hours ~ 'h ' ~ minutes ~ 'm' }}        
        friendly_name: Ultima conexión persiana comedor
        icon_template: "mdi:calendar-clock"    

Y ya que estamos pues me dio por actualizar zigbee2mqtt de la versión 1.4 a versiones 2.X , lo primero cambiarlo en el docker compose

Bueno lo primero es hacer una buena copia de seguridad del directorio zigbee2mqtt para poder volver atrás en caso necesario

Una vez realizada la copia actualizamos el docker de zigbee2mqtt

Una vez acabado vemos que no arranca por un error en el adaptador USB

Según nos dice en esta pagina https://github.com/Koenkk/zigbee2mqtt/discussions/24364

Tenemos que pasar de esto que es lo que tenia actualmente

serial:
  port: /dev/ttyUSB0

Añadiendo esta linea

serial:
  port: /dev/ttyUSB0
  adapter: zstack

Y vemos que ya arranca perfectamente

Una vez arrancado introducimos el token que tenemos definido en configuration.yaml

frontend:
  enabled: true
  port: 28081
  host: 0.0.0.0
  auth_token: '¡TuTokenSuperSecretoAquí!'

Y ya podemos ver correctamente el dispositivo

Existen una serie de breaking changes a la hora de actualizar Zigbee2MQTT a 2.0.0 o superior , aquí estan las dos paginas donde lo explica claramente :

Y por ultimo la calibración del sonoff y la persiana

Calibración del MINI-ZBRBS solo con botones

Este método sirve para que el dispositivo aprenda la posición totalmente abierta y totalmente cerrada de la persiana.

⚠️ Imprescindible para que funcione el control por porcentaje.


Paso 1: Entrar en modo calibración

  1. Localiza el botón del MINI-ZBRBS.
  2. Mantén pulsado el botón más de 10 segundos.
  3. El LED azul empezará a “respirar” (encenderse y apagarse suavemente).
  4. Suelta el botón.
  5. Pulsa una vez brevemente el botón para entrar en calibración manual.

Paso 2: Guardar posición totalmente abierta

  1. Usa el interruptor externo (subir/bajar) para abrir completamente la persiana.
  2. Cuando esté totalmente abierta:
    • Pulsa brevemente el botón del dispositivo.
  3. El LED azul parpadeará 3 veces
    → posición “abierta” guardada.

Paso 3: Guardar posición totalmente cerrada

  1. El dispositivo cerrará la persiana automáticamente.
  2. Cuando esté totalmente cerrada:
    • Pulsa brevemente el botón del dispositivo otra vez.
  3. El LED azul parpadeará 3 veces
    → calibración completada.

Calibración finalizada ✅

La persiana ya está calibrada y el dispositivo conoce todo el recorrido.


Consejos importantes

  • 🔁 Si el recorrido no queda bien ajustado, repite todo el proceso.
  • ⏱️ No dejes el motor funcionando más de 2 minutos seguidos.
  • 🔄 Si la persiana sube cuando debería bajar:
    • Corta la corriente.
    • Intercambia los cables L out1 y L out2.
  • ⚡ Realiza la calibración con la persiana sin bloqueos.

Y este es el resultado final en nuestro panel lovelace

Y con esto y un bizcocho ………….