Downlink to TTN/TTI and TTS

Dear Team,

I have sent a downlink from TagoIO to TTN/TTI and TTS. The downlinks are reaching the LNS perfectly.

  1. Downlink data in Analysis
    image
  2. Downlink data reaches the TTS

But, when the downlink is executed i am getting a downlink with port 0 auto generated with the some value.

And i have sent the downlink(DL) mentioning that “confirmed”: true, but in TTS that variable is missing

  1. DL data in analysis
    image

  2. DL data in TTS
    image

Can any one help me on the above points.

Thank you

I was checking and the “confirmed” parameter wasn’t available for both TTI and TTN thorugh TagoIO downlinks. I already requested for the support to be added, I’ll get back to this post once it is available.

1 Like

Thank you vitor. Now i am getting the confirmed parameter in my TTI account.

I am facing another problem when sending the downlink from TagoIO.

I am supposed to send the downlink using parameter called “frm_payload” to the TTI, but when i am using it my values are not reaching the TTI.

Here is my analysis code for your reference.

data = {
    device: token.serie_number,
    authorization: token.last_authorization,
    frm_payload: dl,
    port: (port.value),
    priority:"NORMAL",
    confirmed:true
    }
  context.log(data);
  await axios.post(`https://${network.middleware_endpoint}/downlink`, data)
   .then((result) => {
     context.log(`Downlink accepted with status ${result.status}`);
   })
   .catch((error) => {
     context.log(`Downlink failed with status ${error.response.status}`);
     context.log(error.response.data || JSON.stringify(error));
   });

and the output on my analysis console is as follows:

[2021-06-24 10:27:13] Downlink accepted with status 200
[2021-06-24 10:27:13] {"device":"0004a30b00f7595a","authorization":"at1e8431b4c6d64a95be634fd63223967e","frm_payload":"AAEAAg==","port":"11","priority":"NORMAL","confirmed":true}
[2021-06-24 10:27:13] Trying to send the downlink
[2021-06-24 10:27:12] Downlink analysis started
[2021-06-24 10:27:12] Starting analysis 60cf7d816dc8af00199e16cf

And the values which i got in my TTI console is as follows:

