TTN to Domoticz without CayenneLPP

Moderator: leecollings

Post Reply
Peter83
Posts: 32
Joined: Sunday 31 July 2022 13:27
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

TTN to Domoticz without CayenneLPP

Post by Peter83 »

Hello,

i am new to this subject and after trying for 2 days now, i think asking for help is the only way for me to get this running.

I have a device sending with lorawan to ttn. TTN send mqtt/ i am trying to pic them up on their server.

In Domoticz i installed new hardware: MQTT Client Gateway with Lan Interface- Remote adress is eu1.cloud.thethings.network, Port ist 1883, Username is x@ttn, password ist API key. Topic in prefix is: v3/x@ttn/devices

with this datamqtt explorer shows my decoded payload.

End device in TTN uses "Use Device Repository formatter"

Code: Select all

function decodeUplink(input) {
  switch (input.fPort) {
    case 10:
      bytes = input.bytes;
      var data = {};
      for (i=0 ; i<bytes.length; i++) {
        switch (bytes[i]) {
          case 0x03:
            data.hw_version = bytes[++i];
            data.capabilities = (bytes[++i] << 8)+  bytes[++i];
            break;
          case 0x04:
            p = bytes[++i];
            v = (bytes[++i] << 8)+  bytes[++i];
            switch (p) {
              case 0x01:
                data.conf_system=v;
                break;
              case 0x02:
                data.conf_heartbeat=v;
                break;
              case 0x03:
                data.conf_heavyrain=v;
                break;
              case 0x04:
                data.conf_interval=v;
                break;
              default:
                if (p > 16) data.error = "config parameter? "+bytes[i];
                else {
                  data.conf_parameter = p;
                  data.conf_value = v;
                }
              }
            break;
            case 0x06:
              sensor = bytes[++i];
                sensorvalue = (bytes[++i] << 8)+  bytes[++i];
                switch(sensor) {
                case 0x01:
                    if (sensorvalue < 0x4000)
                      data.temperature_C = sensorvalue/10.0;
                    else
                      data.temperature_C = (sensorvalue - 0xffff)/10.0;
                    break;
                case 0x02:
                      data.humidity = sensorvalue;
                    break;
                case 0x03:
                    data.uptime = sensorvalue;
                    break;
                case 0x08:
                    if (sensorvalue < 0x4000)
                      data.airtemperature_C = sensorvalue/10.0;
                    else
                      data.airtemperature_C = (sensorvalue - 0xffff)/10.0;
                    break;
                case 0x11:
                    data.consumption = sensorvalue;
                    break;
                case 0x12:
                    data.flow = sensorvalue;
                    break;
                case 0x81:
                    data.rainlevel = sensorvalue * 0.5 ; // 1 == 500 ml
                    break;
                  default:
                      data.error = "sensor type? "+bytes[i];
                }
            break;
          case 0x07:
              data.motorposition = bytes[++i];
              break;
          case 0x0a:
              data.fw_version = (bytes[++i] << 24) +(bytes[++i] << 16) + (bytes[++i] << 8) + bytes[++i];
              break;
          case 0x0b:
              data.a_status = bytes[++i];
              data.a_type = bytes[++i];
              data.a_value = (bytes[++i] << 8)+  bytes[++i];
              var val;
              switch (data.a_type) {
                case 0x01:
                  data.alarm = "Flood";
                  val = " ";
                  break;
                case 0x02:
                  data.alarm = "Temperature Low";
                    if (data.a_value > 0x4000)
                      data.a_value = (data.a_value - 0xffff)/10.0;
                  val = " with " + data.a_value + " C";
                  break;
                case 0x03:
                  data.alarm = "Heavyrain";
                  val = " with " + data.a_value + " l/m3";
                  break;
                case 0x04:
                  data.alarm = "Humidity";
                  val = " with " + data.a_value + " %";
                  break;
                case 0x05:
                  data.alarm = "Vibration";
                  val = " device";
                  break;
                case 0x06:
                  data.alarm = "Temperature High";
                    if (data.a_value > 0x4000)
                      data.a_value = (data.a_value - 0xffff)/10.0;
                  val = " with " + data.a_value + " C";
                  break;
                case 0x07:
                  data.alarm = "Air Temperature Low";
                    if (data.a_value > 0x4000)
                      data.a_value = (data.a_value - 0xffff)/10.0;
                  val = " with " + data.a_value + " C";
                  break;
                case 0x0c:
                  data.alarm = "Battery";
                  val = " with " + data.a_value +  " mAh";
                  break;
                default:
                  data.error = "Alarm? "+data.a_type;
                  break;
              }
              if (data.a_status) data.alarm += " on" + val; else data.alarm += " off" + val;
              break;
          case 0x12:
              data.bat_volt = (bytes[++i])/10.0;
              data.bat_mAh = (bytes[++i] << 8) + bytes[++i];
              break;
          case 0x33:
              data.duration = (bytes[++i] << 8)+  bytes[++i];
              data.diff = (bytes[++i] << 8)+  bytes[++i];
                break;
          default:
              data.error = "command? "+bytes[i];
          }
        }
      return { data: data};
    default:
      return {errors: ['invalid FPort']};
    }
}

