Link Search Menu Expand Document

nke WATTECO - Flash O

The Flash’O detects the impulses of flashin LED’s from pulse meters: e.g. water, gas, electricity or energy meters.


Table of contents

  1. Specifications
  2. Documents
  3. Ordering Info
  4. Compatibility of Electricity Meters
  5. Device specific Information
    1. Handler device information
  6. Adding the Device to TTN
  7. Optional settings
    1. Change measurement interval
  8. Payload formatter

Specifications

  • Price ca. CHF 183.- (15.07.2020)
  • Up to 500 pulses/second Flash LED rate counting
  • Cable length approx. 0.5m
  • Protection: IP55
  • Power Supply: 1 battery, 3.6 V, 3600mAh, Lithium
    • Expected life time: up to 12 years with 1 telegram per day
  • Size: 84 × 82 × 85 mm

Documents


Ordering Info

  • Ordering Link
  • Product Ref 50-70-071 (FLASH’O with sensor - mounted in factory with 0.5m cable)

Compatibility of Electricity Meters

The device was successfully tested with the following products.

  • Landis & Gyr ZMD120AP (1’000 impulses per kWh)

    • Place the sensor over the LED (yellow colored)
  • Kamstrup DK-8660 Omnipower (1’000 impulses per kWh)

    • Place the sensor over the LED (yellow colored)

Device specific Information

Handler device information

  • The supplier should give you a csv file where you can find the DevEUI, AppEUI and AppKey

Adding the Device to TTN

  • Before a device can communicate via “The Things Network” we have to add it to an application.
  1. Create a new application
  2. Under Overview click (+) Register device
  3. Under Input method select Select the end device in the LoRaWAN Device Repository
  4. Enter the following device information
    • End device brand select nke WATTECO
    • Model select Flash'O
    • Hardware Ver. select 50-70-071 or whatever the sticker says
    • Firmware select 3.5.2 or whatever the sticker says
      • Profile (Region) select EU_863_870
  5. Under Frequency plan select Europe 863-870 Mhz (SF9 for RX2 - recommended)
  6. Under JoinEUI enter the App EUI
  7. Enter as well the DevEUI and the AppKey
  8. Set an end-device name
  9. Press Register end device
  10. Now we have to reset the device manually by switching the small button under the cover from Off to On
  11. The device should log in and you should see a green circle as Status in the tab Device Overview.
    • if not, please wait several hours and check again. The first attempt might take a while.
  • Now the device should join the network and you can see the incoming telegrams in the Live data section
  • The payload formatter should already be preset. If not, you can copy/paste it from below

Optional settings

Change measurement interval

  • The Flash’O sends per default a telegram once a day with the counter value.
  • If you want to change that you have to send a telegram to the device. To do so follow the instructions below.
  • There are two properties of the Flash’O, the min sending interval in seconds and the impuls count.
  1. Select the device and change to the tab Messaging, select Downlink
  2. Change the FPort to 125
  3. Copy/paste one of the following payloads into the Payload field
    • every 15 minutes and/or every 200 impulses: 11 06 00 0f 00 04 02 23 00 00 03 84 00 00 00 c8
    • every 1 day: 11 06 00 0f 00 04 02 23 00 00 00 00 00 00 00 00
  4. Press Send
  5. In the Data tab you should now see the scheduled telegram. The next time the device is sending data, a short timeframe for the downlink-message will open and the telegram gets sent. This can be accelerated by pressing the key on the nkewattecoflasho and forcing a telegram to be sent.

Payload formatter

var ST_UNDEF = 0
var ST_BL = 1
var ST_U4 = 2
var ST_I4 = 3
var ST_U8 = 4
var ST_I8 = 5
var ST_U16 = 6
var ST_I16 = 7
var ST_U24 = 8
var ST_I24 = 9
var ST_U32 = 10
var ST_I32 = 11
var ST_FL = 12

