How to integrate an already made payload parser for TTN in TagoIO?

Hi,

Is it possible to explain or make a concrete example of how to integrate/adapt an already finished decoder (JS for TTN) into the TagoIO payload parser?

Specifically, I have receive raw data from ChirpStack in TagoIO and I have a decoder for TTN (JS code).

So, from beginning to end. Thx!

Hi markosplit,

Can you send over the decoder? I’ll change it to the format and explain it for you!

Hi, thx for help!

I am attaching the following files:



\- raw\_payload\_tagoio.txt (from Live inspector)  

\- Dragino decoder ([official wiki page](https://github.com/dragino/dragino-end-node-decoder/tree/main)) for TTN (LSN50V2\_v1.8.0\_Decoder\_TTN)  

      and for Chirpstack (LSN50V2\_v1.8.0\_Decoder\_ChirpstackV4).

  

Both decoder are working, first is tested on Helium console, second on [https://console.helium-iot.xyz](https://console.helium-iot.xyz) (Chirpstack).  

  

Correct decoded data also attached (decoded\_data).  



Can anyone help solve this problem?

Hi Markosplit,

I created the following decoder which you can add to your device payload parser or add to a connector you created:

function Decoder(bytes, port) {const decode = {};const mode = (bytes[6] & 0x7C) >> 2;decode.Digital_IStatus = (bytes[6] & 0x02) ? "H" : "L";if (mode != 2) {decode.BatV = (bytes[0] << 8 | bytes[1]) / 1000;decode.TempC1 = (bytes[2] == 0x7f && bytes[3] == 0xff) ? "NULL" : parseFloat(((bytes[2] << 24 >> 16 | bytes[3]) / 10).toFixed(1));if (mode != 8) {decode.ADC_CH0V = (bytes[4] << 8 | bytes[5]) / 1000;}}if (mode != 5 && mode != 6) {decode.EXTI_Trigger = (bytes[6] & 0x01) ? "TRUE" : "FALSE";decode.Door_status = (bytes[6] & 0x80) ? "CLOSE" : "OPEN";}switch (mode) {case 0:decode.Work_mode = "IIC";if ((bytes[9] << 8 | bytes[10]) === 0) {decode.Illum = (bytes[7] << 8 | bytes[8]);} else {decode.TempC_SHT = ((bytes[7] == 0x7f && bytes[8] == 0xff) || (bytes[7] == 0xff && bytes[8] == 0xff)) ? "NULL" : parseFloat(((bytes[7] << 24 >> 16 | bytes[8]) / 10).toFixed(1));decode.Hum_SHT = (bytes[9] == 0xff && bytes[10] == 0xff) ? "NULL" : parseFloat(((bytes[9] << 8 | bytes[10]) / 10).toFixed(1));}break;case 1:decode.Work_mode = "Distance";decode.Distance_cm = (bytes[7] === 0x00 && bytes[8] === 0x00) ? "NULL" : parseFloat(((bytes[7] << 8 | bytes[8]) / 10).toFixed(1));if (!(bytes[9] == 0xff && bytes[10] == 0xff)) {decode.Distance_signal_strength = (bytes[9] << 8 | bytes[10]);}break;case 2:decode.Work_mode = "3ADC+IIC";decode.BatV = bytes[11] / 10;decode.ADC_CH0V = (bytes[0] << 8 | bytes[1]) / 1000;decode.ADC_CH1V = (bytes[2] << 8 | bytes[3]) / 1000;decode.ADC_CH4V = (bytes[4] << 8 | bytes[5]) / 1000;if ((bytes[9] << 8 | bytes[10]) === 0) {decode.Illum = (bytes[7] << 8 | bytes[8]);} else {decode.TempC_SHT = ((bytes[7] == 0x7f && bytes[8] == 0xff) || (bytes[7] == 0xff && bytes[8] == 0xff)) ? "NULL" : parseFloat(((bytes[7] << 24 >> 16 | bytes[8]) / 10).toFixed(1));decode.Hum_SHT = (bytes[9] == 0xff && bytes[10] == 0xff) ? "NULL" : parseFloat(((bytes[9] << 8 | bytes[10]) / 10).toFixed(1));}break;case 3:decode.Work_mode = "3DS18B20";decode.TempC2 = (bytes[7] == 0x7f && bytes[8] == 0xff) ? "NULL" : parseFloat(((bytes[7] << 24 >> 16 | bytes[8]) / 10).toFixed(1));decode.TempC3 = (bytes[9] == 0x7f && bytes[10] == 0xff) ? "NULL" : parseFloat(((bytes[9] << 24 >> 16 | bytes[10]) / 10).toFixed(1));break;case 4:decode.Work_mode = "Weight";decode.Weight = (bytes[9] << 24 | bytes[10] << 16 | bytes[7] << 8 | bytes[8]);break;case 5:decode.Work_mode = "1Count";decode.Count = (bytes[7] << 24 | bytes[8] << 16 | bytes[9] << 8 | bytes[10]) >>> 0;break;case 6:decode.Work_mode = "3Interrupt";decode.EXTI1_Trigger = (bytes[6] & 0x01) ? "TRUE" : "FALSE";decode.EXTI1_Status = (bytes[6] & 0x80) ? "CLOSE" : "OPEN";decode.EXTI2_Trigger = (bytes[7] & 0x10) ? "TRUE" : "FALSE";decode.EXTI2_Status = (bytes[7] & 0x01) ? "CLOSE" : "OPEN";decode.EXTI3_Trigger = (bytes[8] & 0x10) ? "TRUE" : "FALSE";decode.EXTI3_Status = (bytes[8] & 0x01) ? "CLOSE" : "OPEN";break;case 7:decode.Work_mode = "3ADC+1DS18B20";decode.ADC_CH1V = (bytes[7] << 8 | bytes[8]) / 1000;decode.ADC_CH4V = (bytes[9] << 8 | bytes[10]) / 1000;break;case 8:decode.Work_mode = "3DS18B20+2Count";decode.TempC2 = (bytes[4] == 0x7f && bytes[5] == 0xff) ? "NULL" : parseFloat(((bytes[4] << 24 >> 16 | bytes[5]) / 10).toFixed(1));decode.TempC3 = (bytes[7] == 0x7f && bytes[8] == 0xff) ? "NULL" : parseFloat(((bytes[7] << 24 >> 16 | bytes[8]) / 10).toFixed(1));decode.Count1 = (bytes[9] << 24 | bytes[10] << 16 | bytes[11] << 8 | bytes[12]) >>> 0;decode.Count2 = (bytes[13] << 24 | bytes[14] << 16 | bytes[15] << 8 | bytes[16]) >>> 0;break;}return decode;}function parsePort5(bytes) {const freq_band_map = {0x01: "EU868",0x02: "US915",0x03: "IN865",0x04: "AU915",0x05: "KZ865",0x06: "RU864",0x07: "AS923",0x08: "AS923_1",0x09: "AS923_2",0x0A: "AS923_3",0x0F: "AS923_4",0x0B: "CN470",0x0C: "EU433",0x0D: "KR920",0x0E: "MA869"};const freq_band = freq_band_map[bytes[0]] || "UNKNOWN";const sub_band = bytes[1] == 0xff ? "NULL" : bytes[1];const firm_ver = (bytes[2] & 0x0f) + '.' + (bytes[3] >> 4 & 0x0f) + '.' + (bytes[3] & 0x0f);const tdc_time = bytes[4] << 16 | bytes[5] << 8 | bytes[6];return {FIRMWARE_VERSION: firm_ver,FREQUENCY_BAND: freq_band,SUB_BAND: sub_band,TDC_sec: tdc_time};}const data = payload.find((x) => ["payload_raw", "payload", "data"].includes(x.variable));const port = payload.find((x) => ["port", "fport", "fPort"].includes(x.variable));if (data && port) {const bytes = Buffer.from(data.value, "hex");let decodedData;if (port.value == 0x02) {decodedData = Decoder(bytes, port.value);} else if (port.value == 5) {decodedData = parsePort5(bytes);}const time = data.time || new Date().toISOString();const group = data.group || `${new Date().getTime()}-${Math.random().toString(36).substring(2, 5)}`;const tagoData = Object.keys(decodedData).map(key => ({variable: key,value: decodedData[key],group,time}));payload = payload.concat(tagoData);}

Since your decoder already outputs the following:

{            "EXTI3_Trigger": "FALSE",            "ADC_CH0V": 0,            "TempC1": 25,            "EXTI2_Trigger": "FALSE",            "EXTI1_Trigger": "TRUE",            "Digital_IStatus": "L",            "EXTI2_Status": "OPEN",            "EXTI3_Status": "OPEN",            "EXTI1_Status": "OPEN",            "BatV": 3.68,            "Work_mode": "3Interrupt"        }

I’ve add the following code to map it over to the TagoIO Format:

const time = data.time || new Date().toISOString();const group = data.group || `${new Date().getTime()}-${Math.random().toString(36).substring(2, 5)}`;const tagoData = Object.keys(decodedData).map(key => ({variable: key,value: decodedData[key],group,time}));

Note that this is just one of several ways you can achieve this, you could also modify the Decoder function to just return the data already in the TagoIO Format for example.

Hope this helps! :slight_smile:

Hi Mr. Freddy!

Thank you very much for your time and detailed explanation! I will try to adapt some more decoders according to the example sent!