"data": {
    "@type": "type.googleapis.com/ttn.lorawan.v3.ApplicationDownlink",
    "f_port": 11,
    "confirmed": true,
    "priority": "NORMAL",
    "correlation_ids": [
      "as:downlink:01F8YB6YWB59C685MZM8KV7N2W"
    ]

If you see the above output console of TTI, the frm_payload is not received whereas in TagoIO analysis console its shows that the DL is sent successfully.

Why does the “frm_payload” parameter does not reaches the TTI?
Can you help me on this?

Thank you.

1 Like

You can’t send frm_payload to the downlink service. You need to send a hexadecimal in the payload parameter:

The Buffer.from() can convert base64 to hexadecimal if you want to use the code you have.

data = {
    device: token.serie_number,
    authorization: token.last_authorization,
    payload: Buffer.from(dl, 'base64').toString('hex'),
    port: Number(port.value),
    priority:"NORMAL",
    confirmed:true
    }
  context.log(data);
  await axios.post(`https://${network.middleware_endpoint}/downlink`, data)
   .then((result) => {
     context.log(`Downlink accepted with status ${result.status}`);
   })
   .catch((error) => {
     context.log(`Downlink failed with status ${error.response.status}`);
     context.log(error.response.data || JSON.stringify(error));
   });
1 Like

Hi vitor,

Thank you for your reply.

If i send the parameter “payload” the downlink send gets executed using my encoder, which i do not want.

I want to send the raw value directly without executing the encoder script.

Is there any way for it?

1 Like

I don’t think so. With the script from TagoIO that you’re using, you are sending the downlink cmd to TagoIO service, which then communicatse with TTN for you. As this is made to work with any network, it will only work with the payload parameter.

If you need to do someting extra, you may need to change the URL to your Tektelic downlink URL and integrate with TTN directly.
There is an example with Chirpstack here: Manage downlinks to ChirpStack - Analysis - TagoIO Community

1 Like

For TTN its working fine, but for TTI its not woking correctly.

I have sent the DL from my widget and it triggers the analysis.

Below is the Data received in the console of analysis.

[2021-06-29 20:51:17] Downlink accepted with status 200
[2021-06-29 20:51:17] {"device":"0004a30b00f7595a","authorization":"at1e8431b4c6d64a95be634fd63223967e","port":16,"payload":"FFA050FF2100FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF","priority":"NORMAL","confirmed":true}
[2021-06-29 20:51:17] Trying to send the downlink
[2021-06-29 20:51:17] Downlink analysis started
[2021-06-29 20:51:17] Starting analysis 60cf7d816dc8af00199e16cf

And below is the data received in TTI

As per the above image the data is sent on FPort 16 and when the FPort 16 gets executed, it automatically generates another downlink on FPort 1, which changes my sensor actuator values.

This is the issue i am facing in TTI, when i send the downlink using parameter “payload”.

If i try with frm_payload the DL with only one bytes (example 30 or 31) reaches the TTI, whereas the remaining DL such as DL on the above images is not reaching TTI. It sends a blank frame with that FPort.

1 Like

Hi vitor,

Here is the error which i am getting in the analysis console, what does this represents?

{"code":16,"message":"error:pkg/rpcmetadata:unauthenticated (the context is not authenticated)","details":[{"@type":"type.googleapis.com/ttn.lorawan.v3.ErrorDetails","namespace":"pkg/rpcmetadata","name":"unauthenticated","message_format":"the context is not authenticated","code":16}]}
[2021-06-29 22:01:39] Downlink failed with status 401
[2021-06-29 22:01:39] Trying to send the downlink
[2021-06-29 22:01:39] {"method":"POST","url":"https://strega.eu1.cloud.thethings.industries/api/v3/as/applications/brunata-colibird/webhooks/test-tagoio-tti/devices/strega-demo-se-sn7041/down/push","headers":{"Content-Type":"application/json","Grpc-Metadata-Authorization":"Bearer NNSXS.YGG6DAS4IUGYUFS3OGXLFZHJEMTVV5EGEKSAHLQ.HHFB4JEB3YIHDR6ZEAGCVQD3GIPD3HZUNBCLFEITZLBKQXQ5F6NQ"},"data":{"end_device_ids":{"device_id":"strega-demo-se-sn7041","application_ids":{"application_id":"brunata-colibird"}},"downlinks":[{"f_port":1,"frm_payload":"MQ==","priority":"NORMAL","confirmed":true}]}}
[2021-06-29 22:01:38] Downlink analysis started
[2021-06-29 22:01:38] Starting analysis 60cf7d816dc8af00199e16cf
1 Like

Since you’re sending directly to TTI, you will need to check this error on TTI forum/documentation.

Regardig the other downlink to fport 1 when using TagoIO method, I did check this and already fixed the issue. Can you check again and see if its fixed for you ?

1 Like

Hi Vitor,

Sorry for the delayed reply.

Thank you for very much for your support.

Now i am not getting the automatic reply on fport 1 and my downlinks are working perfectly.

Thank you.

1 Like

Hi Vitor,

Good day!

From few days before, my analysis is not sending any downlink to TTI/TTS. The code which i used is same which it was working fine till last week. I have updated the dashboard to the new version.

Here is the code which i used for sending DL

const { Analysis, Account, Utils } = require('@tago-io/sdk');
const axios        = require('axios');
let dl;let Schl_on = 10;let Schl_off = 00;let Schl_m = 0; let unit__1; let unit__2; let time__1; let time__2;
let schl1h_on;let schl1m_on;let schl1h_off;let schl1m_off;let schl2h_on;let schl2m_on;let schl2h_off;let schl2m_off;
let schl3h_on;let schl3m_on;let schl3h_off;let schl3m_off;let schl4h_on;let schl4m_on;let schl4h_off;let schl4m_off;
let schl1_start;let schl1_end;let schl2_start; let schl2_end;let schl3_start; let schl3_end;let schl4_start; let schl4_end;
async function init(context, scope) {
  if (!scope[0]) return context.log('This analysis must be triggered by a widget.');
  context.log('Downlink analysis started');
  // Get the environment variables.
  const env = Utils.envToJson(context.environment);
  if (!env.account_token) return context.log('Missing "account_token" environment variable');
  else if (env.account_token.length !== 36) return context.log('Invalid "account_token" in the environment variable');
  // Instance the Account class
  const account = new Account({ token: env.account_token });
  // Get the variables form_payload and form_port sent by the widget/dashboard.
  const payload          = scope.find(x => x.variable === 'form_payload')   || {value: env.payload, origin: env.device_id};
  const port             = scope.find(x => x.variable === 'form_port')      || {value: env.default_PORT} || {value: env.default_port};
  const payload_fields   = scope.find(x => x.variable === 'payload_fields') || {value: env.payload_fields};
  const scheduler1_start = scope.find(x => x.variable === 'schl1_start')    || {value: env.scheduler1_start};
  const scheduler1_end   = scope.find(x => x.variable === 'schl1_end')      || {value: env.scheduler1_end};
  const scheduler2_start = scope.find(x => x.variable === 'schl2_start')    || {value: env.scheduler2_start};
  const scheduler2_end   = scope.find(x => x.variable === 'schl2_end')      || {value: env.scheduler2_end};
  const scheduler3_start = scope.find(x => x.variable === 'schl3_start')    || {value: env.scheduler3_start};
  const scheduler3_end   = scope.find(x => x.variable === 'schl3_end')      || {value: env.scheduler3_end};
  const scheduler4_start = scope.find(x => x.variable === 'schl4_start')    || {value: env.scheduler4_start};
  const scheduler4_end   = scope.find(x => x.variable === 'schl4_end')      || {value: env.scheduler4_end};
  const unit1            = scope.find(x => x.variable === 'unit_1')          || {value: env.unit1};
  const time1            = scope.find(x => x.variable === 'time_1')          || {value: env.time1};
  if (!payload || !payload.value || !payload.origin) return context.log('Missing "form_payload" in the data scope.');
  else if (!port || !port.value) return context.log('Missing "form_port" in the data scope.');
  const device_id = payload.origin; // All variables that trigger the analysis have the "origin" parameter, with the TagoIO Device ID.
  if (!device_id) return context.log('Device ID <origin> not found in the variables sent by the widget/dashboard.');
  // Find the token containing the authorization code used.
  const device_tokens = await account.devices.tokenList(device_id, { page: 1, fields: ['name', 'serie_number', 'last_authorization'], amount: 10 });
  const token = device_tokens.find(x => x.serie_number && x.last_authorization);
  if (!token) return context.log("Couldn't find a token with serial/authorization for this device");
  // Get the connector ID from the device
  const { network: network_id } = await account.devices.info(device_id);
  if (!network_id) return context.log('Device is not using a network.');
  // Get the network information with the NS URL for the Downlink
  const network = await account.integration.networks.info(network_id, ['id', 'middleware_endpoint', "name"]);
  if (!network.middleware_endpoint) return context.log("Couldn't find a network middleware for this device.");
  // Set the parameters for the device. Some NS like Everynet need this.
  const params = await account.devices.paramList(device_id);
  let downlink_param = params.find(x => x.key === 'downlink');
  downlink_param = { id: downlink_param ? downlink_param.id : null, key: 'downlink', value: String(payload.value), sent: false };
  await account.devices.paramSet(device_id, downlink_param);
  function pad(num, len) {
   return ("00" + num).substr(-len);}
  function pad1(num, len) {
   return ("000" + num).substr(-len);}
  function pad2(num, len) {
   return ("0000" + num).substr(-len);}
  function pad6(num, len) {
   return ("000000" + num).substr(-len);}
  function decimalToHexString(number){
    if (number < 0){number = 0xFFFFFFFF + number + 1;}
    return number.toString(16).toUpperCase();}
  function dec_to_bho (n, base) {
      if (n < 0) {n = 0xFFFFFFFF + n + 1;}
    switch (base){
    case 'B':
      return parseInt(n, 10).toString(2);
      break;
    case 'H':
      return parseInt(n, 10).toString(16);
      break;
    case 'O':
      return parseInt(n, 10).toString(8);
      break;
    default:
      return("Wrong input.........");}}
  function btoa(bin) {
    var tableStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    var table = tableStr.split("");
    for (var i = 0, j = 0, len = bin.length / 3, base64 = []; i < len; ++i) {
      var a = bin.charCodeAt(j++), b = bin.charCodeAt(j++), c = bin.charCodeAt(j++);
      if ((a | b | c) > 255) throw new Error("String contains an invalid character");
      base64[base64.length] = table[a >> 2] + table[((a << 4) & 63) | (b >> 4)] +
                              (isNaN(b) ? "=" : table[((b << 2) & 63) | (c >> 6)]) +
                              (isNaN(b + c) ? "=" : table[c & 63]);}
    return base64.join("");} 
  function hexToBase64(hexStr) {
    let base64 = "";
    for(let i = 0; i < hexStr.length; i++) {
    base64 += !(i - 1 & 1) ? String.fromCharCode(parseInt(hexStr.substring(i - 1, i + 1), 16)) : ""}
    return btoa(base64);}
  // Downlink scripts / Open & Close / Class setting / Time sync request / Schedulers status setting / Magnetic control
  // Downlink scripts / Counter value retrieval / Analog value retrieval
  if (port.value == 1 || port.value == 9 || port.value == 13 || port.value == 12  || port.value == 21 || port.value == 22 || port.value == 24 || port.value == 25 || port.value == 26 || port.value == 27 ){
     dl = (payload.value) }

  context.log('Trying to send the downlink');
   const data = {
    device: token.serie_number,
    authorization: token.last_authorization,
    payload: dl,
    port: (port.value),
    priority:"NORMAL",
    confirmed:true
    }
  context.log(data);
  await axios.post(`https://${network.middleware_endpoint}/downlink`, data)
   .then((result) => {
     context.log(`Downlink accepted with status ${result.status}`);
   })
   .catch((error) => {
     context.log(`Downlink failed with status ${error.response.status}`);
     context.log(error.response.data || JSON.stringify(error));
   });
}
module.exports = new Analysis(init);

The output in the analysis console is as follows:

[2021-07-19 11:30:31] Downlink failed with status 400
[2021-07-19 11:30:31] {}
[2021-07-19 11:30:30] {"device":"0004a30b00f77468","authorization":"atbb3058b3a3b044aab101495a3ac00f3f","payload":"31","port":"01"}
[2021-07-19 11:30:30] Trying to send the downlink
[2021-07-19 11:30:30] Downlink analysis started
[2021-07-19 11:30:30] Starting analysis 60dd218f7efc1f00183bc7b0

Can you help me on this?

Thank you

Hi Surendar,
Try to send the downlink again, you should be able to see the error message now.

I’m assuming you did remove privilegies on this integration on TTI side to perform downlinks.