var ST = {}
ST[ST_UNDEF] = 0
ST[ST_BL] = 1
ST[ST_U4] = 4
ST[ST_I4] = 4
ST[ST_U8] = 8
ST[ST_I8] = 8
ST[ST_U16] = 16
ST[ST_I16] = 16
ST[ST_U24] = 24
ST[ST_I24] = 24
ST[ST_U32] = 32
ST[ST_I32] = 32
ST[ST_FL] = 32

var BR_HUFF_MAX_INDEX_TABLE = 14
var NUMBER_OF_SERIES = 16

var HUFF = [
  [
    { sz: 2, lbl: 0x000 },
    { sz: 2, lbl: 0x001 },
    { sz: 2, lbl: 0x003 },
    { sz: 3, lbl: 0x005 },
    { sz: 4, lbl: 0x009 },
    { sz: 5, lbl: 0x011 },
    { sz: 6, lbl: 0x021 },
    { sz: 7, lbl: 0x041 },
    { sz: 8, lbl: 0x081 },
    { sz: 10, lbl: 0x200 },
    { sz: 11, lbl: 0x402 },
    { sz: 11, lbl: 0x403 },
    { sz: 11, lbl: 0x404 },
    { sz: 11, lbl: 0x405 },
    { sz: 11, lbl: 0x406 },
    { sz: 11, lbl: 0x407 }
  ],
  [
    { sz: 7, lbl: 0x06f },
    { sz: 5, lbl: 0x01a },
    { sz: 4, lbl: 0x00c },
    { sz: 3, lbl: 0x003 },
    { sz: 3, lbl: 0x007 },
    { sz: 2, lbl: 0x002 },
    { sz: 2, lbl: 0x000 },
    { sz: 3, lbl: 0x002 },
    { sz: 6, lbl: 0x036 },
    { sz: 9, lbl: 0x1bb },
    { sz: 9, lbl: 0x1b9 },
    { sz: 10, lbl: 0x375 },
    { sz: 10, lbl: 0x374 },
    { sz: 10, lbl: 0x370 },
    { sz: 11, lbl: 0x6e3 },
    { sz: 11, lbl: 0x6e2 }
  ],
  [
    { sz: 4, lbl: 0x009 },
    { sz: 3, lbl: 0x005 },
    { sz: 2, lbl: 0x000 },
    { sz: 2, lbl: 0x001 },
    { sz: 2, lbl: 0x003 },
    { sz: 5, lbl: 0x011 },
    { sz: 6, lbl: 0x021 },
    { sz: 7, lbl: 0x041 },
    { sz: 8, lbl: 0x081 },
    { sz: 10, lbl: 0x200 },
    { sz: 11, lbl: 0x402 },
    { sz: 11, lbl: 0x403 },
    { sz: 11, lbl: 0x404 },
    { sz: 11, lbl: 0x405 },
    { sz: 11, lbl: 0x406 },
    { sz: 11, lbl: 0x407 }
  ]
]

Math.trunc =
  Math.trunc ||
  function(x) {
    if (isNaN(x)) {
      return NaN
    }
    if (x > 0) {
      return Math.floor(x)
    }
    return Math.ceil(x)
  }

function brUncompress(tagsz, argList, hexString, batch_absolute_timestamp) {
  var out = initResult()
  var buffer = createBuffer(parseHexString(hexString))
  var flag = generateFlag(buffer.getNextSample(ST_U8))

  out.batch_counter = buffer.getNextSample(ST_U8, 3)
  buffer.getNextSample(ST_U8, 1)

  var temp = prePopulateOutput(out, buffer, argList, flag, tagsz)
  var last_timestamp = temp.last_timestamp
  var index_of_the_first_sample = temp.index_of_the_first_sample

  if (flag.hasSample) {
    last_timestamp = uncompressSamplesData(
      out,
      buffer,
      index_of_the_first_sample,
      argList,
      last_timestamp,
      flag,
      tagsz
    )
  }

  out.batch_relative_timestamp = extractTimestampFromBuffer(
    buffer,
    last_timestamp
  )
  return adaptToExpectedFormat(out, argList, batch_absolute_timestamp)
}

/////////////// Sub functions ///////////////

