UNPKG

knx

Version:

KNXnet/IP protocol implementation for Node(>=6.x)

697 lines (667 loc) 29 kB
/** * knx.js - a KNX protocol stack in pure Javascript * (C) 2016-2018 Elias Karakoulakis */ const util = require('util'); const ipaddr = require('ipaddr.js'); const Parser = require('binary-parser').Parser; const BinaryProtocol = require('binary-protocol'); const KnxProtocol = new BinaryProtocol(); const KnxAddress = require('./Address'); const KnxConstants = require('./KnxConstants'); const KnxLog = require('./KnxLog'); // defaults KnxProtocol.twoLevelAddressing = false; KnxProtocol.lengths = {}; // TODO: Can this be a local variable, do we need to expose it? // helper function: what is the byte length of an object? const knxlen = (objectName, context) => { const lf = KnxProtocol.lengths[objectName]; return typeof lf === 'function' ? lf(context) : lf; }; KnxProtocol.define('IPv4Endpoint', { read(propertyName) { this.pushStack({ addr: null, port: null }) .raw('addr', 4) .UInt16BE('port') .tap((hdr) => { hdr.addr = ipaddr.fromByteArray(hdr.addr); }) .popStack(propertyName, (data) => data.addr.toString() + ':' + data.port); }, write(value) { if (!value) throw 'cannot write null value for IPv4Endpoint'; if (typeof value !== 'string' || !value.match(/\d*\.\d*\.\d*\.\d*:\d*/)) throw "Invalid IPv4 endpoint, please set a string as 'ip.add.re.ss:port'"; const [addr, port] = value.split(':'); this.raw(Buffer.from(ipaddr.parse(addr).toByteArray()), 4); this.UInt16BE(port); }, }); KnxProtocol.lengths['IPv4Endpoint'] = (value) => (value ? 6 : 0); /* CRI: connection request/response */ // creq[22] = 0x04; /* structure len (4 bytes) */ // creq[23] = 0x04; /* connection type: DEVICE_MGMT_CONNECTION = 0x03; TUNNEL_CONNECTION = 0x04; */ // creq[24] = 0x02; /* KNX Layer (Tunnel Link Layer) */ // creq[25] = 0x00; /* Reserved */ // ==> 4 bytes KnxProtocol.define('CRI', { read(propertyName) { this.pushStack({ header_length: 0, connection_type: null, knx_layer: null, unused: null, }) // .UInt8('header_length') .UInt8('connection_type') .UInt8('knx_layer') .UInt8('unused') .tap((hdr) => { switch (hdr.connection_type) { case KnxConstants.CONNECTION_TYPE.DEVICE_MGMT_CONNECTION: break; // TODO case KnxConstants.CONNECTION_TYPE.TUNNEL_CONNECTION: break; // TODO default: throw 'Unsupported connection type: ' + hdr.connection_type; } }) .popStack(propertyName, (data) => { if (KnxProtocol.debug) KnxLog.get().debug('read CRI: ' + JSON.stringify(data)); // pop the interim value off the stack and insert the real value into `propertyName` return data; }); }, write(value) { if (!value) return KnxLog.get().warn('CRI: cannot write null value for CRI'); this.UInt8(0x04) // length .UInt8(value.connection_type) .UInt8(value.knx_layer) .UInt8(value.unused); }, }); KnxProtocol.lengths['CRI'] = (value) => (value ? 4 : 0); // connection state response/request KnxProtocol.define('ConnState', { read(propertyName) { this.pushStack({ channel_id: null, status: null }) .UInt8('channel_id') .UInt8('status') .popStack(propertyName, (data) => { if (KnxProtocol.debug) KnxLog.get().trace('read ConnState: %j', data); return data; }); }, write(value) { if (!value) return KnxLog.get().error('cannot write null value for ConnState'); this.UInt8(value.channel_id).UInt8(value.status); }, }); KnxProtocol.lengths['ConnState'] = (value) => (value ? 2 : 0); // connection state response/request KnxProtocol.define('TunnState', { read(propertyName) { this.pushStack({ header_length: null, channel_id: null, seqnum: null, rsvd: null, }) .UInt8('header_length') .UInt8('channel_id') .UInt8('seqnum') .UInt8('rsvd') .tap((hdr) => { if (KnxProtocol.debug) KnxLog.get().trace('reading TunnState: %j', hdr); switch (hdr.status) { case 0x00: break; //default: throw "Connection State status: " + hdr.status; } }) .popStack(propertyName, (data) => data); }, write(value) { if (!value) return KnxLog.get().error( 'TunnState: cannot write null value for TunnState' ); if (KnxProtocol.debug) KnxLog.get().trace('writing TunnState: %j', value); this.UInt8(0x04) .UInt8(value.channel_id) .UInt8(value.seqnum) .UInt8(value.rsvd); }, }); KnxProtocol.lengths['TunnState'] = (value) => (value ? 4 : 0); /* Connection HPAI */ // creq[6] = /* Host Protocol Address Information (HPAI) Lenght */ // creq[7] = /* IPv4 protocol UDP = 0x01, TCP = 0x02; */ // creq[8-11] = /* IPv4 address */ // creq[12-13] = /* IPv4 local port number for CONNECTION, CONNECTIONSTAT and DISCONNECT requests */ // ==> 8 bytes /* Tunneling HPAI */ // creq[14] = /* Host Protocol Address Information (HPAI) Lenght */ // creq[15] = /* IPv4 protocol UDP = 0x01, TCP = 0x02; */ // creq[16-19] = /* IPv4 address */ // creq[20-21] = /* IPv4 local port number for TUNNELING requests */ // ==> 8 bytes KnxProtocol.define('HPAI', { read(propertyName) { this.pushStack({ header_length: 8, protocol_type: null, tunnel_endpoint: null, }) .UInt8('header_length') .UInt8('protocol_type') .IPv4Endpoint('tunnel_endpoint') .tap(function (hdr) { if (this.buffer.length < hdr.header_length) { if (KnxProtocol.debug) KnxLog.get().trace( '%d %d %d', this.buffer.length, this.offset, hdr.header_length ); throw 'Incomplete KNXNet HPAI header'; } if (KnxProtocol.debug) { KnxLog.get().trace( 'read HPAI: %j, proto = %s', hdr, KnxConstants.keyText('PROTOCOL_TYPE', hdr.protocol_type) ); } switch (hdr.protocol_type) { case KnxConstants.PROTOCOL_TYPE.IPV4_TCP: throw 'TCP is not supported'; default: } }) .popStack(propertyName, (data) => data); }, write(value) { if (!value) return KnxLog.get().error('HPAI: cannot write null value for HPAI'); this.UInt8(0x08) // length: 8 bytes .UInt8(value.protocol_type) .IPv4Endpoint(value.tunnel_endpoint); }, }); KnxProtocol.lengths['HPAI'] = (value) => { return value ? 8 : 0; }; /* ==================== APCI ====================== */ // // Message Code = 0x11 - a L_Data.req primitive // COMMON EMI MESSAGE CODES FOR DATA LINK LAYER PRIMITIVES // FROM NETWORK LAYER TO DATA LINK LAYER // +---------------------------+--------------+-------------------------+---------------------+------------------+ // | Data Link Layer Primitive | Message Code | Data Link Layer Service | Service Description | Common EMI Frame | // +---------------------------+--------------+-------------------------+---------------------+------------------+ // | L_Raw.req | 0x10 | | | | // +---------------------------+--------------+-------------------------+---------------------+------------------+ // | | | | Primitive used for | Sample Common | // | L_Data.req | 0x11 | Data Service | transmitting a data | EMI frame | // | | | | frame | | // +---------------------------+--------------+-------------------------+---------------------+------------------+ // | L_Poll_Data.req | 0x13 | Poll Data Service | | | // +---------------------------+--------------+-------------------------+---------------------+------------------+ // | L_Raw.req | 0x10 | | | | // +---------------------------+--------------+-------------------------+---------------------+------------------+ // FROM DATA LINK LAYER TO NETWORK LAYER // +---------------------------+--------------+-------------------------+---------------------+ // | Data Link Layer Primitive | Message Code | Data Link Layer Service | Service Description | // +---------------------------+--------------+-------------------------+---------------------+ // | L_Poll_Data.con | 0x25 | Poll Data Service | | // +---------------------------+--------------+-------------------------+---------------------+ // | | | | Primitive used for | // | L_Data.ind | 0x29 | Data Service | receiving a data | // | | | | frame | // +---------------------------+--------------+-------------------------+---------------------+ // | L_Busmon.ind | 0x2B | Bus Monitor Service | | // +---------------------------+--------------+-------------------------+---------------------+ // | L_Raw.ind | 0x2D | | | // +---------------------------+--------------+-------------------------+---------------------+ // | | | | Primitive used for | // | | | | local confirmation | // | L_Data.con | 0x2E | Data Service | that a frame was | // | | | | sent (does not mean | // | | | | successful receive) | // +---------------------------+--------------+-------------------------+---------------------+ // | L_Raw.con | 0x2F | | | // +---------------------------+--------------+-------------------------+---------------------+ // Add.Info Length = 0x00 - no additional info // Control Field 1 = see the bit structure above // Control Field 2 = see the bit structure above // Source Address = 0x0000 - filled in by router/gateway with its source address which is // part of the KNX subnet // Dest. Address = KNX group or individual address (2 byte) // Data Length = Number of bytes of data in the APDU excluding the TPCI/APCI bits // APDU = Application Protocol Data Unit - the actual payload including transport // protocol control information (TPCI), application protocol control // information (APCI) and data passed as an argument from higher layers of // the KNX communication stack /* ==================== CEMI ====================== */ // CEMI (start at position 6) // +--------+--------+--------+--------+----------------+----------------+--------+----------------+ // | Msg |Add.Info| Ctrl 1 | Ctrl 2 | Source Address | Dest. Address | Data | APDU | // | Code | Length | | | | | Length | | // +--------+--------+--------+--------+----------------+----------------+--------+----------------+ // 1 byte 1 byte 1 byte 1 byte 2 bytes 2 bytes 1 byte 2 bytes /* Control Field 1 Bit | ------+--------------------------------------------------------------- 7 | Frame Type - 0x0 for extended frame | 0x1 for standard frame ------+--------------------------------------------------------------- 6 | Reserved ------+--------------------------------------------------------------- 5 | Repeat Flag - 0x0 repeat frame on medium in case of an error | 0x1 do not repeat ------+--------------------------------------------------------------- 4 | System Broadcast - 0x0 system broadcast | 0x1 broadcast ------+--------------------------------------------------------------- 3 | Priority - 0x0 system | 0x1 normal ------+ 0x2 urgent 2 | service_type: -1, 0x3 low ------+--------------------------------------------------------------- 1 | Acknowledge Request - 0x0 no ACK requested | (L_Data.req) 0x1 ACK requested ------+--------------------------------------------------------------- 0 | Confirm - 0x0 no error | (L_Data.con) - 0x1 error ------+--------------------------------------------------------------- Control Field 2 Bit | ------+--------------------------------------------------------------- 7 | Destination Address Type - 0x0 physical address, 0x1 group address ------+--------------------------------------------------------------- 6-4 | Hop Count (0-7) ------+--------------------------------------------------------------- 3-0 | Extended Frame Format - 0x0 standard frame ------+--------------------------------------------------------------- */ // In the Common EMI frame, the APDU payload is defined as follows: // +--------+--------+--------+--------+--------+ // | TPCI + | APCI + | Data | Data | Data | // | APCI | Data | | | | // +--------+--------+--------+--------+--------+ // byte 1 byte 2 byte 3 ... byte 16 // For data that is 6 bits or less in length, only the first two bytes are used in a Common EMI // frame. Common EMI frame also carries the information of the expected length of the Protocol // Data Unit (PDU). Data payload can be at most 14 bytes long. <p> // The first byte is a combination of transport layer control information (TPCI) and application // layer control information (APCI). First 6 bits are dedicated for TPCI while the two least // significant bits of first byte hold the two most significant bits of APCI field, as follows: // Bit 1 Bit 2 Bit 3 Bit 4 Bit 5 Bit 6 Bit 7 Bit 8 Bit 1 Bit 2 // +--------+--------+--------+--------+--------+--------+--------+--------++--------+----.... // | | | | | | | | || | // | TPCI | TPCI | TPCI | TPCI | TPCI | TPCI | APCI | APCI || APCI | // | | | | | | |(bit 1) |(bit 2) ||(bit 3) | // +--------+--------+--------+--------+--------+--------+--------+--------++--------+----.... // + B Y T E 1 || B Y T E 2 // +-----------------------------------------------------------------------++-------------.... //Total number of APCI control bits can be either 4 or 10. The second byte bit structure is as follows: // Bit 1 Bit 2 Bit 3 Bit 4 Bit 5 Bit 6 Bit 7 Bit 8 Bit 1 Bit 2 // +--------+--------+--------+--------+--------+--------+--------+--------++--------+----.... // | | | | | | | | || | // | APCI | APCI | APCI/ | APCI/ | APCI/ | APCI/ | APCI/ | APCI/ || Data | Data // |(bit 3) |(bit 4) | Data | Data | Data | Data | Data | Data || | // +--------+--------+--------+--------+--------+--------+--------+--------++--------+----.... // + B Y T E 2 || B Y T E 3 // +-----------------------------------------------------------------------++-------------.... // control field const ctrlStruct = new Parser() // byte 1 .bit1('frameType') .bit1('reserved') .bit1('repeat') .bit1('broadcast') .bit2('priority') .bit1('acknowledge') .bit1('confirm') // byte 2 .bit1('destAddrType') .bit3('hopCount') .bit4('extendedFrame'); // APDU: 2 bytes, tcpi = 6 bits, apci = 4 bits, remaining 6 bits = data (when length=1) KnxProtocol.apduStruct = new Parser().bit6('tpci').bit4('apci').bit6('data'); KnxProtocol.define('APDU', { read(propertyName) { this.pushStack({ apdu_length: null, apdu_raw: null, tpci: null, apci: null, data: null, }) .UInt8('apdu_length') .tap(function (hdr) { //if (KnxProtocol.debug) KnxLog.get().trace('--- parsing extra %d apdu bytes', hdr.apdu_length+1); this.raw('apdu_raw', hdr.apdu_length + 1); }) .tap((hdr) => { // Parse the APDU. tcpi/apci bits split across byte boundary. // Typical example of protocol designed by committee. const apdu = KnxProtocol.apduStruct.parse(hdr.apdu_raw); hdr.tpci = apdu.tpci; hdr.apci = KnxConstants.APCICODES[apdu.apci]; // APDU data should ALWAYS be a buffer, even for 1-bit payloads hdr.data = hdr.apdu_length > 1 ? hdr.apdu_raw.slice(2) : Buffer.from([apdu.data]); if (KnxProtocol.debug) KnxLog.get().trace(' unmarshalled APDU: %j', hdr); }) .popStack(propertyName, (data) => data); }, write(value) { if (!value) throw 'cannot write null APDU value'; const total_length = knxlen('APDU', value); //if (KnxProtocol.debug) KnxLog.get().trace('APDU.write: \t%j (total %d bytes)', value, total_length); if (KnxConstants.APCICODES.indexOf(value.apci) == -1) return KnxLog.get().error('invalid APCI code: %j', value); if (total_length < 3) throw util.format('APDU is too small (%d bytes)', total_length); if (total_length > 17) throw util.format('APDU is too big (%d bytes)', total_length); // camel designed by committee: total length MIGHT or MIGHT NOT include the payload // APDU length (1 byte) + TPCI/APCI: 6+4 bits + DATA: 6 bits (2 bytes) // OR: APDU length (1 byte) + TPCI/APCI: 6+4(+6 unused) bits (2bytes) + DATA: (1 to 14 bytes)) this.UInt8(total_length - 2); let word = value.tpci * 0x400 + KnxConstants.APCICODES.indexOf(value.apci) * 0x40; // if (total_length == 3) { // payload embedded in the last 6 bits word += parseInt( isFinite(value.data) && typeof value.data !== 'object' ? value.data : value.data[0] ); this.UInt16BE(word); } else { this.UInt16BE(word); // payload follows TPCI+APCI word // KnxLog.get().trace('~~~%s, %j, %d', typeof value.data, value.data, total_length); this.raw(Buffer.from(value.data, total_length - 3)); } }, }); /* APDU length is truly chaotic: header and data can be interleaved (but not always!), so that apdu_length=1 means _2_ bytes following the apdu_length */ KnxProtocol.lengths['APDU'] = (value) => { if (!value) return 0; // if we have the APDU bitlength, usually by the DPT, then simply use it if (value.bitlength || (value.data && value.data.bitlength)) { const bitlen = value.bitlength || value.data.bitlength; // KNX spec states that up to 6 bits of payload must fit into the TPCI // if payload larger than 6 bits, than append it AFTER the TPCI return 3 + (bitlen > 6 ? Math.ceil(bitlen / 8) : 0); } // not all requests carry a value; eg read requests if (!value.data) value.data = 0; if (value.data.length) { if (value.data.length < 1) throw 'APDU value is empty'; if (value.data.length > 14) throw 'APDU value too big, must be <= 14 bytes'; if (value.data.length == 1) { const v = value.data[0]; if (!isNaN(parseFloat(v)) && isFinite(v) && v >= 0 && v <= 63) { // apdu_length + tpci/apci/6-bit integer == 1+2 bytes return 3; } } return 3 + value.data.length; } else { if ( !isNaN(parseFloat(value.data)) && isFinite(value.data) && value.data >= 0 && value.data <= 63 ) { return 3; } else { KnxLog.get().warn( 'Fix your code - APDU data payload must be a 6-bit int or an Array/Buffer (1 to 14 bytes), got: %j (%s)', value.data, typeof value.data ); throw 'APDU payload must be a 6-bit int or an Array/Buffer (1 to 14 bytes)'; } } }; KnxProtocol.define('CEMI', { read(propertyName) { this.pushStack({ msgcode: 0, addinfo_length: -1, ctrl: null, src_addr: null, dest_addr: null, apdu: null, }) .UInt8('msgcode') .UInt8('addinfo_length') .tap(function (hdr) { if (hdr.addinfo_length !== 0) { this.raw('addinfo', hdr.addinfo_length); } }) .raw('ctrl', 2) .raw('src_addr', 2) .raw('dest_addr', 2) .tap(function (hdr) { // parse 16bit control field hdr.ctrl = ctrlStruct.parse(hdr.ctrl); // KNX source addresses are always physical hdr.src_addr = KnxAddress.toString( hdr.src_addr, KnxAddress.TYPE.PHYSICAL ); hdr.dest_addr = KnxAddress.toString( hdr.dest_addr, hdr.ctrl.destAddrType ); switch (hdr.msgcode) { case KnxConstants.MESSAGECODES['L_Data.req']: case KnxConstants.MESSAGECODES['L_Data.ind']: case KnxConstants.MESSAGECODES['L_Data.con']: { this.APDU('apdu'); if (KnxProtocol.debug) KnxLog.get().trace('--- unmarshalled APDU ==> %j', hdr.apdu); } } }) .popStack(propertyName, (data) => data); }, write(value) { if (!value) throw 'cannot write null CEMI value'; if (KnxProtocol.debug) KnxLog.get().trace('CEMI.write: \n\t%j', value); if (value.ctrl === null) throw 'no Control Field supplied'; const ctrlField1 = value.ctrl.frameType * 0x80 + value.ctrl.reserved * 0x40 + value.ctrl.repeat * 0x20 + value.ctrl.broadcast * 0x10 + value.ctrl.priority * 0x04 + value.ctrl.acknowledge * 0x02 + value.ctrl.confirm; const ctrlField2 = value.ctrl.destAddrType * 0x80 + value.ctrl.hopCount * 0x10 + value.ctrl.extendedFrame; this.UInt8(value.msgcode) .UInt8(value.addinfo_length) .UInt8(ctrlField1) .UInt8(ctrlField2) .raw(KnxAddress.parse(value.src_addr, KnxAddress.TYPE.PHYSICAL), 2) .raw(KnxAddress.parse(value.dest_addr, value.ctrl.destAddrType), 2); // only need to marshal an APDU if this is a // L_Data.* (requet/indication/confirmation) switch (value.msgcode) { case KnxConstants.MESSAGECODES['L_Data.req']: case KnxConstants.MESSAGECODES['L_Data.ind']: case KnxConstants.MESSAGECODES['L_Data.con']: { if (value.apdu === null) throw 'no APDU supplied'; this.APDU(value.apdu); } } }, }); KnxProtocol.lengths['CEMI'] = (value) => { if (!value) return 0; const apdu_length = knxlen('APDU', value.apdu); if (KnxProtocol.debug) KnxLog.get().trace('knxlen of cemi: %j == %d', value, 8 + apdu_length); return 8 + apdu_length; }; KnxProtocol.define('KNXNetHeader', { read(propertyName) { this.pushStack({ header_length: 0, protocol_version: -1, service_type: -1, total_length: 0, }) .UInt8('header_length') .UInt8('protocol_version') .UInt16BE('service_type') .UInt16BE('total_length') .tap(function (hdr) { if (KnxProtocol.debug) KnxLog.get().trace('read KNXNetHeader :%j', hdr); if (this.buffer.length + hdr.header_length < this.total_length) throw util.format( 'Incomplete KNXNet packet: got %d bytes (expected %d)', this.buffer.length + hdr.header_length, this.total_length ); switch (hdr.service_type) { // case SERVICE_TYPE.SEARCH_REQUEST: case KnxConstants.SERVICE_TYPE.CONNECT_REQUEST: { this.HPAI('hpai').HPAI('tunn').CRI('cri'); break; } case KnxConstants.SERVICE_TYPE.CONNECT_RESPONSE: case KnxConstants.SERVICE_TYPE.CONNECTIONSTATE_REQUEST: case KnxConstants.SERVICE_TYPE.CONNECTIONSTATE_RESPONSE: case KnxConstants.SERVICE_TYPE.DISCONNECT_REQUEST: case KnxConstants.SERVICE_TYPE.DISCONNECT_RESPONSE: { this.ConnState('connstate'); if (hdr.total_length > 8) this.HPAI('hpai'); if (hdr.total_length > 16) this.CRI('cri'); break; } case KnxConstants.SERVICE_TYPE.DESCRIPTION_RESPONSE: { this.raw('value', hdr.total_length); break; } // most common case: case KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST: this.TunnState('tunnstate'); this.CEMI('cemi'); break; case KnxConstants.SERVICE_TYPE.TUNNELING_ACK: this.TunnState('tunnstate'); break; case KnxConstants.SERVICE_TYPE.ROUTING_INDICATION: this.CEMI('cemi'); break; default: { KnxLog.get().warn( 'read KNXNetHeader: unhandled serviceType = %s', KnxConstants.keyText('SERVICE_TYPE', hdr.service_type) ); } } }) .popStack(propertyName, (data) => { if (KnxProtocol.debug) KnxLog.get().trace(JSON.stringify(data, null, 4)); return data; }); }, write(value) { if (!value) throw 'cannot write null KNXNetHeader value'; value.total_length = knxlen('KNXNetHeader', value); if (KnxProtocol.debug) KnxLog.get().trace('writing KnxHeader:', value); this.UInt8(6) // header length (6 bytes constant) .UInt8(0x10) // protocol version 1.0 .UInt16BE(value.service_type) .UInt16BE(value.total_length); switch (value.service_type) { //case SERVICE_TYPE.SEARCH_REQUEST: case KnxConstants.SERVICE_TYPE.CONNECT_REQUEST: { if (value.hpai) this.HPAI(value.hpai); if (value.tunn) this.HPAI(value.tunn); if (value.cri) this.CRI(value.cri); break; } case KnxConstants.SERVICE_TYPE.CONNECT_RESPONSE: case KnxConstants.SERVICE_TYPE.CONNECTIONSTATE_REQUEST: case KnxConstants.SERVICE_TYPE.CONNECTIONSTATE_RESPONSE: case KnxConstants.SERVICE_TYPE.DISCONNECT_REQUEST: { if (value.connstate) this.ConnState(value.connstate); if (value.hpai) this.HPAI(value.hpai); if (value.cri) this.CRI(value.cri); break; } // most common case: case KnxConstants.SERVICE_TYPE.ROUTING_INDICATION: case KnxConstants.SERVICE_TYPE.TUNNELING_ACK: case KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST: { if (value.tunnstate) this.TunnState(value.tunnstate); if (value.cemi) this.CEMI(value.cemi); break; } // case KnxConstants.SERVICE_TYPE.DESCRIPTION_RESPONSE: { default: { throw util.format( 'write KNXNetHeader: unhandled serviceType = %s (%j)', KnxConstants.keyText('SERVICE_TYPE', value), value ); } } }, }); KnxProtocol.lengths['KNXNetHeader'] = (value) => { if (!value) throw 'Must supply a valid KNXNetHeader value'; switch (value.service_type) { //case SERVICE_TYPE.SEARCH_REQUEST: case KnxConstants.SERVICE_TYPE.CONNECT_REQUEST: return ( 6 + knxlen('HPAI', value.hpai) + knxlen('HPAI', value.tunn) + knxlen('CRI', value.cri) ); case KnxConstants.SERVICE_TYPE.CONNECT_RESPONSE: case KnxConstants.SERVICE_TYPE.CONNECTIONSTATE_REQUEST: case KnxConstants.SERVICE_TYPE.CONNECTIONSTATE_RESPONSE: case KnxConstants.SERVICE_TYPE.DISCONNECT_REQUEST: return ( 6 + knxlen('ConnState', value.connstate) + knxlen('HPAI', value.hpai) + knxlen('CRI', value.cri) ); case KnxConstants.SERVICE_TYPE.TUNNELING_ACK: case KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST: return ( 6 + knxlen('TunnState', value.tunnstate) + knxlen('CEMI', value.cemi) ); case KnxConstants.SERVICE_TYPE.ROUTING_INDICATION: return 6 + knxlen('CEMI', value.cemi); } }; module.exports = KnxProtocol;