TTN Smart Sensor (Mcci)

Sensor

Codec Description

Codec for Mcci

Codec Preview

/* Name: catena-message-port2-format-22-decoder-ttn.js Function: Decode port 0x02 format 0x22 messages for TTN console. Copyright and License: See accompanying LICENSE file at https://github.com/mcci-catena/MCCI-Catena-4430/ Author: Terry Moore, MCCI Corporation August 2019 */ // calculate dewpoint (degrees C) given temperature (C) and relative humidity (0..100) // from http://andrew.rsmas.miami.edu/bmcnoldy/Humidity.html // rearranged for efficiency and to deal sanely with very low (< 1%) RH function dewpoint(t, rh) { var c1 = 243.04; var c2 = 17.625; var h = rh / 100; if (h <= 0.01) h = 0.01; else if (h > 1.0) h = 1.0; var lnh = Math.log(h); var tpc1 = t + c1; var txc2 = t * c2; var txc2_tpc1 = txc2 / tpc1; var tdew = c1 * (lnh + txc2_tpc1) / (c2 - lnh - txc2_tpc1); return tdew; } /* Name: CalculateHeatIndex() Description: Calculate the NWS heat index given dry-bulb T and RH Definition: function CalculateHeatIndex(t, rh) -> value or null Description: T is a Farentheit temperature in [76,120]; rh is a relative humidity in [0,100]. The heat index is computed and returned; or an error is returned. For consistency with the other temperature, despite the heat index being defined in Farenheit, we return in Celsius. Returns: number => heat index in Farenheit. null => error. References: https://github.com/mcci-catena/heat-index/ https://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml Results was checked against the full chart at iweathernet.com: https://www.iweathernet.com/wxnetcms/wp-content/uploads/2015/07/heat-index-chart-relative-humidity-2.png The MCCI-Catena heat-index site has a test js script to generate CSV to match the chart, a spreadsheet that recreates the chart, and a spreadsheet that compares results. */ function CalculateHeatIndex(t, rh) { var tRounded = Math.floor(t + 0.5); // return null outside the specified range of input parameters if (tRounded < 76 || tRounded > 126) return null; if (rh < 0 || rh > 100) return null; // according to the NWS, we try this first, and use it if we can var tHeatEasy = 0.5 * (t + 61.0 + ((t - 68.0) * 1.2) + (rh * 0.094)); // The NWS says we use tHeatEasy if (tHeatHeasy + t)/2 < 80.0 // This is the same computation: if ((tHeatEasy + t) < 160.0) return (tHeatEasy - 32) * 5 / 9; // need to use the hard form, and possibly adjust. var t2 = t * t; // t squared var rh2 = rh * rh; // rh squared var tResult = -42.379 + (2.04901523 * t) + (10.14333127 * rh) + (-0.22475541 * t * rh) + (-0.00683783 * t2) + (-0.05481717 * rh2) + (0.00122874 * t2 * rh) + (0.00085282 * t * rh2) + (-0.00000199 * t2 * rh2); // these adjustments come from the NWA page, and are needed to // match the reference table. var tAdjust; if (rh < 13.0 && 80.0 <= t && t <= 112.0) tAdjust = -((13.0 - rh) / 4.0) * Math.sqrt((17.0 - Math.abs(t - 95.0)) / 17.0); else if (rh > 85.0 && 80.0 <= t && t <= 87.0) tAdjust = ((rh - 85.0) / 10.0) * ((87.0 - t) / 5.0); else tAdjust = 0; // apply the adjustment tResult += tAdjust; // finally, the reference tables have no data above 183 (rounded), // so filter out answers that we have no way to vouch for. if (tResult >= 183.5) return null; else return (tResult - 32) * 5 / 9; } function DecodeU16(Parse) { var i = Parse.i; var bytes = Parse.bytes; var result = (bytes[i] << 8) + bytes[i + 1]; Parse.i = i + 2; return result; } function DecodeUflt16(Parse) { var rawUflt16 = DecodeU16(Parse); var exp1 = rawUflt16 >> 12; var mant1 = (rawUflt16 & 0xFFF) / 4096.0; var f_unscaled = mant1 * Math.pow(2, exp1 - 15); return f_unscaled; } function DecodeSflt16(Parse) { var rawSflt16 = DecodeU16(Parse); // rawSflt16 is the 2-byte number decoded from wherever; // it's in range 0..0xFFFF // bit 15 is the sign bit // bits 14..11 are the exponent // bits 10..0 are the the mantissa. Unlike IEEE format, // the msb is explicit; this means that numbers // might not be normalized, but makes coding for // underflow easier. // As with IEEE format, negative zero is possible, so // we special-case that in hopes that JavaScript will // also cooperate. // // The result is a number in the open interval (-1.0, 1.0); // // throw away high bits for repeatability. rawSflt16 &= 0xFFFF; // special case minus zero: if (rawSflt16 === 0x8000) return -0.0; // extract the sign. var sSign = ((rawSflt16 & 0x8000) !== 0) ? -1 : 1; // extract the exponent var exp1 = (rawSflt16 >> 11) & 0xF; // extract the "mantissa" (the fractional part) var mant1 = (rawSflt16 & 0x7FF) / 2048.0; // convert back to a floating point number. We hope // that Math.pow(2, k) is handled efficiently by // the JS interpreter! If this is time critical code, // you can replace by a suitable shift and divide. var f_unscaled = sSign * mant1 * Math.pow(2, exp1 - 15); return f_unscaled; } function DecodeLight(Parse) { return DecodeUflt16(Parse); } function DecodeActivity(Parse) { return DecodeSflt16(Parse); } function DecodeI16(Parse) { var i = Parse.i; var bytes = Parse.bytes; var result = (bytes[i] << 8) + bytes[i + 1]; Parse.i = i + 2; // interpret uint16 as an int16 instead. if (result & 0x8000) result += -0x10000; return result; } function DecodeI32(Parse) { var i = Parse.i; var bytes = Parse.bytes; var result = (bytes[i + 0] << 24)+ (bytes[i + 1] << 16) + (bytes[i + 2] << 8) + bytes[i + 3]; Parse.i = i + 4; // interpret uint16 as an int16 instead. if (result & 0x80000000) result += -0x100000000; return result; } function DecodeU32(Parse) { var i = Parse.i; var bytes = Parse.bytes; var result = (bytes[i + 0] << 24)+ (bytes[i + 1] << 16) + (bytes[i + 2] << 8) + bytes[i + 3]; Parse.i = i + 4; return result; } function RemainingBytes(Parse) { var i = Parse.i; var nBytes = Parse.bytes.length; if (i < nBytes) return (nBytes - i); else return 0; } function DecodeV(Parse) { return DecodeI16(Parse) / 4096.0; } function Decoder(bytes, port) { // Decode an uplink message from a buffer // (array) of bytes to an object of fields. var decoded = {}; if (! (port === 2)) return null; var uFormat = bytes[0]; if (! (uFormat === 0x22)) return null; // an object to help us parse. var Parse = {}; Parse.bytes = bytes; // i is used as the index into the message. Start with the time. Parse.i = 1; // fetch time, convert from GPS to ISO time assuming 17 leap seconds, // and then convert to JSON format. decoded.time = new Date((DecodeU32(Parse) + 315964800 - 17) * 1000).toJSON(); // fetch the bitmap. var flags = bytes[Parse.i++]; if (flags & 0x1) { decoded.Vbat = DecodeV(Parse); } if (flags & 0x2) { decoded.Vsys = DecodeV(Parse); } if (flags & 0x4) { decoded.Vbus = DecodeV(Parse); } if (flags & 0x8) { var iBoot = bytes[Parse.i++]; decoded.boot = iBoot; } if (flags & 0x10) { // we have temp, pressure, RH decoded.tempC = DecodeI16(Parse) / 256; decoded.p = DecodeU16(Parse) * 4 / 100.0; decoded.rh = DecodeU16(Parse) * 100 / 65535.0; decoded.tDewC = dewpoint(decoded.tempC, decoded.rh); var tHeat = CalculateHeatIndex(decoded.tempC * 1.8 + 32, decoded.rh); if (tHeat !== null) decoded.tHeatIndexC = tHeat; } if (flags & 0x20) { // we have light decoded.irradiance = {}; decoded.irradiance.White = DecodeLight(Parse) * Math.pow(2.0, 24); } if (flags & 0x40) { // we have gpio counts decoded.pellets = []; for (var i = 0; i < 2; ++i) { decoded.pellets[i] = {}; decoded.pellets[i].Total = DecodeU16(Parse); decoded.pellets[i].Delta = bytes[Parse.i++]; } } if (flags & 0x80) { // we have Activity decoded.activity = []; var i = 0; while (RemainingBytes(Parse) >= 2) { decoded.activity[i] = DecodeActivity(Parse); ++i; } } return decoded; } // TTN V3 decoder function decodeUplink(tInput) { var decoded = Decoder(tInput.bytes, tInput.fPort); var result = {}; result.data = decoded; return result; } 

This codec is sourced from The Things Network. All rights belong to The Things Network.

This codec is licensed under the GNU General Public License v3 (GPL v3). Modifications, if any, are clearly marked. You are free to use, modify, and distribute the codec under the terms of GPL v3.

Community Feedback