function initResult() {
  var series = [],
    i = 0
  while (i < NUMBER_OF_SERIES) {
    series.push({
      codingType: 0,
      codingTable: 0,
      resolution: null,
      uncompressSamples: []
    })
    i += 1
  }
  return {
    batch_counter: 0,
    batch_relative_timestamp: 0,
    series: series
  }
}

function createBuffer(byteArray) {

  function bitsBuf2HuffPattern(byteArray, index, nb_bits) {
    var sourceBitStart = index
    var sz = nb_bits - 1
    if (byteArray.length * 8 < sourceBitStart + nb_bits) {
      throw "Verify that dest buf is large enough"
    }
    var bittoread = 0
    var pattern = 0
    while (nb_bits > 0) {
      if (byteArray[sourceBitStart >> 3] & (1 << (sourceBitStart & 0x07))) {
        pattern |= 1 << (sz - bittoread)
      }
      nb_bits--
      bittoread++
      sourceBitStart++
    }
    return pattern
  }

  return {
    index: 0,
    byteArray: byteArray,
    getNextSample: function(sampleType, nbBitsInput) {
      var nbBits = nbBitsInput || ST[sampleType]
      var sourceBitStart = this.index
      this.index += nbBits
      if (sampleType === ST_FL && nbBits !== 32) {
        throw "Mauvais sampletype"
      }

      var u32 = 0
      var nbytes = Math.trunc((nbBits - 1) / 8) + 1
      var nbitsfrombyte = nbBits % 8
      if (nbitsfrombyte === 0 && nbytes > 0) {
        nbitsfrombyte = 8
      }

      while (nbytes > 0) {
        var bittoread = 0
        while (nbitsfrombyte > 0) {
          var idx = sourceBitStart >> 3
          if (this.byteArray[idx] & (1 << (sourceBitStart & 0x07))) {
            u32 |= 1 << ((nbytes - 1) * 8 + bittoread)
          }
          nbitsfrombyte--
          bittoread++
          sourceBitStart += 1
        }
        nbytes--
        nbitsfrombyte = 8
      }
      // Propagate the sign bit if 1
      if (
        (sampleType == ST_I4 || sampleType == ST_I8 ||sampleType == ST_I16 || sampleType == ST_I24) &&
        u32 & (1 << (nbBits - 1))
      ) {
        for (var i = nbBits; i < 32; i++) {
          u32 |= 1 << i
          nbBits++
        }
      }
      return u32
    },

    /**
     * Extract sz and bi from Huff table
     */
    getNextBifromHi: function(huff_coding) {
      for (var i = 2; i < 12; i++) {
        var lhuff = bitsBuf2HuffPattern(this.byteArray, this.index, i)
        for (var j = 0; j < HUFF[huff_coding].length; j++) {
          if (
            HUFF[huff_coding][j].sz == i &&
            lhuff == HUFF[huff_coding][j].lbl
          ) {
            this.index += i
            return j
          }
        }
      }
      throw "Bi not found in HUFF table"
    }
  }
}

function parseHexString(str) {
  str = str
    .split("")
    .filter(function(x) {
      return !isNaN(parseInt(x, 16))
    })
    .join("")
  var result = []
  while (str.length >= 2) {
    result.push(parseInt(str.substring(0, 2), 16))
    str = str.substring(2, str.length)
  }
  return result
}

function generateFlag(flagAsInt) {
  var binbase = flagAsInt.toString(2)

  // leftpad
  while (binbase.length < 8) {
    binbase = "0" + binbase
  }

  return {
    isCommonTimestamp: parseInt(binbase[binbase.length - 2], 2),
    hasSample: !parseInt(binbase[binbase.length - 3], 2),
    batch_req: parseInt(binbase[binbase.length - 4], 2),
    nb_of_type_measure: parseInt(binbase.substring(0, 4), 2)
  }
}

