-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
5423a56
commit 7b38ec1
Showing
4 changed files
with
657 additions
and
0 deletions.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
// | ||
// Module: catena-message-port3-decoder-node-red.js | ||
// | ||
// Function: | ||
// This Node-RED decoding function decodes the record sent by the Catena 4617/4618 | ||
// simple sensor app. | ||
// | ||
// License: | ||
// Copyright (C) 2019, MCCI Corporation. | ||
// See LICENSE in accompanying git repository. | ||
// | ||
|
||
// begin catena-message-port3-decoder-ttn.js | ||
/* | ||
Name: catena-message-port2-decoder-ttn.js | ||
Function: | ||
Decode MCCI port 0x02 messages for TTN console. | ||
Copyright and License: | ||
See accompanying LICENSE file at https://github.com/mcci-catena/MCCI-Catena-PMS7003/ | ||
Author: | ||
Terry Moore, MCCI Corporation July 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. | ||
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; | ||
|
||
// 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; | ||
} | ||
|
||
function CalculateHeatIndexCelsius(t, rh) { | ||
var result = CalculateHeatIndex(t, rh); | ||
if (result !== null) { | ||
// convert to celsius. | ||
result = (result - 32) * 5 / 9; | ||
} | ||
return result; | ||
} | ||
|
||
/* | ||
Name: Decoder() | ||
Function: | ||
Decode an MCCI Catena port-2 message for The Things Network console. | ||
Definition: | ||
function Decoder(bytes, port) -> object | ||
Description: | ||
This function decodes the message given by the byte array `bytes[]`, | ||
and returns an object with values in engineering units. | ||
Returns: | ||
Object, or null if the bytes could not be decoded. | ||
*/ | ||
|
||
function Decoder(bytes, port) { | ||
// Decode an uplink message from a buffer | ||
// (array) of bytes to an object of fields. | ||
var decoded = {}; | ||
|
||
if (! (port === 3)) | ||
return null; | ||
|
||
// see catena-message-port3-format.md | ||
// i is used as the index into the message. Start with the flag byte. | ||
// note that there's no discriminator. | ||
// test vectors are also available there. | ||
var i = 0; | ||
// fetch the bitmap. | ||
var flags = bytes[i++]; | ||
|
||
if (flags & 0x1) { | ||
// set Vraw to a uint16, and increment pointer | ||
var Vraw = (bytes[i] << 8) + bytes[i + 1]; | ||
i += 2; | ||
// interpret uint16 as an int16 instead. | ||
if (Vraw & 0x8000) | ||
Vraw += -0x10000; | ||
// scale and save in result. | ||
decoded.Vbat = Vraw / 4096.0; | ||
} | ||
|
||
if (flags & 0x2) { | ||
var Vraw = (bytes[i] << 8) + bytes[i + 1]; | ||
i += 2; | ||
if (Vraw & 0x8000) | ||
Vraw += -0x10000; | ||
decoded.VDD = Vraw / 4096.0; | ||
} | ||
|
||
if (flags & 0x4) { | ||
var iBoot = bytes[i]; | ||
i += 1; | ||
decoded.boot = iBoot; | ||
} | ||
|
||
if (flags & 0x8) { | ||
// we have temp, pressure, RH | ||
var tRaw = (bytes[i] << 8) + bytes[i + 1]; | ||
if (tRaw & 0x8000) | ||
tRaw = -0x10000 + tRaw; | ||
i += 2; | ||
var rhRaw = (bytes[i] << 8) + bytes[i + 1]; | ||
i += 2; | ||
|
||
decoded.t = tRaw / 256; | ||
decoded.rh = rhRaw / 65535.0 * 100; | ||
decoded.tDew = dewpoint(decoded.t, decoded.rh); | ||
decoded.tHeatIndexC = CalculateHeatIndexCelsius(decoded.t, decoded.rh); | ||
} | ||
|
||
if (flags & 0x10) { | ||
// we have light irradiance info | ||
var irradiance = {}; | ||
decoded.irradiance = irradiance; | ||
|
||
var lightRaw = (bytes[i] << 8) + bytes[i + 1]; | ||
i += 2; | ||
irradiance.IR = lightRaw; | ||
|
||
lightRaw = (bytes[i] << 8) + bytes[i + 1]; | ||
i += 2; | ||
irradiance.White = lightRaw; | ||
|
||
lightRaw = (bytes[i] << 8) + bytes[i + 1]; | ||
i += 2; | ||
irradiance.UV = lightRaw; | ||
} | ||
|
||
if (flags & 0x20) { | ||
var Vraw = (bytes[i] << 8) + bytes[i + 1]; | ||
i += 2; | ||
if (Vraw & 0x8000) | ||
Vraw += -0x10000; | ||
decoded.Vbus = Vraw / 4096.0; | ||
} | ||
|
||
// at this point, decoded has the real values. | ||
return decoded; | ||
} | ||
// end catnea-message-port3-decoder-ttn.js | ||
|
||
/* | ||
Node-RED function body. | ||
Input: | ||
msg the object to be decoded. | ||
msg.payload_raw is taken | ||
as the raw payload if present; otheriwse msg.payload | ||
is taken to be a raw payload. | ||
msg.port is taken to be the LoRaWAN port nubmer. | ||
Returns: | ||
This function returns a message body. It's a mutation of the | ||
input msg; msg.payload is changed to the decoded data, and | ||
msg.local is set to additional application-specific information. | ||
*/ | ||
|
||
var b; | ||
|
||
if ("payload_raw" in msg) { | ||
// the console already decoded this | ||
b = msg.payload_raw; // pick up data for convenience | ||
// msg.payload_fields still has the decoded data | ||
} | ||
else { | ||
// no console debug | ||
b = msg.payload; // pick up data for conveneince | ||
} | ||
|
||
var result = Decoder(b, msg.port); | ||
|
||
// now update msg with the new payload and new .local field | ||
// the old msg.payload is overwritten. | ||
msg.payload = result; | ||
msg.local = | ||
{ | ||
nodeType: "Catena 4617/4618", | ||
platformType: "Catena 461x", | ||
radioType: "Murata", | ||
applicationName: "Simple sensor" | ||
}; | ||
|
||
return msg; |
Oops, something went wrong.