UNPKG

bresserweathersensorlw-codec

Version:

LoRaWAN Codec API compliant codec for BresserWeatherSensorLW

908 lines (855 loc) 32 kB
/////////////////////////////////////////////////////////////////////////////// // uplink_formatter.js // // Bresser 868 MHz Weather Sensor Radio Receiver // based on ESP32 and SX1262/SX1276 - // sends data to a LoRaWAN network (e.g. The Things Network) // // This script allows to decode payload received from The Things Network - // data (sent at fixed intervals) and responses (sent upon request) - // from bytes to JSON. // // Commands: // ---------- // (see constants for ports below) // port = CMD_SET_WS_TIMEOUT, {"ws_timeout": <timeout_in_seconds>} // port = CMD_SET_SLEEP_INTERVAL, {"sleep_interval": <interval_in_seconds>} // port = CMD_SET_SLEEP_INTERVAL_LONG, {"sleep_interval_long": <interval_in_seconds>} // port = CMD_SET_LW_STATUS_INTERVAL, {"lw_status_interval": <interval_in_frames>} // port = CMD_GET_DATETIME, {"cmd": "CMD_GET_DATETIME"} / payload = 0x00 // port = CMD_SET_DATETIME, {"epoch": <epoch>} // port = CMD_RESET_WS_POSTPROC, {"reset_flags": <flags>} // port = CMD_GET_WS_POSTPROC, {"cmd": "CMD_GET_WS_POSTPROC"} / payload = 0x00 // port = CMD_SET_WS_POSTPROC, {"update_interval": <update_interval>} // port = CMD_GET_LW_CONFIG, {"cmd": "CMD_GET_LW_CONFIG"} / payload = 0x00 // port = CMD_GET_WS_TIMEOUT, {"cmd": "CMD_GET_WS_TIMEOUT" / payload = 0x00 // port = CMD_SET_WS_TIMEOUT, {"ws_timeout": <ws_timeout>} // port = CMD_GET_APP_STATUS_INTERVAL, {"cmd": "CMD_GET_APP_STATUS_INTERVAL"} / payload = 0x00 // port = CMD_SET_APP_STATUS_INTERVAL, {"app_status_interval": <app_status_interval>} // port = CMD_GET_SENSORS_STAT, {"cmd": "CMD_GET_SENSORS_STAT"} / payload = 0x00 // port = CMD_GET_SENSORS_INC, {"cmd": "CMD_GET_SENSORS_INC"} / payload = 0x00 // port = CMD_SET_SENSORS_INC, {"sensors_inc": [<sensors_inc0>, ..., <sensors_incN>]} // port = CMD_GET_SENSORS_EXC, {"cmd": "CMD_GET_SENSORS_EXC"} / payload = 0x00 // port = CMD_SET_SENSORS_EXC, {"sensors_exc": [<sensors_exc0>, ..., <sensors_excN>]} // port = CMD_GET_BLE_ADDR, {"cmd": "CMD_GET_BLE_ADDR"} / payload = 0x00 // port = CMD_SET_BLE_ADDR, {"ble_addr": [<ble_addr0>, ..., <ble_addrN>]} // port = CMD_GET_BLE_CONFIG, {"cmd": "CMD_GET_BLE_CONFIG"} / payload = 0x00 // port = CMD_SET_BLE_CONFIG, {"ble_active": <ble_active>, "ble_scantime": <ble_scantime>} // port = CMD_GET_APP_PAYLOAD_CFG, {"cmd": "CMD_GET_APP_PAYLOAD_CFG"} / payload = 0x00 // // Responses: // ----------- // (The response uses the same port as the request.) // CMD_GET_LW_CONFIG {"sleep_interval": <sleep_interval>, // "sleep_interval_long": <sleep_interval_long>} // // CMD_GET_DATETIME {"epoch": <unix_epoch_time>, "rtc_source": <rtc_source>} // // CMD_GET_WS_TIMEOUT {"ws_timeout": <ws_timeout>} // // CMD_GET_WS_POSTPROC {"update_interval": <update_interval>} // // CMD_GET_APP_STATUS_INTERVAL {"app_status_interval": <app_status_interval>} // // CMD_GET_SENSORS_STAT {"sensor_status": {bresser: [<bresser_stat0>, ..., <bresser_stat15>], "ble_stat": <ble_stat>}} // // CMD_GET_SENSORS_INC {"sensors_inc": [<sensors_inc0>, ...]} // // CMD_GET_SENSORS_EXC {"sensors_exc"}: [<sensors_exc0>, ...]} // // CMD_GET_SENSORS_CFG {"max_sensors": <max_sensors>, "rx_flags": <rx_flags>, "en_decoders": <en_decoders>} // // CMD_GET_BLE_ADDR {"ble_addr": [<ble_addr0>, ...]} // // CMD_GET_BLE_CONFIG {"ble_active": <ble_active>, "ble_scantime": <ble_scantime>} // // CMD_GET_APP_PAYLOAD_CFG {"bresser": [<type0>, <type1>, ..., <type15>], "onewire": <onewire>, "analog": <analog>, "digital": <digital>} // // <ws_timeout> : 0...255 // <sleep_interval> : 0...65535 // <sleep_interval_long>: 0...65535 // <lw_status_interval> : LoRaWAN node status message uplink interval in no. of frames (0...255, 0: disabled) // <epoch> : unix epoch time, see https://www.epochconverter.com/ (<integer> / "0x....") // <reset_flags> : 0...15 (1: hourly / 2: daily / 4: weekly / 8: monthly) / "0x0"..."0xF" // <update_interval> : Rain gauge / lightning counter post processing interval in minutes (1...255, 0: auto) // <rtc_source> : 0x00: GPS / 0x01: RTC / 0x02: LORA / 0x03: unsynched / 0x04: set (source unknown) // <app_status_interval>: Sensor status message uplink interval in no. of frames (0...255, 0: disabled) // <sensors_incN> : e.g. "0xDEADBEEF" // <sensors_excN> : e.g. "0xDEADBEEF" // <max_sensors> : max. number of Bresser sensors per receive cycle; 1...8 // <rx_flags> : Flags for getData(); see BresserWeatherSensorReceiver // <en_decoders> : Enabled decoders; see BresserWeatherSensorReceiver // <ble_active> : BLE scan mode - 0: passive / 1: active // <ble_scantime> : BLE scan time in seconds (0...255) // <ble_addrN> : e.g. "DE:AD:BE:EF:12:23" // <typeN> : Bitmap for enabling Bresser sensors of type N; each bit position corresponds to a channel, e.g. bit 0 controls ch0; // unused bits can be used to select features // <onewire> : Bitmap for enabling 1-Wire sensors; each bit position corresponds to an index // <analog> : Bitmap for enabling analog input channels; each bit positions corresponds to a channel // <digital> : Bitmap for enabling digital input channels in a broad sense &mdash; GPIO, SPI, I2C, UART, ... // Based on: // --------- // ttn_decoder_fp.js // // created: 08/2023 // // // MIT License // // Copyright (c) 2025 Matthias Prinke // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. // // // History: // 20230821 Created // 20240420 Updated for BresserWeatherSensorLW, // renamed from ttn_uplink_formatter.js // 20240427 Added BLE configuration // 20240507 Added CMD_GET_SENSORS_CFG // 20240508 Added en_decoders to CMD_GET_SENSORS_CFG // 20240517 Added CMD_GET_APP_PAYLOAD_CFG // 20240528 Modified sensor data payload decoder // 20240529 Added uint8fp1 for UV index // Added NaN results to decoding functions // Added supression of NaN results in decoder // 20240530 Added SKIP_INVALID_SIGNALS // Added BLE signals to decoder // 20240531 Fixed handling of arrays in decoder() // 20240601 Change lightning event to provide timestamp and time // Added compatibility mode: "status" as in BresserweatherSensorTTN // 20240603 Added sensor battery status flags (compatibility mode) // Added command Added CMD_GET_SENSORS_STAT and sensor status decoder // 20240604 Added suppression of invalid value in unixtime decoder // 20240605 Fixed decoding of NaN values, fixed flags for compatibility mode // 20240606 Changed naming of post-processed lightning data // 20240607 Added CMD_GET_STATUS_INTERVAL // 20240608 Added CMD_GET_LW_STATUS // 20240609 Refactored command encoding // 20240610 Fixed CMD_GET_SENSORS_CFG and CMD_GET_APP_PAYLOAD_CFG, // decode function 'bits8' // 20240704 Fixed/improved compatibility mode // 20240716 Added CMD_SCAN_SENSORS // 20240722 Added CMD_SET_LW_STATUS_INTERVAL, modified CMD_GET_LW_CONFIG, // renamed CMD_SET_STATUS_INTERVAL to CMD_SET_APP_STATUS_INTERVAL // 20240729 Added PowerFeather specific status information // 20250209 Changed flags in found_sensors() from 8 to 16 bits // Added ws_tglobe_c // 20250828 Added CMD_GET_WS_POSTPROC/CMD_SET_WS_POSTPROC // 20250905 Added module export // // ToDo: // - // /////////////////////////////////////////////////////////////////////////////// function decoder(bytes, port) { // bytes is of type Buffer // Skip signals encoded as invalid const SKIP_INVALID_SIGNALS = false; // Compatibility mode: create "status" as in BresserweatherSensorTTN const COMPATIBILITY_MODE = false; // Enable PowerFeather specific information in LoRaWAN Node Status message const POWERFEATHER = false; const CMD_GET_DATETIME = 0x20; const CMD_GET_LW_CONFIG = 0x36; const CMD_GET_LW_STATUS = 0x38; const CMD_GET_APP_STATUS_INTERVAL = 0x40; const CMD_GET_SENSORS_STAT = 0x42; const CMD_GET_APP_PAYLOAD_CFG = 0x46; const CMD_GET_WS_TIMEOUT = 0xC0; const CMD_GET_WS_POSTPROC = 0xCC; const CMD_SCAN_SENSORS = 0xC4; const CMD_GET_SENSORS_INC = 0xC6; const CMD_GET_SENSORS_EXC = 0xC8; const CMD_GET_SENSORS_CFG = 0xCA; const CMD_GET_BLE_CONFIG = 0xD0; const CMD_GET_BLE_ADDR = 0xD2; const rtc_source_code = { 0x00: "GPS", 0x01: "RTC", 0x02: "LORA", 0x03: "unsynched", 0x04: "set (source unknown)" }; const sensor_types = { 0: "Weather Sensor", 1: "Weather Sensor", 2: "Thermo-/Hygro-Sensor", 3: "Pool / Spa Thermometer", 4: "Soil Temperature and Moisture Sensor", 5: "Water Leakage Sensor", 6: "undefined", 7: "undefined", 8: "Air Quality Sensor (Particulate Matter)", 9: "Lightning Sensor", 10: "CO2 Sensor", 11: "Air Quality Sensor (HCHO and VOC)", 12: "undefined", 13: "Weather Sensor (8-in-1)", 14: "undefined", 15: "undefined" }; const sensor_decoders = { 0: "5-in-1", 1: "6-in-1", 2: "7-in-1", 3: "Lightning", 4: "Leakage" } var rtc_source = function (bytes) { if (bytes.length !== rtc_source.BYTES) { throw new Error('rtc_source must have exactly 1 byte'); } return rtc_source_code[bytes[0]]; }; rtc_source.BYTES = 1; var bytesToInt = function (bytes) { let i = 0; for (var x = 0; x < bytes.length; x++) { i |= +(bytes[x] << (x * 8)); } return i; }; // Big Endian var bytesToIntBE = function (bytes) { let i = 0; for (var x = 0; x < bytes.length; x++) { i |= +(bytes[x] << ((bytes.length - 1 - x) * 8)); } return i; }; var unixtime = function (bytes) { if (bytes.length !== unixtime.BYTES) { throw new Error('Unix time must have exactly 4 bytes'); } dateObj = new Date(bytesToInt(bytes) * 1000); let time = dateObj.toISOString(); let timestamp = bytesToInt(bytes); if (SKIP_INVALID_SIGNALS && (timestamp == -1) || (timestamp == 0)) { return NaN; } if (!COMPATIBILITY_MODE) { return { time: time, timestamp: timestamp }; } else { return timestamp; } }; unixtime.BYTES = 4; var uint8 = function (bytes) { if (bytes.length !== uint8.BYTES) { throw new Error('int must have exactly 1 byte'); } let res = bytesToInt(bytes); if (SKIP_INVALID_SIGNALS && res === 0xFF) { return NaN; } return res; }; uint8.BYTES = 1; // Same as uint8, but 0xFF is not converted to NaN var bits8 = function (bytes) { if (bytes.length !== bits8.BYTES) { throw new Error('bits8 must have exactly 1 byte'); } let res = bytesToInt(bytes); return res; }; bits8.BYTES = 1; var uint8fp1 = function (bytes) { if (bytes.length !== uint8fp1.BYTES) { throw new Error('int must have exactly 1 byte'); } let res = bytesToInt(bytes); if (SKIP_INVALID_SIGNALS && res === 0xFF) { return NaN; } res *= 0.1; return res.toFixed(1); }; uint8fp1.BYTES = 1; var uint16 = function (bytes) { if (bytes.length !== uint16.BYTES) { throw new Error('int must have exactly 2 bytes'); } let res = bytesToInt(bytes); if (SKIP_INVALID_SIGNALS && res === 0xFFFF) { return NaN; } return res; }; uint16.BYTES = 2; var uint16fp1 = function (bytes) { if (bytes.length !== uint16fp1.BYTES) { throw new Error('int must have exactly 2 bytes'); } let res = bytesToInt(bytes); if (SKIP_INVALID_SIGNALS && res === 0xFFFF) { return NaN; } res *= 0.1; return res.toFixed(1); }; uint16fp1.BYTES = 2; var uint32 = function (bytes) { if (bytes.length !== uint32.BYTES) { throw new Error('int must have exactly 4 bytes'); } return bytesToInt(bytes); }; uint32.BYTES = 4; var uint16BE = function (bytes) { if (bytes.length !== uint16BE.BYTES) { throw new Error('int must have exactly 2 bytes'); } return bytesToIntBE(bytes); }; uint16BE.BYTES = 2; var uint32BE = function (bytes) { if (bytes.length !== uint32BE.BYTES) { throw new Error('int must have exactly 4 bytes'); } return bytesToIntBE(bytes); }; uint32BE.BYTES = 4; var int16 = function (bytes) { if (bytes.length !== int16.BYTES) { throw new Error('int must have exactly 2 bytes'); } let res = bytesToInt(bytes); if (SKIP_INVALID_SIGNALS && res === 0xFFFF) { return NaN; } return res - 0x8000; }; int16.BYTES = 2; var int32 = function (bytes) { if (bytes.length !== int32.BYTES) { throw new Error('int must have exactly 4 bytes'); } let res = bytesToInt(bytes); if (SKIP_INVALID_SIGNALS && res === 0xFFFF) { return NaN; } return res - 0x80000000; }; int32.BYTES = 4; function byte2hex(byte) { return ('0' + byte.toString(16)).slice(-2); } var mac48 = function (bytes) { let res = []; let j = 0; for (var i = 0; i < bytes.length; i += 6) { res[j++] = byte2hex(bytes[i]) + ":" + byte2hex(bytes[i + 1]) + ":" + byte2hex(bytes[i + 2]) + ":" + byte2hex(bytes[i + 3]) + ":" + byte2hex(bytes[i + 4]) + ":" + byte2hex(bytes[i + 5]); } return res; }; mac48.BYTES = bytes.length; var bresser_bitmaps = function (bytes) { let res = []; for (var i = 0; i < 16; i++) { res[i] = "0x" + byte2hex(bytes[i]); } return res; }; bresser_bitmaps.BYTES = 16; var hex16 = function (bytes) { let res = "0x" + byte2hex(bytes[0]) + byte2hex(bytes[1]); return res; }; hex16.BYTES = 2; var hex32 = function (bytes) { let res = "0x" + byte2hex(bytes[0]) + byte2hex(bytes[1]) + byte2hex(bytes[2]) + byte2hex(bytes[3]); return res; }; hex32.BYTES = 4; var id32 = function (bytes) { let res = []; let j = 0; for (var i = 0; i < bytes.length; i += 4) { res[j++] = "0x" + byte2hex(bytes[i]) + byte2hex(bytes[i + 1]) + byte2hex(bytes[i + 2]) + byte2hex(bytes[i + 3]); } return res; }; id32.BYTES = bytes.length; var latLng = function (bytes) { if (bytes.length !== latLng.BYTES) { throw new Error('Lat/Long must have exactly 8 bytes'); } let lat = bytesToInt(bytes.slice(0, latLng.BYTES / 2)); let lng = bytesToInt(bytes.slice(latLng.BYTES / 2, latLng.BYTES)); return [lat / 1e6, lng / 1e6]; }; latLng.BYTES = 8; var temperature = function (bytes) { if (bytes.length !== temperature.BYTES) { throw new Error('Temperature must have exactly 2 bytes'); } let isNegative = bytes[0] & 0x80; let b = ('00000000' + Number(bytes[0]).toString(2)).slice(-8) + ('00000000' + Number(bytes[1]).toString(2)).slice(-8); if (isNegative) { let arr = b.split('').map(function (x) { return !Number(x); }); for (var i = arr.length - 1; i > 0; i--) { arr[i] = !arr[i]; if (arr[i]) { break; } } b = arr.map(Number).join(''); } let t = parseInt(b, 2); if (isNegative) { t = -t; } t = t / 1e2; if (SKIP_INVALID_SIGNALS && t == 327.67) { return NaN; } return t.toFixed(1); }; temperature.BYTES = 2; var humidity = function (bytes) { if (bytes.length !== humidity.BYTES) { throw new Error('Humidity must have exactly 2 bytes'); } let h = bytesToInt(bytes); if (SKIP_INVALID_SIGNALS && h === 0xFFFF) { return NaN; } return h / 1e2; }; humidity.BYTES = 2; // Based on https://stackoverflow.com/a/37471538 by Ilya Bursov // quoted by Arjan here https://www.thethingsnetwork.org/forum/t/decode-float-sent-by-lopy-as-node/8757 function rawfloat(bytes) { if (bytes.length !== rawfloat.BYTES) { throw new Error('Float must have exactly 4 bytes'); } // JavaScript bitwise operators yield a 32 bits integer, not a float. // Assume LSB (least significant byte first). var bits = bytes[3] << 24 | bytes[2] << 16 | bytes[1] << 8 | bytes[0]; var sign = (bits >>> 31 === 0) ? 1.0 : -1.0; var e = bits >>> 23 & 0xff; var m = (e === 0) ? (bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000; var f = sign * m * Math.pow(2, e - 150); if (f == 0x40000000) { return NaN; } return f.toFixed(1); } rawfloat.BYTES = 4; var bitmap_node = function (byte) { if (byte.length !== bitmap_node.BYTES) { throw new Error('Bitmap must have exactly 1 byte'); } let i = bytesToInt(byte); let bm = ('00000000' + Number(i).toString(2)).slice(-8).split('').map(Number).map(Boolean); return ['res7', 'res6', 'res5', 'res4', 'res3', 'res2', 'res1', 'res0'] .reduce(function (obj, pos, index) { obj[pos] = bm[index]; return obj; }, {}); }; bitmap_node.BYTES = 1; var bitmap_sensors = function (byte) { if (byte.length !== bitmap_sensors.BYTES) { throw new Error('Bitmap must have exactly 1 byte'); } let i = bytesToInt(byte); let bm = ('00000000' + Number(i).toString(2)).slice(-8).split('').map(Number).map(Boolean); // Only Weather Sensor //return ['res5', 'res4', 'res3', 'res2', 'res1', 'res0', 'dec_ok', 'batt_ok'] // Weather Sensor + MiThermo (BLE) Sensor //return ['res4', 'res3', 'res2', 'res1', 'res0', 'ble_ok', 'dec_ok', 'batt_ok'] // Weather Sensor, Soil Sensor and MiThermo (BLE) Sensor return ['res0', 'ble_ok', 'ls_dec_ok', 'ls_batt_ok', 's1_dec_ok', 's1_batt_ok', 'ws_dec_ok', 'ws_batt_ok'] .reduce(function (obj, pos, index) { obj[pos] = bm[index]; return obj; }, {}); }; bitmap_sensors.BYTES = 1; var sensor_status = function (bytes) { if (bytes.length !== sensor_status.BYTES) { throw new Error('Sensor status must have exactly 26 bytes'); } let res = {}; res.bresser = []; for (var i = 0; i < 16; i++) { res.bresser[i] = "0x" + byte2hex(bytes[i]); } res.ble = "0x" + byte2hex(bytes[24]) + byte2hex(bytes[25]); return res; }; sensor_status.BYTES = 26; function found_sensors(bytes) { let res = []; for (let i = 0; i < bytes.length; i += 9) { const decoded_id = hex32(bytes.slice(i, i + 4)); const tmp = uint8(bytes.slice(i + 4, i + 5)); const decoded_type = sensor_types[tmp & 0x0F]; const decoded_decoder = sensor_decoders[tmp >> 4]; const decoded_channel = uint8(bytes.slice(i + 5, i + 6)); const decoded_flags = "0x" + byte2hex(bytes[i + 7]) + byte2hex(bytes[i + 6]); const decoded_rssi = -uint8(bytes.slice(i + 8, i + 9)); res.push({ 'id': decoded_id, 'type': decoded_type, 'decoder': decoded_decoder, 'ch': decoded_channel, 'flags': decoded_flags, 'rssi': decoded_rssi }); } return res; } /** * Decodes the given bytes using the provided mask and names. * * @param {Array} bytes - The bytes to decode. * @param {Array} mask - The mask used for decoding. * @param {Array} [names] - The names of the decoded values. * @returns {Object} - The decoded values as an object. * @throws {Error} - If the length of the bytes is less than the mask length. */ var decode = function (port, bytes, mask, names) { // Sum of all mask bytes var maskLength = mask.reduce(function (prev, cur) { return prev + cur.BYTES; }, 0); if (bytes.length < maskLength) { throw new Error('Mask length is ' + maskLength + ' whereas input is ' + bytes.length); } names = names || []; var offset = 0; if (COMPATIBILITY_MODE) { var ws_dec_ok = true; var s1_dec_ok = true; var ls_dec_ok = true; var ble_ok = true; } var decodedValues = mask .map(function (decodeFn, idx) { var current = bytes.slice(offset, offset += decodeFn.BYTES); var decodedValue = decodeFn(current); // Check if the decoded value is NaN if (isNaN(decodedValue) && decodedValue.constructor === Number) { if (COMPATIBILITY_MODE) { var name = names[idx] || idx; if ((name == "ws_temp_c") || (name == "ws_humidity") || (name == "ws_rain_mm") || (name.startsWith('ws_wind_'))) { ws_dec_ok = false; } if ((name == "lgt_strike_count") || (name == "lgt_storm_dist_km")) { ls_dec_ok = false; } if (name.startsWith('soil1_')) { s1_dec_ok = false; } if (name.startsWith('ble0_')) { ble_ok = false; } } return null; } return decodedValue; }) .reduce(function (prev, cur, idx) { if (cur !== null) { prev[names[idx] || idx] = cur; } return prev; }, {}); if ((port == 1) && COMPATIBILITY_MODE) { //decodedValues.status = {}; // Create a status object in the decoded values decodedValues.status.ws_dec_ok = ws_dec_ok; decodedValues.status.ls_dec_ok = ls_dec_ok; decodedValues.status.s1_dec_ok = s1_dec_ok; decodedValues.status.ws_batt_ok = decodedValues.status.ws_batt_ok || !ws_dec_ok; decodedValues.status.ls_batt_ok = decodedValues.status.ls_batt_ok || !ls_dec_ok; decodedValues.status.s1_batt_ok = decodedValues.status.s1_batt_ok || !s1_dec_ok; decodedValues.status.ble_ok = ble_ok; } return decodedValues; }; if (typeof module === 'object' && typeof module.exports !== 'undefined') { module.exports = { unixtime: unixtime, uint8: uint8, uint16: uint16, uint32: uint32, int16: int16, int32: int32, uint16BE: uint16BE, uint32BE: uint32BE, mac48: mac48, bresser_bitmaps: bresser_bitmaps, temperature: temperature, humidity: humidity, latLng: latLng, bitmap_node: bitmap_node, bitmap_sensors: bitmap_sensors, sensor_status: sensor_status, rawfloat: rawfloat, bits8: bits8, uint8fp1: uint8fp1, uint16fp1: uint16fp1, rtc_source: rtc_source, found_sensors: found_sensors, decode: decode }; } if (port === 1) { if (!COMPATIBILITY_MODE) { return decode( port, bytes, [ temperature, uint8, rawfloat, uint16fp1, uint16fp1, uint16fp1, //uint32, //uint8fp1, rawfloat, rawfloat, rawfloat, rawfloat, //temperature, temperature, uint8, temperature, uint8, unixtime, uint16, uint8, temperature, uint16, temperature, uint8 ], [ 'ws_temp_c', 'ws_humidity', 'ws_rain_mm', 'ws_wind_gust_ms', 'ws_wind_avg_ms', 'ws_wind_dir_deg', //'ws_light_lux', //'ws_uv', 'ws_rain_hourly_mm', 'ws_rain_daily_mm', 'ws_rain_weekly_mm', 'ws_rain_monthly_mm', //'ws_tglobe_c', 'th1_temp_c', 'th1_humidity', 'soil1_temp_c', 'soil1_moisture', 'lgt_ev_time', 'lgt_ev_events', 'lgt_ev_dist_km', 'ow0_temp_c', 'a0_voltage_mv', 'ble0_temp_c', 'ble0_humidity' ] ); } else { // COMPATIBILITY_MODE - not recommended for new designs! return decode( port, bytes, [ temperature, uint8, rawfloat, uint16fp1, uint16fp1, uint16fp1, //uint32, //uint8fp1, rawfloat, rawfloat, rawfloat, rawfloat, //temperature, temperature, uint8, temperature, uint8, unixtime, uint16, uint8, temperature, uint16, temperature, uint8, bitmap_sensors ], [ 'air_temp_c', 'humidity', 'rain_mm', 'wind_gust_meter_sec', 'wind_avg_meter_sec', 'wind_direction_deg', //'ws_light_lux', //'ws_uv', // new 'rain_hr', 'rain_day', 'rain_week', 'rain_month', //'ws_tglobe_c', 'th1_temp_c', 'th1_humidity', //new 'soil_temp_c', 'soil_moisture', 'lightning_time', 'lightning_events', 'lightning_distance_km', 'water_temp_c', 'supply_v', 'indoor_temp_c', 'indoor_humidity', 'status' ] ); } //return {...res, ...sensorStatus}; } else if (port === CMD_GET_DATETIME) { return decode( port, bytes, [uint32BE, rtc_source ], ['unixtime', 'rtc_source' ] ); } else if (port === CMD_GET_LW_CONFIG) { return decode( port, bytes, [uint16BE, uint16BE, uint8 ], ['sleep_interval', 'sleep_interval_long', 'lw_status_interval' ] ); } else if (port === CMD_GET_LW_STATUS) { if (POWERFEATHER) { return decode( port, bytes, [uint16, uint8, uint16, int16, int16, uint8, uint8, uint16, int32, temperature ], ['ubatt_mv', 'long_sleep', 'usupply_mv', 'isupply_ma', 'ibatt_ma', 'soc', 'soh', 'batt_cycles', 'batt_time_min', 'batt_temp_c' ] ); } else { return decode( port, bytes, [uint16, uint8 ], ['ubatt_mv', 'long_sleep' ] ); } } else if (port === CMD_GET_WS_TIMEOUT) { return decode( port, bytes, [uint8 ], ['ws_timeout' ] ); } else if (port === CMD_GET_WS_POSTPROC) { return decode( port, bytes, [uint8 ], ['update_interval' ] ); } else if (port === CMD_GET_SENSORS_INC) { return decode( port, bytes, [id32 ], ['sensors_inc' ] ); } else if (port === CMD_GET_SENSORS_EXC) { return decode( port, bytes, [id32 ], ['sensors_exc' ] ); } else if (port === CMD_GET_SENSORS_CFG) { return decode( port, bytes, [bits8, bits8, bits8 ], ['max_sensors', 'rx_flags', 'en_decoders' ] ); } else if (port === CMD_GET_BLE_ADDR) { return decode( port, bytes, [mac48 ], ['ble_addr' ] ); } else if (port === CMD_GET_BLE_CONFIG) { return decode( port, bytes, [bits8, bits8 ], ['ble_active', 'ble_scantime'] ); } else if (port === CMD_GET_APP_PAYLOAD_CFG) { return decode( port, bytes, [bresser_bitmaps, hex16, hex16, hex32 ], ['bresser', 'onewire', 'analog', 'digital'] ); } else if (port === CMD_GET_APP_STATUS_INTERVAL) { return decode( port, bytes, [bits8 ], ['app_status_interval' ] ); } else if (port === CMD_SCAN_SENSORS) { return { 'found_sensors': found_sensors(bytes) }; } else if (port === CMD_GET_SENSORS_STAT) { return decode( port, bytes, [sensor_status ], ['sensor_status' ] ); } } function decodeUplink(input) { return { data: { bytes: decoder(input.bytes, input.fPort) }, warnings: [], errors: [] }; } module.exports.decodeUplink = decodeUplink;