function prePopulateOutput(out, buffer, argList, flag, tagsz) {
  var currentTimestamp = 0
  var index_of_the_first_sample = 0
  for (var i = 0; i < flag.nb_of_type_measure; i++) {
    var tag = {
      size: tagsz,
      lbl: buffer.getNextSample(ST_U8, tagsz)
    }
    var sampleIndex = findIndexFromArgList(argList, tag)

    if (i == 0) {
      index_of_the_first_sample = sampleIndex
    }

    currentTimestamp = extractTimestampFromBuffer(buffer, currentTimestamp)
    out.series[sampleIndex] = computeSeries(
      buffer,
      argList[sampleIndex].sampletype,
      tag.lbl,
      currentTimestamp
    )
    if (flag.hasSample) {
      out.series[sampleIndex].codingType = buffer.getNextSample(ST_U8, 2)
      out.series[sampleIndex].codingTable = buffer.getNextSample(ST_U8, 2)
    }
  }
  return {
    last_timestamp: currentTimestamp,
    index_of_the_first_sample: index_of_the_first_sample
  }
}

function computeSeries(buffer, sampletype, label, currentTimestamp) {
  return {
    uncompressSamples: [
      {
        data_relative_timestamp: currentTimestamp,
        data: {
          value: getMeasure(buffer, sampletype),
          label: label
        }
      }
    ],
    codingType: 0,
    codingTable: 0,
    resolution: null
  }
}

function findIndexFromArgList(argList, tag) {
  for (var i = 0; i < argList.length; i++) {
    if (argList[i].taglbl === tag.lbl) {
      return i
    }
  }
  throw "Cannot find index in argList"
}

function extractTimestampFromBuffer(buffer, baseTimestamp) {
  if (baseTimestamp) {
    var bi = buffer.getNextBifromHi(1)
    return computeTimestampFromBi(buffer, baseTimestamp, bi)
  }
  return buffer.getNextSample(ST_U32)
}

function computeTimestampFromBi(buffer, baseTimestamp, bi) {
  if (bi > BR_HUFF_MAX_INDEX_TABLE) {
    return buffer.getNextSample(ST_U32)
  }
  if (bi > 0) {
    return computeTimestampFromPositiveBi(buffer, baseTimestamp, bi)
  }
  return baseTimestamp
}

function computeTimestampFromPositiveBi(buffer, baseTimestamp, bi) {
  return buffer.getNextSample(ST_U32, bi) + baseTimestamp + Math.pow(2, bi) - 1
}

function getMeasure(buffer, sampletype) {
  var v = buffer.getNextSample(sampletype)
  return sampletype === ST_FL ? bytes2Float32(v) : v
}

function bytes2Float32(bytes) {
  var sign = bytes & 0x80000000 ? -1 : 1,
    exponent = ((bytes >> 23) & 0xff) - 127,
    significand = bytes & ~(-1 << 23)

  if (exponent == 128) {
    return sign * (significand ? Number.NaN : Number.POSITIVE_INFINITY)
  }

  if (exponent == -127) {
    if (significand == 0) {
      return sign * 0.0
    }
    exponent = -126
    significand /= 1 << 22
  } else {
    significand = (significand | (1 << 23)) / (1 << 23)
  }

  return sign * significand * Math.pow(2, exponent)
}

function uncompressSamplesData(
  out,
  buffer,
  index_of_the_first_sample,
  argList,
  last_timestamp,
  flag,
  tagsz
) {
  if (flag.isCommonTimestamp) {
    return handleCommonTimestamp(
      out,
      buffer,
      index_of_the_first_sample,
      argList,
      flag,
      tagsz
    )
  }
  return handleSeparateTimestamp(
    out,
    buffer,
    argList,
    last_timestamp,
    flag,
    tagsz
  )
}