function encodeDownlink(input) {
  cmd = input.data.cmd;
  switch (String(cmd)) {
    case "config_set":
      return {
        fPort: 10,
        bytes: [0x04, input.data.parameter, (input.data.value>>8) & 0xff, input.data.value & 0xff],
      };
    case "get_hw":
      return {
        fPort: 10,
        bytes: [0x03],
      };
    case "get_fw":
      return {
        fPort: 10,
        bytes: [0x1a],
      };
    case "config_get":
      return {
        fPort: 10,
        bytes: [0x14, input.data.parameter],
      };
  }
}

function decodeDownlink(input) {
  bytes = input.bytes;
  data = {};
  if (input.fPort != 10)
    return {
        errors: ['invalid FPort'],
    };
  switch (bytes[0]) {
    case 0x04:
      data.cmd = "config_set";
      data.parameter = bytes[1];
      data.value =  (bytes[2] << 8)+  bytes[3];
      break;
    case 0x03:
      data.cmd = "get_hw";
      break;
    case 0x0a:
      data.cmd = "get_fw";
      break;
    case 0x04:
      data.cmd = "config_get";
      data.parameter = bytes[1];
      break;
  }
  return {data:data}
}
MQTT Explorer shows:

Code: Select all

{
"end_device_ids": {
"device_id":"y","application_ids" {
"application_id":"x"
},
"dev_eui":"060000F6xxx","join_eui":"49434854xxx1","dev_addr":"260B8xx"},

"correlation_ids":["gs:uplink:01KATNGAEXDXZKQ5Q9QJ5HTV3C"],
"received_at":"2025-11-24T10:10:45.547949795Z",
"uplink_message":{
"session_key_id":"AZqYkxmTl646rq5ll3XJlQ==",
"f_port":10,"f_cnt":95,"frm_payload":"Eh0AAwYBAPoGgQAA",
"decoded_payload":{
"bat_mAh":3, 
"bat_volt":2.9, 
"rainlevel":0, 
"temperature_C":25},
"rx_metadata":[{
"gateway_ids":{
"gateway_id":"eui-7076ff005607xx","eui":"7076FF005xx"},
"time":"2025-11-24T10:10:45.263Z",
"timestamp":3943593060,"rssi":-122,
"channel_rssi":-122,
"snr":-1.2,
"uplink_token":"CiIKIAoUZXVpLTcwNzZmZjAwNTYwNzBiODxx/wBWBwuDEOxxxgwIpd6QyQYQjNDPogEgoM3ag+OFTioLCKXekMkGEMCftH0=",
"channel_index":5,
"gps_time":"2025-11-24T10:10:45.263Z",
"received_at":"2025-11-24T10:10:45.341043212Z"}],
"settings":{
"data_rate":{
"lora":{
"bandwidth":125000, 
"spreading_factor":10, "coding_rate":"4/5"}},
 "frequency":"868100000", "timestamp":3943593060, "time":"2025-11-24T10:10:45.263Z"},"received_at":"2025-11-24T10:10:45.342156701Z","consumed_airtime":"0.411648s",
 "packet_error_rate":0.071428575,
 "locations":{"user":{"latitude":53.xxx,"longitude":9.xxx,"altitude":22,"source":"SOURCE_REGISTRY"}},
 "version_ids":{"brand_id":"aquascope", "model_id":
 "ran", 
 "hardware_version":"1.0", 
 "firmware_version":"241115", 
 "band_id":"EU_863_870"},"network_ids":{"net_id":"000013",
 "ns_id":"EC656E0000000181",
 "tenant_id":"ttn","cluster_id":"eu1",
 "cluster_address":"eu1.cloud.thethings.network"}}}
