UNPKG

ecowitt-gateway

Version:

Retrieve data and get and set settings for Ecowitt Gateways

434 lines (368 loc) 12.7 kB
const DECODE = { 'rain': decodeRain, 'bigRain': decodeBigRain, 'rainRate': decodeRainRate, 'temp': decodeTemp, 'ugm3': decodeUgm3, 'ugm3_offset': decodeUgm3Offset, 'humid': decodeHumid, 'pressure': decodePress, 'dir': decodeDir, 'speed': decodeSpeed, 'light': decodeLight, 'uv': decodeUV, 'uvi': decodeUVI, 'datetime': decodeDatetime, 'moist': decodeMoist, 'aq': decodeAq, 'batt': decodeBatt, 'tempBatt': decodeTempBatt, 'leak': decodeLeak, 'dist': decodeDistance, 'utc': decodeUTC, 'count': decodeCount, 'rstRain': decodeRstRain, 'gain': decodeGain, 'gain10': decodeGain10 } /* Decode temperature data. Data is contained in a two byte big endian signed integer and represents tenths of a degree celcius. */ function decodeTemp(idx, buffer) { return buffer.readInt16BE(idx) / 10; } /* Decode humidity data. Data is contained in a single unsigned byte and represents whole units (%). */ function decodeHumid(idx, buffer) { return buffer.readInt8(idx); } /* Decode pressure data. Data is contained in a two byte big endian integer and represents tenths of a unit (hpa). */ function decodePress(idx, buffer) { return buffer.readUInt16BE(idx) / 10.0; } /* Decode direction data. Data is contained in a two byte big endian integer and represents whole degrees. */ function decodeDir(idx, buffer) { return buffer.readUInt16BE(idx); } /* Decode speed data. Data is contained in a two byte big endian integer and represents tenths of m/s. */ function decodeSpeed(idx, buffer) { return buffer.readUInt16BE(idx) / 10.0; } /* Decode rain data. Data is contained in a two byte big endian integer and represents tenths of mm. */ function decodeRain(idx, buffer) { return buffer.readUInt16BE(idx) / 10.0; } /* Decode rain rate data. Data is contained in a two byte big endian integer and represents tenths of mm/hr. */ function decodeRainRate(idx, buffer) { return buffer.readUInt16BE(idx) / 10.0; } /* Decode 4 byte rain data. Data is contained in a four byte big endian integer and represents tenths of a unit mm. */ function decodeBigRain(idx, buffer) { return buffer.readUInt32BE(idx) / 10.0; } /* Decode 4 byte light data. Data is contained in a four byte big endian integer and represents tenths of a unit (lux). */ function decodeLight(idx, buffer) { return buffer.readUInt32BE(idx) / 10.0; } /* Decode 2 byte UV data. Data is contained in a two byte big endian integer and represents tenths of a unit (µW/cm²). */ function decodeUV(idx, buffer) { return buffer.readUInt16BE(idx) / 10.0; } /* Decode UV Index. Data is contained in a single unsigned byte and represents whole units (0-15). */ function decodeUVI(idx, buffer) { return buffer.readInt8(idx); } /* Decode date-time data. Unknown format but length is six bytes. */ function decodeDatetime(idx, buffer) { return buffer.slice(idx, idx + 6); } /* Decode UTC time. The Gateway API claims to provide 'UTC time' as a 4 byte big endian integer. The 4 byte integer is a unix epoch timestamp; however, the timestamp is offset by the stations timezone. So for a station in the +10 hour timezone, the timestamp returned is the present epoch timestamp plus 10 * 3600 seconds. When decoded in localtime the decoded date-time is off by the station time zone, when decoded as GMT the date and time figures are correct but the timezone is incorrect. In any case decode the 4 byte big endian integer as is and any further use of this timestamp needs to take the above time zone offset into account when using the timestamp. */ function decodeUTC(idx, buffer) { var val = buffer.readUInt32BE(idx); return (val === 0xFFFFFFFF) ? null : val; } /* Decode combined temperature and battery status data. Data consists of three bytes; bytes 0 and 1 are normal temperature data and byte 3 is battery status data. */ function decodeTempBatt(idx, buffer) { return { temp: buffer.readInt16BE(idx), batt: buffer.readInt8(idx + 2) } } /* Decode lightning distance. Data is contained in a single byte integer and represents a value from 0 to 40km. */ function decodeDistance(idx, buffer) { var val = buffer.readInt8(idx); return val <= 40 ? val : null; } /* Decode lightning count. Count is an integer stored in a 4 byte big endian integer. */ function decodeCount(idx, buffer) { return buffer.readUInt32BE(idx); } /* Decode moist data. Data is contained in a single unsigned byte and represents whole units. */ function decodeMoist(idx, buffer) { return buffer.readInt8(idx); } /* Decode aq data. Data is contained in a two byte big endian integer and represents tenths of a unit. */ function decodeAq(idx, buffer) { return buffer.readUInt16BE(idx) / 10.0; } /* Decode leak data. Data is contained in a single unsigned byte and represents whole units. */ function decodeLeak(idx, buffer) { return buffer.readInt8(idx); } /* Decode Rain Reset Date & Time */ function decodeRstRain(idx, buffer) { const rstHr = buffer.readUInt8(idx); const rstDay = buffer.readInt8(idx + 1); const rstMonth = buffer.readUInt8(idx + 2); const MONTHS = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'November', 'December' ] return `${MONTHS[rstMonth]}, ${rstDay === 0 ? 'Sunday' : 'Monday'}, Hour: ${rstHr}`; } /* Decode Piezo Gain Field */ function decodeGain(idx, buffer) { return buffer.readUInt16BE(idx); } /* Decode Piezo Gain Fields */ function decodeGain10(idx, buffer) { var data = {} for (var i = 0; i < 10; i++) { data[`gain${i}`] = buffer.readUInt16BE(idx + i * 2); } return data; } /* Decode ug/m3 data */ function decodeUgm3(idx, buffer) { return buffer.readUInt16BE(idx) * 10; } /* Decode ug/m3 offset data */ function decodeUgm3Offset(idx, buffer) { return buffer.readInt16BE(idx) * 10; } /* Decode ppm data */ function decodePPM(idx, buffer) { return buffer.readUInt16BE(idx); } const MULTI_BATT = { 'wh40': 4, 'wh26': 5, 'wh25': 6, 'wh65': 7 //aka wh24 } /* Decode battery status data. Battery status data is provided in 16 bytes using a variety of representations. Different representations include: - use of a single bit to indicate low/OK - use of a nibble to indicate battery level - use of a byte to indicate battery voltage WH24, WH25, WH26(WH32), WH31, WH40, WH41 and WH51 stations/sensors use a single bit per station/sensor to indicate OK or low battery. WH55 and WH57 sensors use a single byte per sensor to indicate OK or low battery. WH68 and WS80 sensors use a single byte to store battery voltage. The battery status data is allocated as follows Byte # Sensor Value Comments byte 1 WH40(b4) 0/1 1=low, 0=normal WH26(WH32?)(b5) 0/1 1=low, 0=normal WH25(b6) 0/1 1=low, 0=normal WH24(b7) 0/1 may be WH65, 1=low, 0=normal 2 WH31 ch1(b0) 0/1 1=low, 0=normal, 8 channels b0..b7 ... ch8(b7) 0/1 1=low, 0=normal 3 WH51 ch1(b0) 0/1 1=low, 0=normal, 16 channels b0..b7 over 2 bytes ... ch8(b7) 0/1 1=low, 0=normal 4 ch9(b0) 0/1 1=low, 0=normal ... ch16(b7) 0/1 1=low, 0=normal 5 WH57 0-5 <=1 is low 6 WH68 0.02*value Volts 7 WS80 0.02*value Volts 8 Unused 9 WH41 ch1(b0-b3) 0-5 <=1 is low ch2(b4-b7) 0-5 <=1 is low 10 ch3(b0-b3) 0-5 <=1 is low ch4(b4-b7) 0-5 <=1 is low 11 WH55 ch1 0-5 <=1 is low 12 WH55 ch2 0-5 <=1 is low 13 WH55 ch3 0-5 <=1 is low 14 WH55 ch4 0-5 <=1 is low 15 Unused 16 Unused For stations/sensors using a single bit for battery status 0=OK and 1=low. For stations/sensors using a single byte for battery status >1=OK and <=1=low. For stations/sensors using a single byte for battery voltage the voltage is 0.02 * the byte value. # WH24 F/O THWR sensor station # WH25 THP sensor # WH26(WH32) TH sensor # WH40 rain gauge sensor */ function decodeBatt(idx, buffer, fieldSize, utils) { var status = {}; const batt_fields = (utils !== undefined && utils.batt_fields !== undefined) ? utils.batt_fields : BATT_FIELDS; batt_fields.forEach(([sensor, size, func, format]) => { const data = buffer.readUIntBE(idx, size); idx += size; if (func !== null) { func(status, sensor, data, size, format); } }); return status; } const wh41_batt = { 1: { 'shift': 0, 'mask': 0x0FFFF }, 2: { 'shift': 4, 'mask': 0x0FFF }, 3: { 'shift': 8, 'mask': 0x0FF }, 4: { 'shift': 12, 'mask': 0x0F } } const wh55_batt = { 1: { 'shift': 0, 'mask': 0xFF }, 2: { 'shift': 8, 'mask': 0xFF }, 3: { 'shift': 16, 'mask': 0xFF }, 4: { 'shift': 24, 'mask': 0xFF } } const BATT_FIELDS = [ ['multi', 1, battMultiMask, MULTI_BATT], ['wh31', 1, battMaskAll, null], ['wh51', 2, battMaskAll, null], ['wh57', 1, battVal, null], ['wh68', 1, battVolt, null], ['ws80', 1, battVolt, null], ['unused', 1, null, null], ['wh41', 2, battValSplit, wh41_batt], ['wh55', 4, battValEach, wh55_batt], ['unused', 1, null, null], ['unused', 1, null, null] ] function battMultiMask(status, multiSensor, data, size, format) { for (const [sensor, maskBit] of Object.entries(format)) { status[sensor] = (data & maskBit) == 1; } } function battMaskAll(status, sensor, data, size) { var sensorStatus = {}; for (var i = 0; i < 8 * size; i++) { sensorStatus[`ch${i + 1}`] = (data & i) == 1; } status[sensor] = sensorStatus; } function battVal(status, sensor, data, size, format) { status[sensor] = data >= 0 && data < 2; } function battValSplit(status, sensor, data, size, format) { var sensorStatus = {}; for (const [ch, f] of Object.entries(format)) { var _data = (f.shift != null) ? data >> f.shift : data; if (f.mask != null) { _data = (_data & f.mask); if (_data == f.mask) _data = false; } sensorStatus[`ch${ch}`] = _data; } status[sensor] = sensorStatus; } function battValEach(status, sensor, data, size, format) { var sensorStatus = {}; for (const [ch, f] of Object.entries(format)) { var _data = (f.shift != null) ? data >> f.shift : data; if (f.mask != null) { _data = (_data & f.mask); if (_data == f.mask) _data = false; } sensorStatus[`ch${ch}`] = _data; } status[sensor] = sensorStatus; } function battVolt(status, sensor, data, size) { status[sensor] = data < 256 ? false : data * 0.2; } module.exports = { DECODE: DECODE, MULTI_BATT: MULTI_BATT, BATT_FIELDS: BATT_FIELDS, decodeTemp: decodeTemp, decodeHumid: decodeHumid, decodePress: decodePress, decodeDir: decodeDir, decodeSpeed: decodeSpeed, decodeRain: decodeRain, decodeRainRate: decodeRainRate, decodeBigRain: decodeBigRain, decodeLight: decodeLight, decodeUV: decodeUV, decodeUVI: decodeUVI, decodeDatetime: decodeDatetime, decodeUTC: decodeUTC, decodeTempBatt: decodeTempBatt, decodeDistance: decodeDistance, decodeCount: decodeCount, decodeMoist: decodeMoist, decodeAq: decodeAq, decodeLeak: decodeLeak, decodeRstRain: decodeRstRain, decodeGain: decodeGain, decodeGain10: decodeGain10, decodeUgm3: decodeUgm3, decodeUgm3Offset: decodeUgm3Offset, decodePPM: decodePPM, decodeBatt: decodeBatt }