function handleCommonTimestamp(
  out,
  buffer,
  index_of_the_first_sample,
  argList,
  flag,
  tagsz
) {
  //number of sample
  var nb_sample_to_parse = buffer.getNextSample(ST_U8, 8)
  var tag = {}

  var temp = initTimestampCommonTable(
    out,
    buffer,
    nb_sample_to_parse,
    index_of_the_first_sample
  )
  var timestampCommon = temp.timestampCommon
  var lastTimestamp = temp.lastTimestamp

  for (var j = 0; j < flag.nb_of_type_measure; j++) {
    var first_null_delta_value = 1
    tag.lbl = buffer.getNextSample(ST_U8, tagsz)
    var sampleIndex = findIndexFromArgList(argList, tag)
    for (var i = 0; i < nb_sample_to_parse; i++) {
      //Available bit
      var available = buffer.getNextSample(ST_U8, 1)
      if (available) {
        //Delta value
        var bi = buffer.getNextBifromHi(out.series[sampleIndex].codingTable)
        var currentMeasure = {
          data_relative_timestamp: 0,
          data: {}
        }
        if (bi <= BR_HUFF_MAX_INDEX_TABLE) {
          var precedingValue =
            out.series[sampleIndex].uncompressSamples[
              out.series[sampleIndex].uncompressSamples.length - 1
            ].data.value
          if (bi > 0) {
            currentMeasure.data.value = completeCurrentMeasure(
              buffer,
              precedingValue,
              out.series[sampleIndex].codingType,
              argList[sampleIndex].resol,
              bi
            )
          } else {
            // (bi <= 0)
            if (first_null_delta_value) {
              // First value is yet recorded starting from the header
              first_null_delta_value = 0
              continue
            } else {
              currentMeasure.data.value = precedingValue
            }
          }
        } else {
          // bi > BR_HUFF_MAX_INDEX_TABLE
          currentMeasure.data.value = buffer.getNextSample(
            argList[sampleIndex].sampletype
          )
        }
        currentMeasure.data_relative_timestamp = timestampCommon[i]
        out.series[sampleIndex].uncompressSamples.push(currentMeasure)
      }
    }
  }
  return lastTimestamp
}

function initTimestampCommonTable(
  out,
  buffer,
  nbSampleToParse,
  firstSampleIndex
) {
  var timestampCommon = []
  var lastTimestamp = 0
  var timestampCoding = buffer.getNextSample(ST_U8, 2)
  for (var i = 0; i < nbSampleToParse; i++) {
    //delta timestamp
    var bi = buffer.getNextBifromHi(timestampCoding)
    if (bi <= BR_HUFF_MAX_INDEX_TABLE) {
      if (i == 0) {
        timestampCommon.push(
          out.series[firstSampleIndex].uncompressSamples[0]
            .data_relative_timestamp
        )
      } else {
        if (bi > 0) {
          var precedingTimestamp = timestampCommon[i - 1]
          timestampCommon.push(
            buffer.getNextSample(ST_U32, bi) +
              precedingTimestamp +
              Math.pow(2, bi) -
              1
          )
        } else {
          timestampCommon.push(precedingTimestamp)
        }
      }
    } else {
      timestampCommon.push(buffer.getNextSample(ST_U32))
    }
    lastTimestamp = timestampCommon[i]
  }
  return {
    timestampCommon: timestampCommon,
    lastTimestamp: lastTimestamp
  }
}

function completeCurrentMeasure(buffer, precedingValue, codingType, resol, bi) {
  var currentValue = buffer.getNextSample(ST_U16, bi)
  if (codingType === 0) {
    // ADLC
    return computeAdlcValue(currentValue, resol, precedingValue, bi)
  }
  if (codingType === 1) {
    // Positive
    return (currentValue + Math.pow(2, bi) - 1) * resol + precedingValue
  }
  // Negative
  return precedingValue - (currentValue + (Math.pow(2, bi) - 1)) * resol
}

/**
 * Return current value in ADLC case
 */
function computeAdlcValue(currentValue, resol, precedingValue, bi) {
  if (currentValue >= Math.pow(2, bi - 1)) {
    return currentValue * resol + precedingValue
  }
  return (currentValue + 1 - Math.pow(2, bi)) * resol + precedingValue
}

