{"id":19700,"date":"2026-08-07T11:34:58","date_gmt":"2026-08-07T03:34:58","guid":{"rendered":"https:\/\/www.lansitec.com\/?post_type=docs&#038;p=19700"},"modified":"2026-08-07T11:35:11","modified_gmt":"2026-08-07T03:35:11","password":"","slug":"decoder-lorawan-bluetooth-proximity-gateway-ttn-chirpstack","status":"publish","type":"docs","link":"https:\/\/www.lansitec.com\/th\/docs\/decoders\/decoder-lorawan-bluetooth-proximity-gateway-ttn-chirpstack\/","title":{"rendered":"\u0e15\u0e31\u0e27\u0e16\u0e2d\u0e14\u0e23\u0e2b\u0e31\u0e2a LoRaWAN \u2013 \u0e40\u0e01\u0e15\u0e40\u0e27\u0e22\u0e4c\u0e23\u0e30\u0e22\u0e30\u0e43\u0e01\u0e25\u0e49\u0e1a\u0e25\u0e39\u0e17\u0e39\u0e18 (TTN\/Chirpstack)"},"content":{"rendered":"<div class=\"wp-block-file\"><a id=\"wp-block-file--media-e10cca47-3997-429e-a869-a438ab9ef373\" href=\"https:\/\/www.lansitec.com\/wp-content\/uploads\/2026\/08\/decode-bluetooth-proximity-gateway-ttn_chirpstackv4.js.zip\">decode-bluetooth-proximity-gateway-ttn_chirpstackv4.js<\/a><a href=\"https:\/\/www.lansitec.com\/wp-content\/uploads\/2026\/08\/decode-bluetooth-proximity-gateway-ttn_chirpstackv4.js.zip\" class=\"wp-block-file__button wp-element-button\" download aria-describedby=\"wp-block-file--media-e10cca47-3997-429e-a869-a438ab9ef373\">\u0e14\u0e32\u0e27\u0e19\u0e4c\u0e42\u0e2b\u0e25\u0e14<\/a><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">Below the full decoder code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ Decode uplink function.\n\/\/\n\/\/ Input is an object with the following fields:\n\/\/ - bytes = Byte array containing the uplink payload, e.g. &#91;255, 230, 255, 0]\n\/\/ - fPort = Uplink fPort.\n\/\/ - variables = Object containing the configured device variables.\n\/\/\n\/\/ Output must be an object with the following fields:\n\/\/ - data = Object representing the decoded payload.\n\n\/\/ Bluetooth Gateway\nfunction decodeUplink(input) {\n  \/\/ bytes\n  var bytes = input.bytes;\n  \/\/ type\n  var uplinkType = (bytes&#91;0] >> 4) &amp; 0x0f;\n\n  switch (uplinkType) {\n    case 0x01:\n      return { data: decodeRegistration(bytes) };\n\n    case 0x02:\n      return { data: decodeHeartbeat(bytes) };\n\n    case 0x03:\n      return { data: decodeDeviceReportRule(bytes) };\n\n    case 0x04:\n      return { data: decodeGNSSPosition(bytes) };\n\t  \n\t  case 0x05:\n      return { data: decodeWaterLevelDetection(bytes) };\n\n    case 0x07:\n      return { data: decodePositionBeacon(bytes) };\n\n    case 0x08:\n      return { data: decodeDeviceType1(bytes) };\n\n    case 0x09:\n      return { data: decodeDeviceType2(bytes) };\n\n    case 0x0a:\n      return { data: decodeDeviceType3(bytes) };\n\n    case 0x0b:\n      return { data: decodeOptionalConfigurationReport(bytes) };\n\n    case 0x0e:\n      return { data: decodeMultiDeviceTypeMessage(bytes) };\n\n    case 0x0f:\n      return { data: decodeAcknowledgment(bytes) };\n\n    default:\n      return null;\n  }\n}\n\n\/\/ type: 0x1 Registration\nfunction decodeRegistration(bytes) {\n  var data = {};\n  data.type = \"Registration\";\n  \/\/ adr\n  data.adr = ((bytes&#91;0] >> 3) &amp; 0x1) == 0 ? \"OFF\" : \"ON\";\n  \/\/ mode\n  data.mode = bytes&#91;0] &amp; 0x07;\n  \/\/ loRaWANBand\n  var loRaWANBandValue = bytes&#91;1];\n  if (loRaWANBandValue == 0x00) {\n    data.loRaWANBand = \"KR920\";\n  } else if (loRaWANBandValue == 0x01) {\n    data.loRaWANBand = \"AU915\";\n  } else if (loRaWANBandValue == 0x04) {\n    data.loRaWANBand = \"CN470\";\n  } else if (loRaWANBandValue == 0x08) {\n    data.loRaWANBand = \"AS923\";\n  } else if (loRaWANBandValue == 0x10) {\n    data.loRaWANBand = \"EU433\";\n  } else if (loRaWANBandValue == 0x20) {\n    data.loRaWANBand = \"EU868\";\n  } else if (loRaWANBandValue == 0x40) {\n    data.loRaWANBand = \"US915\";\n  }\n  \/\/ power\n  data.power = ((bytes&#91;2] >> 3) &amp; 0x1f) + \"dBm\";\n  \/\/ continuousBleReceiveEnable\n  data.continuousBleReceiveEnable =\n    ((bytes&#91;2] >> 1) &amp; 0x1) == 0 ? \"Disable\" : \"Enable\";\n  \/\/ dr\n  data.dr = (bytes&#91;3] >> 4) &amp; 0x0f;\n  \/\/ DeviceType\n  var DeviceTypeValue = (bytes&#91;3] >> 1)&amp; 0x0;\n  if (DeviceTypeValue == 0x00) {\n    data.DeviceType = \"Solar gateway\";\n  } else if (DeviceTypeValue == 0x01) {\n    data.DeviceType = \"Compact gateway\";\n  } else if (DeviceTypeValue == 0x02) {\n    data.DeviceType = \"SocketSync gateway\";\n  } else if (DeviceTypeValue == 0x03) {\n    data.DeviceType = \"PA solar gateway\";\n  } else if (DeviceTypeValue == 0x04) {\n    data.DeviceType = \"Macro gateway\";\n  } else if (DeviceTypeValue == 0x05) {\n    data.DeviceType = \"Macro proximity gateway\";\n  } else if (DeviceTypeValue == 0x06) {\n    data.DeviceType = \"Micro gateway\";\n  }\n  \/\/ positionReportInterval\n  data.positionReportInterval =\n    (((bytes&#91;4] &lt;&lt; 8) &amp; 0xff00) | (bytes&#91;5] &amp; 0xff)) * 5 + \"s\";\n  \/\/ heartbeatInterval\n  data.heartbeatInterval = bytes&#91;6] * 30 + \"s\";\n  \/\/ bleReceivingDuration\n  data.bleReceivingDuration = bytes&#91;7] + \"s\";\n  \/\/ networkReconnectionInterval\n  data.networkReconnectionInterval = bytes&#91;8] * 5 + \"min\";\n  return data;\n}\n\n\/\/ type: 0x2 Heartbeat\nfunction decodeHeartbeat(bytes) {\n  var data = {};\n  \/\/ type\n  data.type = \"Heartbeat\";\n  \/\/ battery\n  var batteryValue = bytes&#91;1];\n  if (batteryValue > 100) {\n    data.battery = bytes&#91;1] \/ 100 + 1.5 + \"V\";\n  } else {\n    data.battery = bytes&#91;1] + \"%\";\n  }\n  \/\/ rssi\n  data.rssi = bytes&#91;2] * -1 + \"dBm\";\n  \/\/ snr\n  data.snr = ((((bytes&#91;3] &lt;&lt; 8) &amp; 0xff00) | (bytes&#91;4] &amp; 0xff))- 65536) * 0.01 + \"dB\";\n  \/\/ version\n  data.version = ((bytes&#91;5] &lt;&lt; 8) &amp; 0xff00) | (bytes&#91;6] &amp; 0xff);\n  \/\/ chargeState\n  var chargeStateValue = bytes&#91;7] &amp; 0xff;\n  if (chargeStateValue == 0x00) {\n    data.chargeState = \"Not charging\";\n  } else if (chargeStateValue == 0x50) {\n    data.chargeState = \"Charging\";\n  } else if (chargeStateValue == 0x60) {\n    data.chargeState = \"Charging completed\";\n  }\n  return data;\n}\n\n\/\/ type: 0x3 DeviceReportRule\nfunction decodeDeviceReportRule(bytes) {\n  var data = {};\n  data.type = \"DeviceReportRule\";\n  data.deviceTypeQuantity = bytes&#91;1] &amp; 0xff;\n  data.deviceTypeId = (bytes&#91;2] >> 4) &amp; 0x0f;\n  data.filterAndDataBlockQuantity = bytes&#91;2] &amp; 0x0f;\n  var filterBlock = &#91;];\n  var dataBlock = &#91;];\n  var macBlock = &#91;];\n  var macfilterBlock = &#91;];\n  var index = 3;\n  for (let i = 0; i &lt; data.filterAndDataBlockQuantity; i++) {\n    var ruleType = bytes&#91;index++] &amp; 0xff;\n    var startAddress = bytes&#91;index++] &amp; 0xff;\n    var endAddress = bytes&#91;index++] &amp; 0xff;\n    var filter = {};\n    if (ruleType == 1) {\n      filter.ruleType = \"FilterBlock\";\n      filter.startAddress = byteToHex(startAddress);\n      filter.endAddress = byteToHex(endAddress);\n      var len = endAddress - startAddress;\n      var filterValue = \"\";\n      for (let j = 0; j &lt; len + 1; j++) {\n        filterValue += byteToHex(bytes&#91;index + j]);\n      }\n      filter.value = filterValue;\n      index = index + (endAddress - startAddress + 1);\n      filterBlock.push(filter);\n    } else if (ruleType == 2) {\n      filter.ruleType = \"DataBlock\";\n      filter.startAddress = byteToHex(startAddress);\n      filter.endAddress = byteToHex(endAddress);\n      dataBlock.push(filter);\n    } else if (ruleType == 3) {\n      filter.ruleType = \"MACBlock\";\n      filter.startAddress = byteToHex(startAddress);\n      filter.endAddress = byteToHex(endAddress);\n      macBlock.push(filter);\n    } else if (ruleType == 4) {\n      filter.ruleType = \"MACFilterBlock\";\n      filter.startAddress = byteToHex(startAddress);\n      filter.endAddress = byteToHex(endAddress);\n      var len = endAddress - startAddress;\n      var macfilterValue = \"\";\n      for (let j = 0; j &lt; len + 1; j++) {\n        macfilterValue += byteToHex(bytes&#91;index + j]);\n      }\n      filter.value = macfilterValue;\n      index = index + (endAddress - startAddress + 1);\n      macfilterBlock.push(filter);\n    }\n  }\n  data.filterBlock = filterBlock;\n  data.dataBlock = dataBlock;\n  data.macBlock = macBlock;\n  data.macfilterBlock = macfilterBlock;\n  return data;\n}\n\n\/\/ type: 0x04 GNSSPosition\nfunction decodeGNSSPosition(bytes) {\n  var data = {};\n  data.type = \"GNSSPosition\";\n  \/\/ gnssState\n  data.gnssState = (bytes&#91;0] &amp; 0x01) == 0 ? \"Success\" : \"Fail\";\n  \/\/ longitude\n  let longitude =\n    (bytes&#91;1] &lt;&lt; 24) | (bytes&#91;2] &lt;&lt; 16) | (bytes&#91;3] &lt;&lt; 8) | bytes&#91;4];\n  data.longitude = hex2float(longitude);\n  \/\/ latitude\n  let latitude =\n    (bytes&#91;5] &lt;&lt; 24) | (bytes&#91;6] &lt;&lt; 16) | (bytes&#91;7] &lt;&lt; 8) | bytes&#91;8];\n  data.latitude = hex2float(latitude);\n  \/\/ time\n  let time =\n    (bytes&#91;9] &lt;&lt; 24) | (bytes&#91;10] &lt;&lt; 16) | (bytes&#91;11] &lt;&lt; 8) | bytes&#91;12];\n  data.time = timestampToTime((time + 8 * 60 * 60) * 1000);\n\n  return data;\n}\n\n\/\/ type: 0x5 WaterLevelDetection\nfunction decodeWaterLevelDetection(bytes) {\n\tvar data = {};\n\t\/\/ type\n\tdata.type = \"WaterLevelDetection\";\n\tdata.waterLevel = (((bytes&#91;1] &lt;&lt; 8) &amp; 0xff00) | (bytes&#91;2] &amp; 0xff)) + \"mm\";\n\treturn data;\n}\n\n\/\/ type: 0x7 PositionBeacon\nfunction decodePositionBeacon(bytes) {\n  var data = {\n    type: \"PositionBeacon\",\n    number: bytes&#91;1] &amp; 0x0f,\n  };\n  var index = 1;\n  for (let i = 0; i &lt; data.number; i++) {\n    var major = ((bytes&#91;index + 1] &lt;&lt; 8) | bytes&#91;index + 2])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var minor = ((bytes&#91;index + 3] &lt;&lt; 8) | bytes&#91;index + 4])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var rssi = bytes&#91;index + 5] - 256 + \"dBm\";\n\n    data&#91;\"beacon\" + (i + 1)] = major + minor;\n    data&#91;\"rssi\" + (i + 1)] = rssi;\n\n    index = index + 5;\n  }\n\n  return data;\n}\n\n\/\/ type: 0x8 DeviceType1\nfunction decodeDeviceType1(bytes) {\n  var data = {\n    type: \"DeviceType1\",\n    number: bytes&#91;0] &amp; 0x0f,\n  };\n  var index = 1;\n  for (let i = 0; i &lt; data.number; i++) {\n    var major = ((bytes&#91;index] &lt;&lt; 8) | bytes&#91;index + 1])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var minor = ((bytes&#91;index + 2] &lt;&lt; 8) | bytes&#91;index + 3])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var rssi = bytes&#91;index + 4] - 256 + \"dBm\";\n\n    data&#91;\"beacon\" + (i + 1)] = major + minor;\n    data&#91;\"rssi\" + (i + 1)] = rssi;\n\n    index = index + 5;\n  }\n\n  return data;\n}\n\n\/\/ type: 0x9 DeviceType2\nfunction decodeDeviceType2(bytes) {\n  var data = {\n    type: \"DeviceType2\",\n    number: bytes&#91;0] &amp; 0x0f,\n  };\n  var index = 1;\n  for (let i = 0; i &lt; data.number; i++) {\n    var major = ((bytes&#91;index] &lt;&lt; 8) | bytes&#91;index + 1])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var minor = ((bytes&#91;index + 2] &lt;&lt; 8) | bytes&#91;index + 3])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var rssi = bytes&#91;index + 4] - 256 + \"dBm\";\n\n    data&#91;\"beacon\" + (i + 1)] = major + minor;\n    data&#91;\"rssi\" + (i + 1)] = rssi;\n\n    index = index + 5;\n  }\n\n  return data;\n}\n\n\/\/ type: 0xa DeviceType3\nfunction decodeDeviceType3(bytes) {\n  var data = {\n    type: \"DeviceType3\",\n    number: bytes&#91;0] &amp; 0x0f,\n  };\n  var index = 1;\n  for (let i = 0; i &lt; data.number; i++) {\n    var major = ((bytes&#91;index] &lt;&lt; 8) | bytes&#91;index + 1])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var minor = ((bytes&#91;index + 2] &lt;&lt; 8) | bytes&#91;index + 3])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var rssi = bytes&#91;index + 4] - 256 + \"dBm\";\n\n    data&#91;\"beacon\" + (i + 1)] = major + minor;\n    data&#91;\"rssi\" + (i + 1)] = rssi;\n\n    index = index + 5;\n  }\n\n  return data;\n}\n\n\/\/ type: 0xe MultiDeviceTypeMessage\nfunction decodeMultiDeviceTypeMessage(bytes) {\n  var data = {\n    type: \"MultiDeviceTypeMessage\",\n    number: bytes&#91;0] &amp; 0x0f,\n  };\n  var index = 1;\n  for (let i = 0; i &lt; data.number; i++) {\n    var index = 1 + 6 * i;\n    var deviceTypeId = bytes&#91;index];\n    var major = ((bytes&#91;index + 1] &lt;&lt; 8) | bytes&#91;index + 2])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var minor = ((bytes&#91;index + 3] &lt;&lt; 8) | bytes&#91;index + 4])\n      .toString(16)\n      .toUpperCase()\n      .padStart(4, \"0\");\n    var rssi = bytes&#91;index + 5] - 256 + \"dBm\";\n\n    data&#91;\"deviceTypeId\" + (i + 1)] = deviceTypeId;\n    data&#91;\"beacon\" + (i + 1)] = major + minor;\n    data&#91;\"rssi\" + (i + 1)] = rssi;\n\n    index = index + 6;\n  }\n\n  return data;\n}\n\n\/\/ type: 0xf Acknowledgment\nfunction decodeAcknowledgment(bytes) {\n  var data = {};\n  data.type = \"Acknowledgment\";\n  data.result = (bytes&#91;0] &amp; 0x0f) == 0 ? \"Success\" : \"Failure\";\n  data.msgId = (bytes&#91;1] &amp; 0xff).toString(16).toUpperCase();\n\n  return data;\n}\n\n\/\/ type: 0xb OptionalConfigurationReport\nfunction decodeOptionalConfigurationReport(bytes){\n  var data = {\n    type: \"OptionalConfigurationReport\",\n  };\n  var optionalParametersType = bytes&#91;1] &amp; 0xff;\n  if (optionalParametersType == 0x01) {\n    data.parametersType = \"GNSS position report interval\";\n    data.parametersValue = ((bytes&#91;2] &lt;&lt; 8) | bytes&#91;3]) * 5 + \" minutes\";\n  } \n  else if (optionalParametersType == 0x02) {\n    data.parametersType = \"Water level report interval\";\n    data.parametersValue = ((bytes&#91;2] &lt;&lt; 8) | bytes&#91;3]) * 5 + \" minutes\";\n  } \n  else if (optionalParametersType == 0x03) {\n    data.parametersType = \"Duty-cycle configuration\";\n    data.parametersValue = ((bytes&#91;2] &lt;&lt; 8) | bytes&#91;3]) * 0.1 + \"%\";\n  } \n  else if (optionalParametersType == 0x04) {\n    data.parametersType = \"Beacon reporting count configuration\";\n    data.parametersValue = (bytes&#91;2] &lt;&lt; 8) | bytes&#91;3];\n  } \n  else if (optionalParametersType == 0x0f) {\n    data.parametersType = \"Ble rssi threshold\";\n    data.parametersValue = (bytes&#91;2] - 256) + \"dBm\";\n  } \n  else if (optionalParametersType == 0x10) {\n    data.parametersType = \"Switch on\/off time\";\n    data.wakesuptime = byteToDecimalStr(bytes&#91;2]) + \":\" + byteToDecimalStr(bytes&#91;3]);\n    data.shutdowntime = byteToDecimalStr(bytes&#91;4]) + \":\" + byteToDecimalStr(bytes&#91;5]);\n  }\n\n  return data;\n}\n\nfunction byteToHex(str) {\n  return str.toString(16).toUpperCase().padStart(2, \"0\");\n}\n\nfunction byteToDecimalStr(byte) {\n  return byte.toString().padStart(2, '0');\n}\n\nfunction hex2float(num) {\n  const sign = (num >>> 31) ? -1 : 1;\n  const exponent = ((num >>> 23) &amp; 0xff);\n  const mantissa = num &amp; 0x7fffff;\n\n  if (exponent === 0) {\n    \/\/ Subnormal number\n    return sign * Math.pow(2, -126) * (mantissa \/ Math.pow(2, 23));\n  } else if (exponent === 255) {\n    if (mantissa === 0) return sign * Infinity;\n    else return NaN;\n  } else {\n    \/\/ Normalized number\n    return sign * Math.pow(2, exponent - 127) * (1 + mantissa \/ Math.pow(2, 23));\n  }\n}\n\nfunction timestampToTime(timestamp) {\n  const date = new Date(timestamp);\n  const year = date.getFullYear();\n  const month = (date.getMonth() + 1).toString().padStart(2, \"0\");\n  const day = date.getDate().toString().padStart(2, \"0\");\n  const hour = date.getHours().toString().padStart(2, \"0\");\n  const minute = date.getMinutes().toString().padStart(2, \"0\");\n  const second = date.getSeconds().toString().padStart(2, \"0\");\n  return `${year}-${month}-${day} ${hour}:${minute}:${second}`;\n}\n\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>","protected":false},"excerpt":{"rendered":"<p>Below the full decoder code:<\/p>","protected":false},"author":5,"featured_media":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"neve_meta_sidebar":"","neve_meta_container":"","neve_meta_enable_content_width":"","neve_meta_content_width":0,"neve_meta_title_alignment":"","neve_meta_author_avatar":"","neve_post_elements_order":"","neve_meta_disable_header":"","neve_meta_disable_footer":"","neve_meta_disable_title":"","footnotes":""},"doc_category":[338],"doc_tag":[],"class_list":["post-19700","docs","type-docs","status-publish","hentry","doc_category-decoders"],"year_month":"2026-08","word_count":1341,"total_views":"22","reactions":{"happy":"0","normal":"0","sad":"0"},"author_info":{"name":"\u0e44\u0e21\u0e0b\u0e35\u0e48","author_nicename":"maizi","author_url":"https:\/\/www.lansitec.com\/th\/author\/maizi\/"},"doc_category_info":[{"term_name":"Decoders","term_url":"https:\/\/www.lansitec.com\/th\/doc\/decoders\/"}],"doc_tag_info":[],"knowledge_base_info":[],"knowledge_base_slug":[],"_links":{"self":[{"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/docs\/19700","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/docs"}],"about":[{"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/types\/docs"}],"author":[{"embeddable":true,"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/users\/5"}],"replies":[{"embeddable":true,"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/comments?post=19700"}],"version-history":[{"count":1,"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/docs\/19700\/revisions"}],"predecessor-version":[{"id":19702,"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/docs\/19700\/revisions\/19702"}],"wp:attachment":[{"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/media?parent=19700"}],"wp:term":[{"taxonomy":"doc_category","embeddable":true,"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/doc_category?post=19700"},{"taxonomy":"doc_tag","embeddable":true,"href":"https:\/\/www.lansitec.com\/th\/wp-json\/wp\/v2\/doc_tag?post=19700"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}