<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Custom Decoders :: Neura N-Hub documentation</title>
    <link>/custom-decoders/index.html</link>
    <description>N-Hub automatically tries to parse payloads coming from LoRaWAN devices by leveraging the deviceType field associated with a device.&#xA;N-Hub will try and match the deviceType against a list of built-in device types associated with a built in decoder.&#xA;Custom decoders In addition to the mechanism described above, a custom LoraParser can be associated with a device.&#xA;Custom LoraParsers take precedence over built in decoders and are Typescript snippets of code with a structure that matches the following example:</description>
    <generator>Hugo</generator>
    <language>en</language>
    <lastBuildDate>Mon, 01 Jan 0001 00:00:00 +0000</lastBuildDate>
    <atom:link href="/custom-decoders/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Examples</title>
      <link>/custom-decoders/examples/index.html</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>/custom-decoders/examples/index.html</guid>
      <description>Here are some examples of custom decoders for a few very well known LoRaWAN devices to get you started.&#xA;Simple payload - RisingHF function parseDeviceMsg(buf:Buffer, loraMessage:LoraMessage) { if(buf.length != 9) throw new Error(`Invalid payload length. Expected exactly 9 bytes. Got ${buf.length}`); let status = buf.readUInt8(0); if(status !== 0x81 &amp;&amp; status !== 0x01) throw new Error(`Invalid payload. Status can be 0x81 = OK or 0x01 = NO_DOWNLINK. Got 0x${status.toString(16)}`); let temperature = ((buf.readInt16LE(1) * 175.72) / 65536) - 46.85; let humidity = ((buf.readUInt8(3) * 125) / 256) -6; let periodSec = buf.readInt16LE(4) * 2; let rssi = buf.readUInt8(6) - 180; let snr = buf.readUInt8(7) / 4; let vcc = (buf.readUInt8(8) + 150) / 100; return [ {channelId: 0, type: ReadingType.digital, value:status, label:&#39;status&#39;}, {channelId: 1, type: ReadingType.temperature, value:+temperature.toFixed(2), unit:&#39;°C&#39;, label:&#39;temperature&#39;}, {channelId: 2, type: ReadingType.humidity, value:+humidity.toFixed(2), unit:&#39;%&#39;,label:&#39;humidity&#39; }, {channelId: 3, type: ReadingType.time, value:periodSec, unit:&#39;s&#39;,label:&#39;uplink&#39;}, {channelId: 4, type: ReadingType.loraRssi, value:+rssi.toFixed(2),unit:&#39;dB&#39;,label:&#39;signal-strength&#39;}, {channelId: 5, type: ReadingType.loraSnr, value:+snr.toFixed(2),unit:&#39;dB&#39;, label:&#39;singal-to-noise&#39;}, {channelId: 6, type: ReadingType.voltage, value:+vcc.toFixed(2), unit:&#39;V&#39;, label:&#39;battery&#39;}, ] } Bitwise payload - Netvox device R311A function parseDeviceMsg(buf:Buffer, loraMsg:LoraMessage) { if(buf.length != 11) throw new Error(`Invalid payload length. Expected exactly 11 bytes. Got ${buf.length}`); if(buf.readUInt8(0) !== 0x01) throw new Error(`Invalid payload structure: expected loraWAN version 0x01 for byte 0, got =&gt; ${buf.readUInt8(0).toString(16)}`); // this is for R311A let expectedTypeCode:number = 0x02; if(buf.readUInt8(1) !== expectedTypeCode) throw new Error(`Invalid payload structure: expected sensor type ${expectedTypeCode}, got =&gt; ${buf.readUInt8(1).toString(16)}`); if(buf.readUInt8(2) !== 0x01) throw new Error(`Invalid report type ${buf.readUInt8(2)}, expected 0x01 got =&gt; ${buf.readUInt8(2).toString(16)}`); if(buf.readUInt8(4) !== 0x01 &amp;&amp; buf.readUInt8(4) !== 0x00) throw new Error(`Invalid payload structure: expected contact switch to be either 0x00 or 0x01, got =&gt; ${buf.readUInt8(4).toString(16)}`); let lowVoltage = (buf.readUInt8(3) &amp; 0x80) === 0x80 ? true : false; let voltage = (buf.readUInt8(3) &amp; 0x7F)/10; // byte 3 let openContact = buf.readUInt8(4) === 0x01 ? false : true; return { { channelId: 0, type:ReadingType.boolean, value: openContact ? 1 : 0, label:&#39;open-contact&#39;}, { channelId: 1, type:ReadingType.voltage, value: voltage, label:&#39;battery&#39;, unit:&#39;V&#39;}, { channelId: 2, type:ReadingType.boolean, value: lowVoltage, label:&#39;low-battery&#39;} } } Variable length payload device - NSEN function parseDeviceMsg(buf:Buffer, loraMsg:LoraMessage):ParserReading[] { // we support data messages only if(loraMsg.loraPort !== 3) return []; let offset = 0; let retList:ParserReading[] = []; while(offset &lt; buf.length) { let {readingList, size } = this.readValue(buf); retList = retList.concat(readingList); // we move the cursor inside the buffer for the next reading buf = buf.slice(size); } return retList; } function readValue(buf:Buffer):{size:number, readingList:ParserReading[]} { let channelId = buf.readUInt8(0); let size = 2; let retList:ParserReading[] = []; // the channel meaning is fixed so we ignore the channel type information (byte 1) switch(channelId) { case 0: retList.push({channelId: channelId, type:ReadingType.voltage, value: buf.readInt16BE(2)/100, label:&#39;primary-battery&#39;, unit:&#39;V&#39;}); size+=2; break; case 1: retList.push({channelId: channelId, type:ReadingType.current, value: buf.readInt16BE(2)/100, label:&#39;primary-battery&#39;, unit:&#39;mA&#39;}); size+=2; break; case 2: retList.push({channelId: channelId, type:ReadingType.voltage, value: buf.readInt16BE(2)/100, label:&#39;external-supply&#39;, unit:&#39;V&#39;}); size+=2; break; case 3: retList.push({channelId: channelId, type:ReadingType.voltage, value: buf.readInt16BE(2)/100, label:&#39;secondary-battery&#39;, unit:&#39;V&#39;}); size+=2; break; case 4: retList.push({channelId: channelId, type:ReadingType.current, value: buf.readInt16BE(2)/100, label:&#39;secondary-battery&#39;, unit:&#39;mA&#39;}); size+=2; break; case 5: retList.push({channelId: channelId, type:ReadingType.current, value: buf.readInt16BE(2)/100, label:&#39;loop-current&#39;, unit:&#39;mA&#39;}); size+=2; break; case 6: retList.push({channelId: channelId, type:ReadingType.voltage, value: buf.readInt16BE(2)/100, label:&#39;analog-input-1&#39;, unit:&#39;V&#39;}); size+=2; break; case 7: retList.push({channelId: channelId, type:ReadingType.voltage, value: buf.readInt16BE(2)/100, label:&#39;analog-input-2&#39;, unit:&#39;V&#39;}); size+=2; break; case 8: retList.push({channelId: channelId, type:ReadingType.voltage, value: buf.readInt16BE(2)/100, label:&#39;analog-input-3&#39;, unit:&#39;V&#39;}); size+=2; break; case 9: retList.push({channelId: channelId, type:ReadingType.analog, value: buf.readUInt16BE(2), label:&#39;pulse-count-1&#39;, unit:&#39;counts&#39;}); size+=2; break; case 10: retList.push({channelId: channelId, type:ReadingType.analog, value: buf.readUInt16BE(2), label:&#39;pulse-count-2&#39;, unit:&#39;counts&#39;}); size+=2; break; case 11: retList.push({channelId: channelId, type:ReadingType.analog, value: buf.readUInt16BE(2), label:&#39;pulse-count-3&#39;, unit:&#39;counts&#39;}); size+=2; break; case 12: retList.push({channelId: channelId, type:ReadingType.humidity, value: buf.readUInt8(2)*0.5, unit:&#39;%&#39;}); size+=1; break; case 13: retList.push({channelId: channelId, type:ReadingType.temperature, value: buf.readInt16BE(2)/10, unit:&#39;°C&#39;}); size+=2; break; case 14: retList.push({channelId: channelId, type:ReadingType.pressure, value: buf.readUInt16BE(2)/10, unit:&#39;hPa&#39;}); size+=2; break; case 15: retList.push({channelId: channelId, type:ReadingType.acceleration, value:{x: buf.readInt16BE(2)/1000, y:buf.readInt16BE(4)/1000, z:buf.readInt16BE(6)/1000}, unit: &#39;G&#39;}); size+=6;break; case 16: retList.push({channelId: channelId, type:ReadingType.pitch, value: buf.readInt16BE(2)/100, unit: &#39;°&#39;}); size+=2; break; case 17: retList.push({channelId: channelId, type:ReadingType.roll, value: buf.readInt16BE(2)/100, unit: &#39;°&#39;}); size+=2; break; case 18: retList.push({channelId: channelId, type:ReadingType.temperature, value: buf.readInt16BE(2)/10, unit: &#39;°C&#39;, label:&#39;thermistor-1&#39;}); size+=2; break; case 19: retList.push({channelId: channelId, type:ReadingType.temperature, value: buf.readInt16BE(2)/10, unit: &#39;°C&#39;, label:&#39;thermistor-2&#39;}); size+=2; break; case 20: retList.push({channelId: channelId, type:ReadingType.gps, value: { lat: buf.readIntBE(2, 3)/10000, long: buf.readIntBE(5, 3)/10000, alt: buf.readIntBE(8, 3)/100}}); size+=9; break; case 21: retList.push({channelId: channelId, type:ReadingType.percentage, value: buf.readInt16BE(2)/100, unit:&#39;%&#39;, label:&#39;pb-state-of-charge&#39;}); size+=2; break; case 22: retList.push({channelId: channelId, type:ReadingType.percentage, value: buf.readInt16BE(2)/100, unit:&#39;%&#39;, label:&#39;cpu-usage&#39;}); size+=2; break; case 23: retList.push({channelId: channelId, type:ReadingType.time, value: buf.readInt16BE(2)/100, unit:&#39;months&#39;, label:&#39;cpu-uptime&#39;}); size+=2; break; case 24: retList.push({channelId: channelId, type:ReadingType.loraRssi, value: buf.readInt16BE(2)/100, unit:&#39;db&#39;, label:&#39;rssi-down&#39;}); size+=2; break; case 25: retList.push({channelId: channelId, type:ReadingType.loraSnr, value: buf.readInt16BE(2)/100, label:&#39;snr-down&#39;}); size+=2; break; case 26: retList.push({channelId: channelId, type:ReadingType.boolean, value: buf.readUInt8(2), label:&#39;pulse-state-1&#39;}); size+=1; break; case 27: retList.push({channelId: channelId, type:ReadingType.boolean, value: buf.readUInt8(2), label:&#39;pulse-state-2&#39;}); size+=1; break; case 28: retList.push({channelId: channelId, type:ReadingType.boolean, value: buf.readUInt8(2), label:&#39;pulse-state-3&#39;}); size+=1; break; case 29: retList.push({channelId: channelId, type:ReadingType.digital, value: buf.readUInt32BE(2), label:&#39;accum-pulse-count-1&#39;}); size+=4; break; case 30: retList.push({channelId: channelId, type:ReadingType.digital, value: buf.readUInt32BE(2), label:&#39;accum-pulse-count-2&#39;}); size+=4; break; case 31: retList.push({channelId: channelId, type:ReadingType.digital, value: buf.readUInt32BE(2), label:&#39;accum-pulse-count-3&#39;}); size+=4; break; case 32: retList.push({channelId: channelId, type:ReadingType.pressure, value: buf.readInt16BE(2), unit:&#39;kPa&#39;, label:&#39;bridge-1&#39;}); size+=2; break; case 33: retList.push({channelId: channelId, type:ReadingType.analog, value: buf.readUInt16BE(2)/100, label:&#39;bridge-2&#39;}); size+=2; break; } return { readingList: retList, size: size }; }</description>
    </item>
  </channel>
</rss>