function handleSeparateTimestamp(
  out,
  buffer,
  argList,
  last_timestamp,
  flag,
  tagsz
) {
  var tag = {}
  for (var i = 0; i < flag.nb_of_type_measure; i++) {
    tag.lbl = buffer.getNextSample(ST_U8, tagsz)
    var sampleIndex = findIndexFromArgList(argList, tag)
    var compressSampleNb = buffer.getNextSample(ST_U8, 8)
    if (compressSampleNb) {
      var timestampCoding = buffer.getNextSample(ST_U8, 2)
      for (var j = 0; j < compressSampleNb; j++) {
        var precedingRelativeTimestamp =
          out.series[sampleIndex].uncompressSamples[
            out.series[sampleIndex].uncompressSamples.length - 1
          ].data_relative_timestamp
        var currentMeasure = {
          data_relative_timestamp: 0,
          data: {}
        }
        var bi = buffer.getNextBifromHi(timestampCoding)
        currentMeasure.data_relative_timestamp = computeTimestampFromBi(
          buffer,
          precedingRelativeTimestamp,
          bi
        )
        if (currentMeasure.data_relative_timestamp > last_timestamp) {
          last_timestamp = currentMeasure.data_relative_timestamp
        }
        bi = buffer.getNextBifromHi(out.series[sampleIndex].codingTable)
        if (bi <= BR_HUFF_MAX_INDEX_TABLE) {
          var precedingValue =
            out.series[sampleIndex].uncompressSamples[
              out.series[sampleIndex].uncompressSamples.length - 1
            ].data.value
          if (bi > 0) {
            currentMeasure.data.value = completeCurrentMeasure(
              buffer,
              precedingValue,
              out.series[sampleIndex].codingType,
              argList[sampleIndex].resol,
              bi
            )
          } else {
            // bi <= 0
            currentMeasure.data.value = precedingValue
          }
        } else {
          // bi > BR_HUFF_MAX_INDEX_TABLE
          currentMeasure.data.value = buffer.getNextSample(
            argList[sampleIndex].sampletype
          )
        }
        out.series[sampleIndex].uncompressSamples.push(currentMeasure)
      }
    }
  }
  return last_timestamp
}

function adaptToExpectedFormat(out, argList, batchAbsoluteTimestamp) {
  var returnedGlobalObject = {
    //batch_counter: out.batch_counter,
    //batch_relative_timestamp: out.batch_relative_timestamp
  }
  if (batchAbsoluteTimestamp) {
    returnedGlobalObject.b_ts = batchAbsoluteTimestamp
  }
  returnedGlobalObject.datas = out.series.reduce(function(
    acc,
    current,
    index
  ) {
    return acc.concat(
      current.uncompressSamples.map(function(item) {
        var returned = {
          //data_relative_timestamp: item.data_relative_timestamp,
          data: {
            value: argList[index].divide
              ? item.data.value / argList[index].divide
              : item.data.value,

          }
        }
        if (argList[index].lblname) {
          returned.data.label = argList[index].lblname
        }
        if (batchAbsoluteTimestamp) {
          returned.date = computeDataAbsoluteTimestamp(
            batchAbsoluteTimestamp,
            out.batch_relative_timestamp,
            item.data_relative_timestamp
          )
        }
        return returned
      })
    )
  },
  [])
  return returnedGlobalObject
}

function computeDataAbsoluteTimestamp(bat, brt, drt) {
  return new Date(new Date(bat) - (brt - drt) * 1000).toISOString()
}

try {
  module.exports = brUncompress
} catch (e) {
  // when called from nashorn,  module.exports is unavailable…
}


function UintToInt(Uint, Size) {
  if (Size === 2) {
    if ((Uint & 0x8000) > 0) {
      Uint = Uint - 0x10000;
    }
  }
  if (Size === 3) {
    if ((Uint & 0x800000) > 0) {
      Uint = Uint - 0x1000000;
    }
  }
  if (Size === 4) {
    if ((Uint & 0x80000000) > 0) {
      Uint = Uint - 0x100000000;
    }
  }
  return Uint;
}



function decimalToHex(d, padding) {
  var hex = Number(d).toString(16).toUpperCase();
  padding = typeof (padding) === "undefined" || padding === null ? padding = 2 : padding;

  while (hex.length < padding) {
      hex = "0" + hex;
  }

  return "0x" + hex;
}

