How to publish / relay MQTT downlinks to The Things Network?

N00b MQTT question.

Situation: I have a network of The Things Network lorawan sensors connected to HA. TTN has an MQTT broker which publishes the uplink data. Roughly following this blog, I have my own MQTT broker on my local network which subscribes to the TTN Broker, and then relays the messages to HA. This all works OK.

Aim: What I am now trying to do is send data the other way: i.e. send downlink commands from HA to one of the nodes my Things Network. I have this working from the command line in my MQTT broker. (Probably not hugely important to this query, but this is the CLI command which works as expected):

mosquitto_pub -d -h MYTENANT.eu2.cloud.thethings.industries -u 'MYAPP@MYTENANT' -P 'REDACTEDPASSWORD' -t 'v3/MYAPP@MYTENANT/devices/orchard-controller/down/push' -m '{"downlinks":[{"f_port": 1,"frm_payload":"BQASdTA=","priority": "NORMAL"}]}'

Problem: I cannot figure out how to send this within HA. If I go to ‘Publish a packet’ under my MQTT integration, the ‘broker address’ is the address of my local broker, not the TTN host that I am trying to reach (MYTENANT.eu2.cloud.thethings.industries). Do I need to modify the relay script running on my local MQTT broker? Or can I create a second MQTT integration within HA which points directly to TTN’s broker? Or am I missing something else altogether?

Use the Shell Command integration.

You configure a new Shell Command using your mosquitto_pub command. You can pass variables to your shell_command, such as the topic and payload (refer to the last example in its documentation).

Many thanks, @123 . Since I posted my original message a few hours ago, I think I have it working through editing the relay script in Mosquitto. But thanks for the suggestion - I’ll look into that if this approach doesn’t work.

In case of interest, this is the modified relay script I’m using. This seems to relay things both ways as I want, though I’ve only been testing it a few minutes so far:

import paho.mqtt.client as mqtt
import paho.mqtt.publish as publish
import threading

TTN_BROKER = 'MYTENANT.eu2.cloud.thethings.industries'
TTN_USERNAME = 'MYAPP@MYTENANT'
TTN_PASSWORD = 'REDACTEDPASSWORD'>
TTN_TOPIC = '#'
LOCAL_BROKER = '192.168.1.46'
LOCAL_UPLINK_TOPIC = 'homeassistant/sensor/lorawan/water'
LOCAL_DOWNLINK_TOPIC = 'lorawan/downlink/orchard-controller'

def on_ttn_connect(client, userdata, flags, rc):
    """Subscribe to topic after connection to broker is made."""
    print("Connected with result code", str(rc))
    client.subscribe(TTN_TOPIC)

def on_ttn_message(client, userdata, msg):
    """Relay message to a different broker."""
    try:
      publish.single(
          LOCAL_UPLINK_TOPIC, payload=msg.payload, qos=0, retain=False,
          hostname=LOCAL_BROKER, port=1883, client_id='ttn-local',
          keepalive=60, will=None, auth=None, tls=None, protocol=mqtt.MQTTv311)
    except Exception as e:
      print(f"Error relaying uplink: {e}")

def on_local_connect(client, userdata, flags, rc):
     """subscribe to downlink messages on local broker."""
     print("Connected to local broker with result code", str(rc))
     client.subscribe(LOCAL_DOWNLINK_TOPIC)

def on_local_message(client, userdata, msg):
     """relay downlink message from local broker to TTN"""
     print("Received downlink request from Home Assistant.")
     try:
       publish.single('v3/cnf-lilygo-lorawan@cnf/devices/orchard-controller/down/push',
          payload=msg.payload, qos=0, retain=False,
          hostname=TTN_BROKER, port=1883, client_id='local-ttn',
          keepalive=60,auth={'username':TTN_USERNAME, 'password':TTN_PASSWORD},
          tls=None,  # Must remember to add this later!
          protocol=mqtt.MQTTv311)
       print("relayed downlink to TTN.")
     except Exception as e:
       print(f"Error relaying downlink to TTN: {e}")

ttn_client = mqtt.Client()
ttn_client.username_pw_set(TTN_USERNAME, password=TTN_PASSWORD)
ttn_client.on_connect = on_ttn_connect
ttn_client.on_message = on_ttn_message

local_client = mqtt.Client()
local_client.on_connect = on_local_connect
local_client.on_message = on_local_message

# Connect both clients
ttn_client.connect(TTN_BROKER, 1883, 60)
local_client.connect(LOCAL_BROKER, 1883, 60)

# Run both clients in parallel threads
threading.Thread(target=ttn_client.loop_forever).start()
threading.Thread(target=local_client.loop_forever).start()

Edit: I’m marking this as resolved for now.