domoticz log just shows:

Code: Select all

TtnMqttFuerLuaScripte: Connected to: eu1.cloud.thethings.network:1883
then i do have a lua script supposed to translate the paylode to idx's

Code: Select all

-- Lua script to parse TTN MQTT messages

commandArray = {}

-- Das VOLLSTÄNDIGE erwartete MQTT Topic
-- Dies ist der exakte Topic-String, den TTN verwendet
local expectedFullTopic = "v3/x@ttn/devices/y/up"                        --DAS MUSS FÜR JEDES DEVICE UND GGF AUCH FÜR DIE APPLICANTION NEU

local rainSensorIDX = 45

local batSensorIDX = 46

-- PRÜFUNG HINZUFÜGEN: Stellen Sie sicher, dass 'devicechanged' existiert und 'MQTTTopic' enthält
if (devicechanged and devicechanged['MQTTTopic']) then
    
    -- Prüfen, ob es der RICHTIGE, vollständige Topic ist
    if (devicechanged['MQTTTopic'] == expectedFullTopic) then

        -- Die JSON-Payload auslesen
        local jsonPayload = devicechanged['MQTTPayload']
        
        -- JSON dekodieren
        local decodedData = jsonlite.decode(jsonPayload)

        -- Überprüfen, ob die Dekodierung erfolgreich war und die Datenstruktur passt
        if (decodedData and decodedData.uplink_message and decodedData.uplink_message.decoded_payload) then
    
            -- Hier greifen Sie auf die spezifischen Werte zu, die TTN dekodiert hat
            local rainlevel = decodedData.uplink_message.decoded_payload.rainlevel
            local bat_volt = decodedData.uplink_message.decoded_payload.bat_volt

            -- Werte in die virtuellen Domoticz-Geräte schreiben
            if (rainlevel) then
                commandArray = {['UpdateDevice'] = rainSensorIDX .. "|0|" .. rainlevel}
            end

            if (bat_volt) then
                 commandArray = {['UpdateDevice'] = batSensorIDX .. "|0|" .. bat_volt .. ";0"}
            end
        end
    end
end

return commandArray

So why is ther no Payload coming in to Domoticz? If it did come in, does the script run properly?
User avatar
waltervl
Posts: 6677
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2025.1
Location: NL
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by waltervl »

As you are new to this: Did you see the following wiki https://wiki.domoticz.com/TTNMQTT
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
Peter83
Posts: 32
Joined: Sunday 31 July 2022 13:27
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by Peter83 »

waltervl wrote: Monday 24 November 2025 21:51 As you are new to this: Did you see the following wiki https://wiki.domoticz.com/TTNMQTT
thanks for your answer.

So i tried "The Things Network (MQTT/CayenneLPP) with LAN interface" with those settings before. Didnt work either. My device is not using CayenneLpp. Maybe thats my problem? But i cant and dont want to change the device. I need to find a way do get Payload in.

with "The Things Network (MQTT/CayenneLPP) with LAN interface" first i had ca-cert problems though i wantedt to use 1883 and left Ca.. empty. i somehow manged to loose those errors but i cant remember how. than log says:

Code: Select all

Could not find or open aliasses file (ttnmqtt_aliasses.json)
User avatar
waltervl
Posts: 6677
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2025.1
Location: NL
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by waltervl »

Perhaps hook into the next topic viewtopic.php?t=22645 to get some answers
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
Peter83
Posts: 32
Joined: Sunday 31 July 2022 13:27
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by Peter83 »

yes ive seen that one to and one other thread as well. But it didn't help me. Im gonna read it again and might ask some questions in that topic as well. Thanks
Peter83
Posts: 32
Joined: Sunday 31 July 2022 13:27
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by Peter83 »

Now i set up ttn(MQTT/CayenneLPP) with Lan interface again an wrote a ttnmqtt_aliases.joson.

Domoticz shows following error now:

Code: Select all

Invalid data received! Unable to decode the raw payload and the decoded payload does not contain any (valid) data!
Is this because may payload is not 100% accurate with Domoticz-names? like temperature_C vs temperature?

i tried 3 differen ttnmqtt_aliases.joson:

Code: Select all

{
  "v3/x@ttn/devices/y/up": "Wald3;/uplink_message/decoded_payload"
}

Code: Select all

