node-enocean
Version:
full implementation of the enocean protocol (ESP3 + EEP2.6)
202 lines (194 loc) • 10.4 kB
JavaScript
// This file is part of node-enocean.
// node-enocean. is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// node-enocean. is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with node-enocean. If not, see <http://www.gnu.org/licenses/>.
// # EnOcean ESP 3.0 implementation
// this module extracts basic info from telegrams which are passed here as a Buffer
var M=[]
M[0x000]="MANUFACTURER_RESERVED"
M[0x001]="PEHA"
M[0x002]="THERMOKON"
M[0x003]="SERVODAN"
M[0x004]="ECHOFLEX_SOLUTIONS"
M[0x005]="OMNIO_AG"
M[0x006]="HARDMEIER_ELECTRONICS"
M[0x007]="REGULVAR_INC"
M[0x008]="AD_HOC_ELECTRONICS"
M[0x009]="DISTECH_CONTROLS"
M[0x00A]="KIEBACK_AND_PETER"
M[0x00B]="ENOCEAN_GMBH"
M[0x00C]="PROBARE"
M[0x00D]="ELTAKO"
M[0x00E]="LEVITON"
M[0x00F]="HONEYWELL"
M[0x010]="SPARTAN_PERIPHERAL_DEVICES"
M[0x011]="SIEMENS"
M[0x012]="T_MAC"
M[0x013]="RELIABLE_CONTROLS_CORPORATION"
M[0x014]="ELSNER_ELEKTRONIK_GMBH"
M[0x015]="DIEHL_CONTROLS"
M[0x016]="BSC_COMPUTER"
M[0x017]="S_AND_S_REGELTECHNIK_GMBH"
M[0x018]="MASCO_CORPORATION"
M[0x019]="INTESIS_SOFTWARE_SL"
M[0x01A]="VIESSMANN"
M[0x01B]="LUTUO_TECHNOLOGY"
M[0x01C]="SCHNEIDER_ELECTRIC"
M[0x01D]="SAUTER"
M[0x01E]="BOOT_UP"
M[0x01F]="OSRAM_SYLVANIA"
M[0x020]="UNOTECH"
M[0x21]="DELTA_CONTROLS_INC"
M[0x022]="UNITRONIC_AG"
M[0x023]="NANOSENSE"
M[0x024]="THE_S4_GROUP"
M[0x025]="MSR_SOLUTIONS"
M[0x26]="GE"
M[0x027]="MAICO"
M[0x28]="RUSKIN_COMPANY"
M[0x29]="MAGNUM_ENERGY_SOLUTIONS"
M[0x02A]="KMC_CONTROLS"
M[0x02B]="ECOLOGIX_CONTROLS"
M[0x2C]="TRIO_2_SYS"
M[0x02D]="AFRISO_EURO_INDEX"
M[0x030]="NEC_ACCESSTECHNICA_LTD"
M[0x031]="ITEC_CORPORATION"
M[0x32]="SIMICX_CO_LTD"
M[0x34]="EUROTRONIC_TECHNOLOGY_GMBH"
M[0x35]="ART_JAPAN_CO_LTD"
M[0x36]="TIANSU_AUTOMATION_CONTROL_SYSTE_CO_LTD"
M[0x38]="GRUPPO_GIORDANO_IDEA_SPA"
M[0x39]="ALPHAEOS_AG"
M[0x3A]="TAG_TECHNOLOGIES"
M[0x3C]="PRESSAC"
M[0x3E]="GIGA_CONCEPT"
M[0x3F]="SENSORTEC"
M[0x40]="JAEGER_DIREKT"
M[0x41]="AIR_SYSTEM_COMPONENTS_INC"
M[0x46]="NODON"
M[0x7F]="MULTI_USER_MANUFACTURER"
var Manufacturer_List = M
// [ // the List of Manufacturers. the index is equal to the number transmitted in learn telegrams
// 'MANUFACTURER_RESERVED' ,'PEHA','THERMOKON','SERVODAN','ECHOFLEX_SOLUTIONS','OMNIO_AG','HARDMEIER_ELECTRONICS','REGULVAR_INC','AD_HOC_ELECTRONICS',
// 'DISTECH_CONTROLS','KIEBACK_AND_PETER','ENOCEAN_GMBH','PROBARE','ELTAKO','LEVITON','HONEYWELL','SPARTAN_PERIPHERAL_DEVICES','SIEMENS','T_MAC',
// 'RELIABLE_CONTROLS_CORPORATION','ELSNER_ELEKTRONIK_GMBH','DIEHL_CONTROLS','BSC_COMPUTER','S_AND_S_REGELTECHNIK_GMBH','MASCO_CORPORATION','INTESIS_SOFTWARE_SL',
// 'VIESSMANN','LUTUO_TECHNOLOGY','SCHNEIDER_ELECTRIC','SAUTER','BOOT_UP','OSRAM_SYLVANIA','UNOTECH','ELTA_CONTROLS_INC','UNITRONIC_AG','NANOSENSE',
// 'THE_S4_GROUP','MSR_SOLUTIONS','GE','MAICO','RUSKIN_COMPANY','MAGNUM_ENERGY_SOLUTIONS','KMC_CONTROLS','ECOLOGIX_CONTROLS','TRIO_2_SYS','AFRISO_EURO_INDEX',
// 'NEC_ACCESSTECHNICA_LTD','ITEC_CORPORATION','SIMICX_CO_LTD','EUROTRONIC_TECHNOLOGY_GMBH','ART_JAPAN_CO_LTD','TIANSU_AUTOMATION_CONTROL_SYSTE_CO_LTD',
// 'GRUPPO_GIORDANO_IDEA_SPA','ALPHAEOS_AG','TAG_TECHNOLOGIES','CLOUD_BUILDINGS_LTD','GIGA_CONCEPT','SENSORTEC','JAEGER_DIREKT','AIR_SYSTEM_COMPONENTS_INC'
// ]
module.exports = function enocean_Telegram( ) {
this.timestamp = Date.now()
this.loadFromBuffer = function( buf ) {
this.rawByte = buf.toString( "hex" ) // store the original Buffer as a string in .rawByte
var dataLength = 255 * buf[ 1 ] + buf[ 2 ] // length of the Data Part of the telegram
var optionalLength = buf[ 3 ] // length of the optional data part of the telegram
this.packetType = buf[ 4 ] // packet type ( 1=radio telegram , 2=response... )
var headerCRC = buf[ 5 ] // checksum of the header
var rawDataByte = buf.slice( 7 , dataLength + 7 ) // Data byte start at Byte 7 and end at 6 + dataLength
switch( this.packetType ) {
case 2 :
// RESPONSES are not really supportet yet (2.0?) the only propper use here is for getting the base address
this.packetTypeString = "RESPONSE"
this.returnCode = buf[ 6 ]
var rc_string = [ "RET_OK" , "RET_ERROR" , "RET_NOT_SUPPORTED" , "RET_WRONG_PARAM" , "RET_OPERATION_DENIED" ]
if( this.returnCode < 128 ) { this.returnCodeString = rc_string[ this.returnCode ] }
if( dataLength == 5 ) {
// not really correct, but for now assume that RESPNSES with a length of 5 contain the base address
this.base = buf.slice( 7 , 11 ).toString( "hex" )
}
this.raw = rawDataByte
break
case 1 :
// this is a RADIO telegram. senderId and choice are part of every radio telegram
this.senderId = buf.slice( dataLength + 1 , dataLength + 5 ).toString( "hex" ) // should we rename this to sensorId? needs a bit of refactoring...
this.choice = buf[ 6 ].toString( 16 ) // choice equals the RORG. for now 4BS (a5), RPS(f6) and 1BS(d5) are supported
var optionalData = buf.slice(dataLength+6,buf.length-1)
this.subTelNum = optionalData[0]
this.destinationId = optionalData.slice(1,5).toString("hex")
this.rssi = optionalData[5]
this.securityLevel = optionalData[6]
switch( this.choice ) {
case "a5" :
// this is a 4BS (4 Byte Communication) Telegram. the data part is 4 bytes long
this.packetTypeString = "4BS"
this.raw = pad( buf.slice( 7 , 11 ).toString( "hex" ) , 8 ) // the data part as a hex string
var rawNr = parseInt( buf.slice( 7 , 11 ).toString( "hex" ) , 16 ) // the data part as decimal number
//extract the learn bit
// Bit 3 (so the 4th Bit) (0b1000=8) is the learnBit. if it is 0 (f.e. 10111) then this is a learn telegram
this.learnBit = ( rawNr & 8 ) >>> 3
// allways use zero fill bit shifts to prevent accidetial negative numbers, because in js INTs are signed
if( this.learnBit == 0 ) {
// the following extractions could be written shorter, but this way ist more clear what goes on
var func = ( ( parseInt( "11111100000000000000000000000000" , 2 ) & rawNr ) >>> 26 ).toString( 16 ) // in the first 6 bits the func part of the eep is stored
var type = ( ( parseInt( "00000011111110000000000000000000" , 2 ) & rawNr ) >>> 19 ).toString( 16 ) // in the next 7 bits the type part of the eep can be found
var MANUFACTURERID = ( parseInt( "00000000000001111111111100000000" , 2 ) & rawNr ) >>> 8 // the next 11 bit contain the manufacturer id, leaving 8 bits (Byte0 of the telegram unused)
// convert the eep into a string of the form "a5-ff-tt" where ff is the func in hex and tt is the type in hex
// pad the values with leading zeros so the numbers are allways 2 digits
this.eep = "a5-" + pad( func , 2 ) + "-" + pad( type , 2 )
this.manufacturer = Manufacturer_List[ MANUFACTURERID ] // look up the name of the manufacturer
}
break
case "f6" :
// this is an RPS Packet. It just contains 1 data byte, there is no learnBit
this.raw = pad( buf[ 7 ].toString( 16 ), 2 ) // extract the data byte (Byte0) as a padded hex string
this.packetTypeString = "RPS" // its an RPS telegram (rocker switch)
break
case "d5" :
// this is a 1BS telegram. it also contains only one data byte, but it also carries a learn bit
this.raw = pad( buf[ 7 ].toString( 16 ) , 2 ) // extract the data byte (Byte0) as a padded hex string
// Bit 3 (so the 4th Bit) (0b1000=8) is the learnBit. if it is 0 (f.e. 10111) then this is a learn telegram
this.learnBit = ( parseInt( this.raw , 16 ) & 8 ) >>> 3
this.packetTypeString = "1BS"
break
case "d2" :
// this is a VLD telegram.
this.raw = pad(rawDataByte.toString("hex"),dataLength*2 )// extract the data byte (Byte0) as a padded hex string
// Bit 3 (so the 4th Bit) (0b1000=8) is the learnBit. if it is 0 (f.e. 10111) then this is a learn telegram
this.learnBit = 1
this.packetTypeString = "VLD"
break
case "d1" :
// this is a MSC telegram.
this.raw = pad(rawDataByte.toString("hex"),dataLength*2 )// extract the data byte (Byte0) as a padded hex string
// Bit 3 (so the 4th Bit) (0b1000=8) is the learnBit. if it is 0 (f.e. 10111) then this is a learn telegram
this.learnBit = rawDataByte[dataLength-1] & 8
if(this.learnBit==0){
var func = pad(rawDataByte[0].toString(16),2)
var type = pad(rawDataByte[1].toString(16),2)
this.eep = `d1-${func}-${type}`
this.manufacturerid=parseInt("0x"+func+""+type[0])
this.manufacturer=Manufacturer_List[parseInt("0x"+func+""+type[0])]
}
this.packetTypeString = "MSC"
break
case "d4" :
// this is an UTE Telegram (Universal Teach In)
this.raw = pad(rawDataByte.toString("hex"),dataLength*2 )// extract the data byte (Byte0) as a padded hex string
this.learnBit = 0
this.packetTypeString = "UTE"
var type = this.raw.substring(8,10)
var func = this.raw.substring(10,12)
var choice = this.raw.substring(12,14)
var MANUFACTURERID = parseInt(this.raw .substring(4,6),16)
this.manufacturerid=MANUFACTURERID
this.eep = choice+"-" + pad( func , 2 ) + "-" + pad( type , 2 )
this.manufacturer = Manufacturer_List[ MANUFACTURERID ]
break
}
break
}
}
}
// a helper function repeated here, so this module does not require anything
function pad( num , size ) { // fill a string with leading zeros up to size
var s = "00000000000000000000000000000000" + num // maximum number of zero we need
return s.substr( s.length - size ) // cut to size
}