UNPKG

advlib-esp

Version:

Wireless advertising packet decoding library for EnOcean Alliance devices via the EnOcean Serial Protocol (ESP). We believe in an open Internet of Things.

297 lines (250 loc) 9.31 kB
/** * Copyright reelyActive 2022-2024 * We believe in an open Internet of Things */ const utils = require('./utils'); const MIN_LENGTH_BYTES = 6; /** * Process a raw RADIO_ERP1 packet. * @param {Buffer} data The raw packet. * @param {Array} libraries Optional ordered list of processing libraries. * @param {Object} options Optional processing options. * @return {Object} The processed packet. */ function process(data, libraries, options) { let buf = utils.convertToBuffer(data); if((buf === null) || (buf.length < MIN_LENGTH_BYTES)) { return null; } options = options || {}; let telegramProperties = {}; let profileProperties = {}; let includeProtocolOverhead = (options.ignoreProtocolOverhead !== true); let telegramType = determineType(buf.readUInt8(0)); let transmitterSignature; let eepType; if(includeProtocolOverhead) { telegramProperties.telegramType = telegramType; } switch(telegramType) { case '4BS': transmitterSignature = utils.parseDeviceSignature(buf.subarray(5, 9)); eepType = lookupEEPType(transmitterSignature, options.deviceProfiles); profileProperties = process4BSTelegram(buf, libraries, options, eepType); break; case 'ADT': transmitterSignature = utils.parseDeviceSignature( buf.subarray(buf.length - 5, buf.length - 1)); break; case 'MSC': transmitterSignature = utils.parseDeviceSignature( buf.subarray(buf.length - 5, buf.length - 1)); eepType = lookupEEPType(transmitterSignature, options.deviceProfiles); profileProperties = processMSCTelegram(buf, libraries, options, eepType); break; case 'VLD': transmitterSignature = utils.parseDeviceSignature( buf.subarray(buf.length - 5, buf.length - 1)); eepType = lookupEEPType(transmitterSignature, options.deviceProfiles); profileProperties = processVLDTelegram(buf, libraries, options, eepType); break; case 'UTE': transmitterSignature = utils.parseDeviceSignature( buf.subarray(buf.length - 5, buf.length - 1)); profileProperties = processUTETelegram(buf, libraries, options); break; case '1BS': transmitterSignature = utils.parseDeviceSignature(buf.subarray(2, 6)); profileProperties = process1BSTelegram(buf, libraries, options); break; case 'RPS': transmitterSignature = utils.parseDeviceSignature(buf.subarray(2, 6)); eepType = lookupEEPType(transmitterSignature, options.deviceProfiles); profileProperties = processRPSTelegram(buf, libraries, options, eepType); break; } if(transmitterSignature) { telegramProperties.deviceIds = [ transmitterSignature ]; } return Object.assign(telegramProperties, profileProperties); } /** * Lookup the EEP type for the given device from optional deviceProfiles. * @param {String} deviceSignature The id signature of the device to look up. * @param {Object} deviceProfiles Optional device profiles as Map or Object. * @return {String} The EEP type, or null if lookup fails. */ function lookupEEPType(deviceSignature, deviceProfiles) { let deviceProfile; if(deviceProfiles instanceof Map) { deviceProfile = deviceProfiles.get(deviceSignature); } else if(deviceProfiles instanceof Object) { deviceProfile = deviceProfiles[deviceSignature]; } if(deviceProfile) { return deviceProfile.eepType || null; } return null; } /** * Process a 4-byte sensor (4BS) telegram. * @param {Buffer} data The raw telegram. * @param {Array} libraries Optional ordered list of processing libraries. * @param {Object} options Optional processing options. * @param {String} eepType Optional EEP type to enable payload processing. * @return {Object} The data type properties. */ function process4BSTelegram(data, libraries, options, eepType) { let hasLibraries = (Array.isArray(libraries) && (libraries.length > 0)); if(hasLibraries) { for(let cLibrary = 0; cLibrary < libraries.length; cLibrary++) { let library = libraries[cLibrary]; if(utils.hasFunction(library, 'process4BSTelegram')) { let profileProperties = library.process4BSTelegram(eepType, data); if(profileProperties !== null) { return profileProperties; } } } } let payload = data.subarray(1, 5).toString('hex'); return { telegramPayload: payload }; } /** * Process a manufacturer-specific communication (MSC) telegram. * @param {Buffer} data The raw telegram. * @param {Array} libraries Optional ordered list of processing libraries. * @param {Object} options Optional processing options. * @param {String} eepType Optional EEP type to enable payload processing. * @return {Object} The data type properties. */ function processMSCTelegram(data, libraries, options, eepType) { let hasLibraries = (Array.isArray(libraries) && (libraries.length > 0)); if(hasLibraries) { for(let cLibrary = 0; cLibrary < libraries.length; cLibrary++) { let library = libraries[cLibrary]; if(utils.hasFunction(library, 'processMSCTelegram')) { let profileProperties = library.processMSCTelegram(eepType, data); if(profileProperties !== null) { return profileProperties; } } } } let payload = data.subarray(1, data.length - 5).toString('hex'); return { telegramPayload: payload }; } /** * Process a variable-length data (VLD) telegram. * @param {Buffer} data The raw telegram. * @param {Array} libraries Optional ordered list of processing libraries. * @param {Object} options Optional processing options. * @param {String} eepType Optional EEP type to enable payload processing. * @return {Object} The data type properties. */ function processVLDTelegram(data, libraries, options, eepType) { let hasLibraries = (Array.isArray(libraries) && (libraries.length > 0)); if(hasLibraries) { for(let cLibrary = 0; cLibrary < libraries.length; cLibrary++) { let library = libraries[cLibrary]; if(utils.hasFunction(library, 'processVLDTelegram')) { let profileProperties = library.processVLDTelegram(eepType, data); if(profileProperties !== null) { return profileProperties; } } } } let payload = data.subarray(1, data.length - 5).toString('hex'); return { telegramPayload: payload }; } /** * Process a universal teach-in (UTE) telegram. * @param {Buffer} data The raw telegram. * @param {Array} libraries Optional ordered list of processing libraries. * @param {Object} options Optional processing options. * @return {Object} The data type properties. */ function processUTETelegram(data, libraries, options) { let eepType = (data.toString('hex', 7, 8) + '-' + data.toString('hex', 6, 7) + '-' + data.toString('hex', 5, 6)).toUpperCase(); let payload = data.subarray(1, 8).toString('hex'); return { eepType: eepType, telegramPayload: payload }; } /** * Process a 1-byte sensor (1BS) telegram. * @param {Buffer} data The raw telegram. * @param {Array} libraries Optional ordered list of processing libraries. * @param {Object} options Optional processing options. * @return {Object} The data type properties. */ function process1BSTelegram(data, libraries, options) { let isContactDetected = [ (data.readUInt8(1) & 0x01) === 0x01 ]; return { isContactDetected: isContactDetected }; } /** * Process a rocker position switch (RPS) telegram. * @param {Buffer} data The raw telegram. * @param {Array} libraries Optional ordered list of processing libraries. * @param {Object} options Optional processing options. * @param {String} eepType Optional EEP type to enable payload processing. * @return {Object} The data type properties. */ function processRPSTelegram(data, libraries, options, eepType) { let hasLibraries = (Array.isArray(libraries) && (libraries.length > 0)); if(hasLibraries) { for(let cLibrary = 0; cLibrary < libraries.length; cLibrary++) { let library = libraries[cLibrary]; if(utils.hasFunction(library, 'processRPSTelegram')) { let profileProperties = library.processRPSTelegram(eepType, data); if(profileProperties !== null) { return profileProperties; } } } } let payload = data.subarray(1, 2).toString('hex'); return { telegramPayload: payload }; } /** * Determine the radio packet type from its R-ORG code. * @param {Number} type The type as an integer between 0-255. * @return {String} The packet type as a string. */ function determineType(type) { switch(type) { case 0x30: return 'SEC'; case 0x31: return 'SEC_ENCAPS'; case 0x34: return 'SEC_MAN'; case 0xa5: return '4BS'; case 0xa6: return 'ADT'; case 0xa7: return 'SM_REQ'; case 0xc5: return 'SYS_EX'; case 0xc6: return 'SM_LRN_REQ'; case 0xc7: return 'SM_LRN_ANS'; case 0xd0: return 'SIGNAL'; case 0xd1: return 'MSC'; case 0xd2: return 'VLD'; case 0xd4: return 'UTE'; case 0xd5: return '1BS'; case 0xf6: return 'RPS'; } } module.exports.process = process;