{
  "v3/x@ttn/devices/y/up": "Wald3;uplink_message.decoded_payload"
}

Code: Select all

{
  "v3/x@ttn/devices/x/up": "Wald3;['uplink_message']['decoded_payload']"
}
same result everytime
User avatar
waltervl
Posts: 6677
Joined: Monday 28 January 2019 18:48
Target OS: Linux
Domoticz version: 2025.1
Location: NL
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by waltervl »

The MQTT payload coming from TTN MQTT should follow Cayenne LPP format.
Then Domoticz TTNMQTT gateway can understand the mesage , create a device or user variable (for GPS data) and updates it.

How to arrange that the MQTT is in the Cayenne LPP format I have no idea. I am not familiar with TTN.

Perhaps take a look in the Domoticz TTNMQTT source code to figure out what it exactly is doing
https://github.com/domoticz/domoticz/bl ... TNMQTT.cpp
Domoticz running on Udoo X86 (on Ubuntu)
Devices/plugins: ZigbeeforDomoticz (with Xiaomi, Ikea, Tuya devices), Nefit Easy, Midea Airco, Omnik Solar, Goodwe Solar
Peter83
Posts: 32
Joined: Sunday 31 July 2022 13:27
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by Peter83 »

thank you. i will have a look and if i got questions i'll ask in a ttn forum.

i tried for some more hours today and couldn't get it running. i just think that payload is not working for me. but changes there didn't help yet.
tried some with AI but i think i got in a loophole
User avatar
kiddigital
Posts: 447
Joined: Thursday 10 August 2017 6:52
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Netherlands
Contact:

TTN to Domoticz without CayenneLPP

Post by kiddigital »

Take a look at `https://github.com/domoticz/domoticz/bl ... asses.json` to see an example of the json files that can be used to provide aliasses for the TTN device so domoticz knows what type of sensor it should be mapped to.

If you start domoticz with the additional debugflags (via commandline) `hardware,received`, the TTN mqtt hardware module will output more details on what it is receiving and trying to do.

The decoded payload looks ok. You don’t need any Lua scripts.
One RPi with Domoticz, RFX433e, aeon labs z-wave plus stick GEN5, ha-bridge 5.4.0 for Alexa, Philips Hue Bridge, Pimoroni Automation Hat
One RPi with Pi foundation standard touch screen to display Dashticz
User avatar
kiddigital
Posts: 447
Joined: Thursday 10 August 2017 6:52
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Netherlands
Contact:

TTN to Domoticz without CayenneLPP

Post by kiddigital »

I see 2 measurements in the payload:
rainlevel
temperature_C
both ‘measurements’ are unknown to the TTNMQTT.
Use the aliasses file to map those to known measurements, so ‘temperature_C’ -> ‘temp’.
I think rainsensors are not supported (yet), but you might try to map it to ‘analog_input’?
Make sure that ‘accept new hardware/sensors’ is turned on.
One RPi with Domoticz, RFX433e, aeon labs z-wave plus stick GEN5, ha-bridge 5.4.0 for Alexa, Philips Hue Bridge, Pimoroni Automation Hat
One RPi with Pi foundation standard touch screen to display Dashticz
Peter83
Posts: 32
Joined: Sunday 31 July 2022 13:27
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by Peter83 »

Thank you. i restartet and took some ttn-bridge.sh i changed a bit. working now. just sending battery and rain (value) now and wrtoe a dz-script for calculating rain from added sensor value.
User avatar
kiddigital
Posts: 447
Joined: Thursday 10 August 2017 6:52
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Netherlands
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by kiddigital »

Good to hear.

I do not understand why you have a (additional) script to proces the TTN sensor data.

Or are you using a script to do some additional calculations AFTER you received the sensor values in domoticz?
One RPi with Domoticz, RFX433e, aeon labs z-wave plus stick GEN5, ha-bridge 5.4.0 for Alexa, Philips Hue Bridge, Pimoroni Automation Hat
One RPi with Pi foundation standard touch screen to display Dashticz
Peter83
Posts: 32
Joined: Sunday 31 July 2022 13:27
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by Peter83 »

both,
so now i have ttn_bridge more or less like this:

Code: Select all

#!/bin/sh

TTN_HOST="eu1.cloud.thethings.network" # Oder Ihre spezifische Cluster-Adresse
TTN_USER="x@ttn"
TTN_PASS="xxx"

