Working Example: Homeassistant to LoRaWAN Chirpstack Downlink Commands

I have spent about 2 days getting this to work so I thought others would benefit if I document a working setup.

There is information out there - but I found it incompete. Some of what I include below is obvious, some is obscure and hard to find.

What I had to work with

Starting Point

See: this

Objective

Run a script on HA that sends a command to the LHT52

The mechanism is:

  • Use HA scripts to send an MQTT message to the MQTT server built into LPS8v2/Chirpstack
  • This MQTT message is received by the encodeDownlink() codec function, and Chirpstack adds the message to the downlink queue for the end device.

Hints/Tricks

  • The MQTT message can include ONE of these options:
    1. a Base64-encoded command, that bypasses the encodeDownlink() function and just gets added to the downlink queue, or
    2. a JSON object that can contain anything. The encodeDownlink() function must create the downlink command from that JSON. Importantly, there is no Base64 involved at all using this method.
  • HA (as I write this) can do Base64 encoding of ASCII strings, but not binary byte arrays. As the commands are binary byte arrays, the JSON object method has to be used.
  • HA can only subscribe to ONE MQTT server. While there is an MQTT server built into Chirpstack, chances are HA will be connected to a different MQTT server (often the MQTT server installed on the HA server) to gather data from more than just LoRaWAN sensors. See below the discussion on MQTT Servers.

MQTT Servers

In my setup, the primary MQTT server was the one bundled with HA ( = [HA-MQTT]).

Uplink sensor data messages flow like this:
[LPS8V2][MQTT Forwarder] --->[HA-MQTT]->[HA]

Downlink command messages flow like this:
[HA]->[HA-MQTT]---bridge--->[Chirpstack MQTT][Chirpstack]

To get this to work, it is neccessary to use MQTT Bridging to get [HA-MQTT] to be a client of [Chirpstack MQTT]

These are the clearest instructions I could find.

Here is my /share/mosquitto/lwgateway.conf file.

connection LwGateway
address dragino-2a748c.local
topic # out      # HA-MQTT forwards on all messages to LwGateway

HA scripts

My design was to have:

  • Separate command scripts; one for each downlink command.
    • e.g. Alarm command
    • e.g. Reset command
  • A common SendCommand script that sends the command to [Chirpstack MQTT]

The pseudo-code is:

SetTempAlarm(applicationID, devEui, enable, minutes, templowc, temphighc)
  Build command;
  SendCommand(applicationID, devEui,command);
    Build Topic & payload
    mqtt.publish(Topic, payload)

SetTempAlarm script

sequence:
  - variables:
      basecommand: AA
      enablecommand: "00"
  - if:
      - condition: template
        value_template: "{{ enable }}"
    then:
      - variables:
          enablecommand: "01"
  - action: script.sendcommand
    metadata: {}
    data:
      devEui: "{{ devEui }}"
      applicationID: "{{ applicationID }}"
      command: >-
        {{ basecommand ~ enablecommand ~ '%04X'%minutes ~ '%04X'%templowc ~
        '%04X'%temphighc }}
fields:
  devEui:
    selector:
      text: null
    name: devEui
    description: DevEUI
    required: true
  enable:
    selector:
      boolean: {}
    name: enable
    required: true
    default: true
  templowc:
    selector:
      number:
        min: -100
        max: 200
    name: TempLowC
    description: Alarm lower temp
    default: 21
    required: true
  temphighc:
    selector:
      number:
        min: -100
        max: 200
    name: TempHighC
    description: Alarm upper temp
    default: 32
    required: true
  minutes:
    selector:
      number:
        min: 1
        max: 100
    name: minutes
    description: interval to check temp
    required: true
    default: 2
  applicationID:
    selector:
      text: null
    name: applicationID
    description: Chirpstack Application ID
    required: true
alias: SetTempAlarm
description: Sets the Alarm for Dragino LHT devices

SendCommand Script

sequence:
  - action: mqtt.publish
    metadata: {}
    data:
      qos: "0"
      retain: false
      topic: application/{{ applicationID }}/device/{{ devEui }}/command/down
      payload: >-
        { "devEui":"{{ devEui }}","confirmed": false,"fPort":
        1,"object":{"command":"{{command}}"} }
fields:
  devEui:
    selector:
      text: null
    name: devEui
    description: DevEUI
    required: true
  applicationID:
    selector:
      text: null
    name: applicationID
    description: Chirpstack Application ID
    required: true
  command:
    selector:
      text: {}
    name: Command
    description: Hex String
    required: true
alias: SendCommand
description: Sends MQTT message to the Gateway with a downlink command

Chirpstack Encoder

This is appended to:

  • Chirpstack > Tenant > Device Profiles > [device] > Codec >Payload codec
  • Chirpstack > Network Server > Device Profile Templates > [device] > Codec > Payload codec
function encodeDownlink(input) {
  var command = input.data.command;
  var output = [];

  for (var i=0,j=0; i<command.length; i+=2,j++) {
   output[j]=parseInt(command.substr(i,2), 16);
  }

  return {bytes: output};
}

Hope this helps the next person

2 Likes