function Bytes2Float32(bytes) {

  var sign = (bytes & 0x80000000) ? -1 : 1;
  var exponent = ((bytes >> 23) & 0xFF) - 127;
  var significand = (bytes & ~(-1 << 23));
  if (exponent == 128)
    return sign * ((significand) ? Number.NaN : Number.POSITIVE_INFINITY);

  if (exponent == -127) {
    if (significand == 0) return sign * 0.0;
      exponent = -126;
    significand /= (1 << 22);
  } 
  else significand = (significand | (1 << 23)) / (1 << 23);

  return sign * significand * Math.pow(2, exponent);
}


function Decoder(bytes, port) {

  // Decode an uplink message from a buffer
  // (array) of bytes to an object of fields.
  var decoded = {};

  var decodedBatch = {};

  var lora = {};

  // decoded.lora.port  = port;
    
  // Get raw payload
  var bytes_len_ = bytes.length;
  var temp_hex_str = ""

  lora.payload  = "";




  for( var j = 0; j < bytes_len_; j++ ){
    temp_hex_str   = bytes[j].toString( 16 ).toUpperCase( );
    if( temp_hex_str.length == 1 ){
      temp_hex_str = "0" + temp_hex_str;
    }
    lora.payload += temp_hex_str;
    }

    var date = new Date();
    var lDate = date.toISOString();
    
    if (port === 125){
        //batch
        decodedBatch = !(bytes[0] & 0x01);
    
        //trame standard
        if (decodedBatch === false){

            decoded.zclheader = {};
            decoded.zclheader.report =  "standard";
            attributID = -1;
            cmdID = -1;
            clusterdID = -1;
            //endpoint
            decoded.zclheader.endpoint = ((bytes[0]&0xE0)>>5) | ((bytes[0]&0x06)<<2);
            //command ID
            cmdID =  bytes[1]; decoded.zclheader.cmdID = decimalToHex(cmdID,2);
            //Cluster ID
            clusterdID = bytes[2]*256 + bytes[3]; decoded.zclheader.clusterdID = decimalToHex(clusterdID,4);
            
        
            // decode report and read atrtribut response
            if((cmdID === 0x0a)|(cmdID === 0x8a)|(cmdID === 0x01)){

                stdData = {};
                var tab=[];

                //Attribut ID
                attributID = bytes[4]*256 + bytes[5]; decoded.zclheader.attributID = decimalToHex(attributID,4);

                if (cmdID === 0x8a) {
                    decoded.zclheader.alarm = 1;
                }
                else {
                    decoded.zclheader.alarm = 0;
                }
                    
                //data index start
                if ((cmdID === 0x0a) | (cmdID === 0x8a)) index = 7;
                // if (cmdID === 0x01) {index = 8; decoded.zclheader.status = bytes[6];}


                //binary input counter
                if (  (clusterdID === 0x000f ) & (attributID === 0x0402)) {
                    stdData.label = "Index1";
                    stdData.value = (bytes[index]*256*256*256+bytes[index+1]*256*256+bytes[index+2]*256+bytes[index+3]); 
                    stdData.date = lDate;
                    tab.push(stdData);
                };
            
                // binary input present value
                if (  (clusterdID === 0x000f ) & (attributID === 0x0055)) {
                    // if (decoded.zclheader.endpoint < 3){
                    //   stdData.label = "Index"+(decoded.zclheader.endpoint+1) ;  
                    // }
        
                    // if ((decoded.zclheader.endpoint >= 3)&&(decoded.zclheader.endpoint < 6)){
                    //   stdData.label = "State"+(decoded.zclheader.endpoint-2) ;
                    // }
                    stdData.label = "Index1";
                    stdData.value =bytes[index]; 
                    stdData.date = lDate;
                    tab.push(stdData);
                };


                // lorawan message type
                if (  (clusterdID === 0x8004 ) & (attributID === 0x0000)) {
                    if (bytes[index] === 1)
                        stdData.message_type = "confirmed";
                    if (bytes[index] === 0)
                        stdData.message_type = "unconfirmed";
                }
                    
                // lorawan retry
                if (  (clusterdID === 0x8004 ) & (attributID === 0x0001)) {
                    stdData.nb_retry= bytes[index] ;
                }
                    
                // lorawan reassociation
                if (  (clusterdID === 0x8004 ) & (attributID === 0x0002)) {
                    stdData.period_in_minutes = bytes[index+1] *256+bytes[index+2];
                    stdData.nb_err_frames = bytes[index+3] *256+bytes[index+4];
                }

                // configuration node power desc

                if (   (clusterdID === 0x0050 ) & (attributID === 0x0006)) {
                    index2 = index + 3;
                    if ((bytes[index+2] &0x01) === 0x01) {
                        tab.push({label:"ExternalPowerVoltage" ,value:(bytes[index2]*256+bytes[index2+1])/1000, date:lDate}) ;
                        index2=index2+2;
                    }
                    if ((bytes[index+2] &0x04) === 0x04) {
                        tab.push({label:"BatteryVoltage" ,value:(bytes[index2]*256+bytes[index2+1])/1000, date:lDate}) ;
                        index2=index2+2;
                    }
                    if ((bytes[index+2] &0x02) === 0x02) {decoded.data.rechargeable_battery_voltage = (bytes[index2]*256+bytes[index2+1])/1000;index2=index2+2;}
                    if ((bytes[index+2] &0x08) === 0x08) {decoded.data.solar_harvesting_voltage = (bytes[index2]*256+bytes[index2+1])/1000;index2=index2+2;}
                    if ((bytes[index+2] &0x10) === 0x10) {decoded.data.tic_harvesting_voltage = (bytes[index2]*256+bytes[index2+1])/1000;index2=index2+2;}
                }
                decoded.data = tab;
            }
            
            // decode configuration response
            if(cmdID === 0x07){
                //AttributID
                attributID = bytes[6]*256 + bytes[7];decoded.zclheader.attributID = decimalToHex(attributID,4);
                //status
                decoded.zclheader.status = bytes[4];
                //batch
                decoded.zclheader.decodedBatch = bytes[5];
            }

            //decode read configuration response
            if(cmdID === 0x09){
                //AttributID
                attributID = bytes[6]*256 + bytes[7];decoded.zclheader.attributID = decimalToHex(attributID,4);
                //status
                decoded.zclheader.status = bytes[4];
                //batch
                decoded.zclheader.decodedBatch = bytes[5];
                //AttributType
                decoded.zclheader.attribut_type = bytes[8];
                //min
                decoded.zclheader.min = {}
                if ((bytes[9] & 0x80) === 0x80) {
                    decoded.zclheader.min.value = (bytes[9]-0x80)*256+bytes[10];
                    decoded.zclheader.min.unity = "minutes";
                } 
                else {
                    decoded.zclheader.min.value = bytes[9]*256+bytes[10];
                    decoded.zclheader.min.unity = "seconds";
                }
                //max
                decoded.zclheader.max = {}
                if ((bytes[9] & 0x80) === 0x80) {
                    decoded.zclheader.max.value = (bytes[9]-0x80)*256+bytes[10];
                    decoded.zclheader.max.unity = "minutes";
                } 
                else {
                    decoded.zclheader.max.value = bytes[9]*256+bytes[10];
                    decoded.zclheader.max.unity = "seconds";
                }

            }   
        }

        else{

            var decoded = {};
            brData = (brUncompress(1,[{taglbl: 0,resol: 1, sampletype: 10,lblname: "Index", divide: 1},{ taglbl: 1, resol: 100, sampletype: 6,lblname: "BatteryVoltage", divide: 1000}], lora.payload, lDate))

            var data_length = brData["datas"].length;
            var tab=[];
            for (var i = 0; i < data_length; i++) {               
                tab.push({label:brData["datas"][i]["data"]["label"] ,value:brData["datas"][i]["data"]["value"], date:brData["datas"][i]["date"]}) ;
            }

            decoded.data = tab;

            decoded.zclheader = {};
            decoded.zclheader.report = "batch";
        }

    }
  return decoded;
}


function decodeUplink(input) {
 
  return {
    data : Decoder(input.bytes, input.fPort),
    
    warnings: [],
    errors: []
  };
}