# Domoticz (lokal) MQTT Host or whole log in data!
DOMO_HOST="xx"
DOMO_PORT="xx"
DOMO_USER="xx"
DOMO_PASS="xx"

mosquitto_sub -h $TTN_HOST -t 'v3/xxx/+/up' -u $TTN_USER -P $TTN_PASS | 
while read RAW_DATA
do
    RAIN_COUNT=$(echo "$RAW_DATA" | jq '.uplink_message.decoded_payload.rainlevel')
    BAT_INT=$(echo "$RAW_DATA" | jq '.uplink_message.decoded_payload.bat_voltage')
    BAT_FLOAT=$(awk "BEGIN {printf \"%.1f\", $BAT_INT / 10}")
    echo "Received:  Rain=$RAIN_COUNT, Battery=$BAT_FLOAT V"    
    
   # mosquitto_pub -h $DOMO_HOST -m '{ "idx" : 12, "nvalue" : 0, "svalue" : "'$TEMP_FLOAT'" }' -t 'domoticz/in'
    mosquitto_pub -h $DOMO_HOST -p $DOMO_PORT -u $DOMO_USER -P $DOMO_PASS -m '{ "idx" : 45, "nvalue" : 0, "svalue" : "'$RAIN_Count'" }' -t 'domoticz/in'   
    mosquitto_pub -h $DOMO_HOST -p $DOMO_PORT -u $DOMO_USER -P $DOMO_PASS -m '{ "idx" : 46, "nvalue" : 0, "svalue" : "'$BAT_FLOAT'" }' -t 'domoticz/in'

done
and to get value to rain i have a dz.vents script:

Code: Select all

return {
    active = true,
    on = {
        devices = { 
            'CountWohld3' 
        }
    },
    execute = function(domoticz, device)
        
        local rawInputValue = tonumber(device.sValue) or 0
        local rainDevice = domoticz.devices('RainWohld3') 
        
        -- Gesamtwert VOR diesem Update lesen, falls vorhanden (ansonsten 0)
        local currentRainBeforeUpdate = tonumber(rainDevice.sValue:match("%;(%d+%.?%d*)$")) or 0
        
        if rawInputValue > 0 then
            -- --- FALL 1: ES REGNET / ES GIBT EINEN IMPULS ---
            
            local faktor = 0.5
            local valueToAdd = rawInputValue * faktor
            local newValue = currentRainBeforeUpdate + valueToAdd 
            local FIXED_TIMESLICE_HOURS = 0.25 
            local rainRatePerHour = domoticz.utils.round((valueToAdd / FIXED_TIMESLICE_HOURS), 1)

            rainDevice.updateRain(rainRatePerHour, newValue)
            
            domoticz.log('LOGIK-OK (devices): Regen hinzugefügt. Gesamtstand: ' .. newValue .. ' mm.')

        else 
            -- --- FALL 2: KEIN REGEN / WERT IST 0 GEBLIEBEN ---
            
            local currentRainRate = 0 
            local newValue = currentRainBeforeUpdate 
            
            rainDevice.updateRain(currentRainRate, newValue)
            
            domoticz.log('LOGIK-OK (devices): Kein Regen. Rate auf 0 gesetzt. Gesamtstand bleibt bei: ' .. newValue .. ' mm.')
        end
    end
}
}
still in testing but looks good so far.

the payload formatter i deleted a lot so its really just telling bat-volt and rain values
Last edited by Peter83 on Saturday 29 November 2025 22:41, edited 3 times in total.
User avatar
kiddigital
Posts: 447
Joined: Thursday 10 August 2017 6:52
Target OS: Raspberry Pi / ODroid
Domoticz version: Beta
Location: Netherlands
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by kiddigital »

The bridge shouldn’t be needed. The TTNMQTT hardware can connect directly to the TTN server, get the payload and create/find the correct device to store the values.

From there, you can use the dzVents script to process the raw values.
One RPi with Domoticz, RFX433e, aeon labs z-wave plus stick GEN5, ha-bridge 5.4.0 for Alexa, Philips Hue Bridge, Pimoroni Automation Hat
One RPi with Pi foundation standard touch screen to display Dashticz
Peter83
Posts: 32
Joined: Sunday 31 July 2022 13:27
Target OS: Raspberry Pi / ODroid
Domoticz version:
Contact:

Re: TTN to Domoticz without CayenneLPP

Post by Peter83 »

yep, thats what i thougt but i could not get in running for hours of trying.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest