', ''GroupValue_Write', src, data
- this.emit(
- util.format('event_%s', datagram.cemi.dest_addr),
- evtName,
- datagram.cemi.src_addr,
- datagram.cemi.apdu.data
- );
- // 2.
- // 'GroupValue_Write_1/2/3', src, data
- this.emit(
- util.format('%s_%s', evtName, datagram.cemi.dest_addr),
- datagram.cemi.src_addr,
- datagram.cemi.apdu.data
- );
- // 3.
- // 'GroupValue_Write', src, dest, data
- this.emit(
- evtName,
- datagram.cemi.src_addr,
- datagram.cemi.dest_addr,
- datagram.cemi.apdu.data
- );
- // 4.
- // 'event', 'GroupValue_Write', src, dest, data
- this.emit(
- 'event',
- evtName,
- datagram.cemi.src_addr,
- datagram.cemi.dest_addr,
- datagram.cemi.apdu.data
- );
- },
-
- getLocalAddress() {
- const candidateInterfaces = this.getIPv4Interfaces();
- // if user has declared a desired interface then use it
- if (this.options && this.options.interface) {
- const iface = candidateInterfaces[this.options.interface];
- if (!iface)
- throw new Error(
- 'Interface ' +
- this.options.interface +
- ' not found or has no useful IPv4 address!'
- );
-
- return candidateInterfaces[this.options.interface].address;
- }
- // just return the first available IPv4 non-loopback interface
- const first = Object.values(candidateInterfaces)[0];
- if (first) return first.address;
-
- // no local IpV4 interfaces?
- throw 'No valid IPv4 interfaces detected';
- },
-
- // get the local address of the IPv4 interface we're going to use
- getIPv4Interfaces() {
- const candidateInterfaces = {};
- const interfaces = os.networkInterfaces();
- for (const [iface, addrs] of Object.entries(interfaces)) {
- for (const addr of addrs) {
- if ([4, 'IPv4'].indexOf(addr.family) > -1 && !addr.internal) {
- this.log.trace(
- util.format('candidate interface: %s (%j)', iface, addr)
- );
- candidateInterfaces[iface] = addr;
- }
- }
- }
- return candidateInterfaces;
- },
-});
diff --git a/src/FSM.ts b/src/FSM.ts
new file mode 100644
index 0000000..9762043
--- /dev/null
+++ b/src/FSM.ts
@@ -0,0 +1,456 @@
+import type { KnxClient, Datagram } from './KnxClient'
+import { KnxConstants, keyText } from './KnxConstants'
+import KnxLog from './KnxLog'
+import { hasProp } from './utils'
+import machina from 'machina'
+import util from 'util'
+
+const KnxFSM = machina.Fsm.extend({
+ namespace: 'knxnet',
+ initialState: 'uninitialized',
+ states: {
+ uninitialized: {
+ '*': function (this: KnxClient) {
+ this.transition('connecting')
+ },
+ },
+
+ jumptoconnecting: {
+ _onEnter(this: KnxClient) {
+ this.transition('connecting')
+ },
+ },
+
+ connecting: {
+ _onEnter(this: KnxClient) {
+ this.emit('disconnected')
+ this.log.debug(
+ util.format('useTunneling=%j', this.useTunneling),
+ )
+ if (this.useTunneling) {
+ let connection_attempts = 0
+ if (!this.localAddress)
+ throw Error(
+ 'Not bound to an IPv4 non-loopback interface',
+ )
+ this.log.debug(
+ util.format('Connecting via %s...', this.localAddress),
+ )
+ this.connecttimer = setInterval(() => {
+ connection_attempts += 1
+ if (connection_attempts >= 3) {
+ clearInterval(this.connecttimer)
+ if (
+ this.remoteEndpoint.addr.range() === 'multicast'
+ ) {
+ this.log.warn(
+ 'connection timed out, falling back to pure routing mode...',
+ )
+ this.usingMulticastTunneling = true
+ this.transition('connected')
+ } else {
+ this.reconnection_cycles += 1
+ const delay = Math.min(
+ this.reconnection_cycles * 3,
+ 300,
+ )
+ this.log.debug(
+ `reattempting connection in ${delay} seconds`,
+ )
+ setTimeout(
+ () => this.transition('jumptoconnecting'),
+ delay * 1000,
+ )
+ }
+ } else {
+ this.log.warn('connection timed out, retrying...')
+ this.send(
+ this.prepareDatagram(
+ KnxConstants.SERVICE_TYPE.CONNECT_REQUEST,
+ ),
+ )
+ }
+ }, 3000)
+ delete this.channel_id
+ delete this.conntime
+ delete this.lastSentTime
+ this.send(
+ this.prepareDatagram(
+ KnxConstants.SERVICE_TYPE.CONNECT_REQUEST,
+ ),
+ )
+ } else {
+ this.transition('connected')
+ }
+ },
+ _onExit(this: KnxClient) {
+ clearInterval(this.connecttimer)
+ },
+ inbound_CONNECT_RESPONSE(this: KnxClient, datagram: any) {
+ this.log.debug(util.format('got connect response'))
+ if (
+ hasProp(datagram, 'connstate') &&
+ datagram.connstate.status ===
+ KnxConstants.RESPONSECODE.E_NO_MORE_CONNECTIONS
+ ) {
+ try {
+ this.socket.close()
+ } catch (error) {
+ // noop
+ }
+ this.transition('uninitialized')
+ this.emit('disconnected')
+ this.log.debug(
+ 'The KNXnet/IP server rejected the data connection (Maximum connections reached). Waiting 1 minute before retrying...',
+ )
+ setTimeout(() => {
+ this.Connect()
+ }, 60000)
+ } else {
+ this.channel_id = datagram.connstate.channel_id
+ this.send(
+ this.prepareDatagram(
+ KnxConstants.SERVICE_TYPE.CONNECTIONSTATE_REQUEST,
+ ),
+ )
+ }
+ },
+ inbound_CONNECTIONSTATE_RESPONSE(this: KnxClient, datagram: any) {
+ if (this.useTunneling) {
+ const str = keyText(
+ 'RESPONSECODE',
+ datagram.connstate.status,
+ )
+ this.log.debug(
+ util.format(
+ 'Got connection state response, connstate: %s, channel ID: %d',
+ str,
+ datagram.connstate.channel_id,
+ ),
+ )
+ this.transition('connected')
+ }
+ },
+ '*': function (this: KnxClient, data: any) {
+ this.log.debug(
+ util.format('*** deferring Until Transition %j', data),
+ )
+ this.deferUntilTransition('idle')
+ },
+ },
+
+ connected: {
+ _onEnter(this: KnxClient) {
+ this.reconnection_cycles = 0
+ this.seqnum = -1
+ this.lastSentTime = Date.now()
+ this.conntime = this.lastSentTime
+ this.log.debug(
+ util.format(
+ '--- Connected in %s mode ---',
+ this.useTunneling ? 'TUNNELING' : 'ROUTING',
+ ),
+ )
+ this.transition('idle')
+ this.emit('connected')
+ },
+ },
+
+ disconnecting: {
+ _onEnter(this: KnxClient) {
+ if (this.useTunneling) {
+ const aliveFor = this.conntime
+ ? Date.now() - this.conntime
+ : 0
+ KnxLog.get().debug(
+ '(%s):\tconnection alive for %d seconds',
+ this.compositeState(),
+ aliveFor / 1000,
+ )
+ this.disconnecttimer = setTimeout(() => {
+ KnxLog.get().debug(
+ '(%s):\tconnection timed out',
+ this.compositeState(),
+ )
+ try {
+ this.socket.close()
+ } catch (error) {
+ // noop
+ }
+ this.transition('uninitialized')
+ this.emit('disconnected')
+ }, 3000)
+ this.send(
+ this.prepareDatagram(
+ KnxConstants.SERVICE_TYPE.DISCONNECT_REQUEST,
+ ),
+ (err: any) => {
+ KnxLog.get().debug(
+ '(%s):\tsent DISCONNECT_REQUEST',
+ this.compositeState(),
+ )
+ },
+ )
+ }
+ },
+ _onExit(this: KnxClient) {
+ clearTimeout(this.disconnecttimer)
+ },
+ inbound_DISCONNECT_RESPONSE(this: KnxClient, datagram: any) {
+ if (this.useTunneling) {
+ KnxLog.get().debug(
+ '(%s):\tgot disconnect response',
+ this.compositeState(),
+ )
+ try {
+ this.socket.close()
+ } catch (error) {
+ // noop
+ }
+ this.transition('uninitialized')
+ this.emit('disconnected')
+ }
+ },
+ },
+
+ idle: {
+ _onEnter(this: KnxClient) {
+ if (this.useTunneling) {
+ if (this.idletimer == null) {
+ this.idletimer = setTimeout(() => {
+ this.transition('requestingConnState')
+ clearTimeout(this.idletimer)
+ this.idletimer = null
+ }, 60000)
+ }
+ }
+ KnxLog.get().debug(
+ '(%s):\t%s',
+ this.compositeState(),
+ ' zzzz...',
+ )
+ this.processQueue()
+ },
+ _onExit(this: KnxClient) {},
+ outbound_ROUTING_INDICATION(this: KnxClient, datagram: Datagram) {
+ const elapsed = Date.now() - this.lastSentTime
+ if (
+ !this.options.minimumDelay ||
+ elapsed >= this.options.minimumDelay
+ ) {
+ this.transition('sendDatagram', datagram)
+ } else {
+ setTimeout(
+ () =>
+ this.handle(
+ 'outbound_ROUTING_INDICATION',
+ datagram,
+ ),
+ this.minimumDelay - elapsed,
+ )
+ }
+ },
+ outbound_TUNNELING_REQUEST(this: KnxClient, datagram: Datagram) {
+ if (this.useTunneling) {
+ const elapsed = Date.now() - this.lastSentTime
+ if (
+ !this.options.minimumDelay ||
+ elapsed >= this.options.minimumDelay
+ ) {
+ this.transition('sendDatagram', datagram)
+ } else {
+ setTimeout(
+ () =>
+ this.handle(
+ 'outbound_TUNNELING_REQUEST',
+ datagram,
+ ),
+ this.minimumDelay - elapsed,
+ )
+ }
+ } else {
+ KnxLog.get().debug(
+ "(%s):\tdropping outbound TUNNELING_REQUEST, we're in routing mode",
+ this.compositeState(),
+ )
+ }
+ },
+ 'inbound_TUNNELING_REQUEST_L_Data.ind': function (
+ this: KnxClient,
+ datagram: Datagram,
+ ) {
+ if (this.useTunneling) {
+ this.transition('recvTunnReqIndication', datagram)
+ }
+ },
+ 'inbound_TUNNELING_REQUEST_L_Data.con': function (
+ this: KnxClient,
+ datagram: Datagram,
+ ) {
+ if (this.useTunneling) {
+ const confirmed =
+ this.sentTunnRequests[datagram.cemi.dest_addr]
+ if (confirmed) {
+ delete this.sentTunnRequests[datagram.cemi.dest_addr]
+ this.emit('confirmed', confirmed)
+ }
+ KnxLog.get().trace(
+ '(%s): %s %s',
+ this.compositeState(),
+ datagram.cemi.dest_addr,
+ confirmed
+ ? 'delivery confirmation (L_Data.con) received'
+ : 'unknown dest addr',
+ )
+ this.acknowledge(datagram)
+ }
+ },
+ 'inbound_ROUTING_INDICATION_L_Data.ind': function (
+ this: KnxClient,
+ datagram: Datagram,
+ ) {
+ this.emitEvent(datagram)
+ },
+ inbound_DISCONNECT_REQUEST(this: KnxClient, datagram: any) {
+ if (this.useTunneling) {
+ this.transition('connecting')
+ }
+ },
+ },
+
+ requestingConnState: {
+ _onEnter(this: KnxClient) {
+ KnxLog.get().debug('Requesting Connection State')
+ KnxLog.get().trace(
+ '(%s): Requesting Connection State',
+ this.compositeState(),
+ )
+ this.send(
+ this.prepareDatagram(
+ KnxConstants.SERVICE_TYPE.CONNECTIONSTATE_REQUEST,
+ ),
+ )
+ this.connstatetimer = setTimeout(() => {
+ const msg = 'timed out waiting for CONNECTIONSTATE_RESPONSE'
+ KnxLog.get().trace('(%s): %s', this.compositeState(), msg)
+ this.transition('connecting')
+ this.emit('error', msg)
+ }, 1000)
+ },
+ _onExit(this: KnxClient) {
+ clearTimeout(this.connstatetimer)
+ },
+ inbound_CONNECTIONSTATE_RESPONSE(this: KnxClient, datagram: any) {
+ const state = keyText('RESPONSECODE', datagram.connstate.status)
+ switch (datagram.connstate.status) {
+ case 0:
+ this.transition('idle')
+ break
+ default:
+ this.log.debug(
+ util.format(
+ '*** error: %s *** (connstate.code: %d)',
+ state,
+ datagram.connstate.status,
+ ),
+ )
+ this.transition('connecting')
+ this.emit('error', state)
+ }
+ },
+ '*': function (this: KnxClient, data: any) {
+ this.log.debug(
+ util.format(
+ '*** deferring %s until transition from requestingConnState => idle',
+ data.inputType,
+ ),
+ )
+ this.deferUntilTransition('idle')
+ },
+ },
+
+ sendDatagram: {
+ _onEnter(this: KnxClient, datagram: Datagram) {
+ this.seqnum += 1
+ if (this.useTunneling)
+ datagram.tunnstate.seqnum = this.seqnum & 0xff
+ this.send(datagram, (err: any) => {
+ if (err) {
+ this.seqnum -= 1
+ this.transition('idle')
+ } else {
+ if (this.useTunneling)
+ this.sentTunnRequests[datagram.cemi.dest_addr] =
+ datagram
+ this.lastSentTime = Date.now()
+ this.log.debug(
+ '(%s):\t>>>>>>> successfully sent seqnum: %d',
+ this.compositeState(),
+ this.seqnum,
+ )
+ if (this.useTunneling) {
+ this.transition('sendTunnReq_waitACK', datagram)
+ } else {
+ this.transition('idle')
+ }
+ }
+ })
+ },
+ '*': function (this: KnxClient, data: any) {
+ this.log.debug(
+ util.format(
+ '*** deferring %s until transition sendDatagram => idle',
+ data.inputType,
+ ),
+ )
+ this.deferUntilTransition('idle')
+ },
+ },
+ sendTunnReq_waitACK: {
+ _onEnter(this: KnxClient, datagram: Datagram) {
+ this.tunnelingAckTimer = setTimeout(() => {
+ this.log.debug('timed out waiting for TUNNELING_ACK')
+ this.transition('idle')
+ this.emit('tunnelreqfailed', datagram)
+ }, 2000)
+ },
+ _onExit(this: KnxClient) {
+ clearTimeout(this.tunnelingAckTimer)
+ },
+ inbound_TUNNELING_ACK(this: KnxClient, datagram: Datagram) {
+ this.log.debug(
+ util.format(
+ '===== datagram %d acknowledged by IP router',
+ datagram.tunnstate.seqnum,
+ ),
+ )
+ this.transition('idle')
+ },
+ '*': function (this: KnxClient, data: any) {
+ this.log.debug(
+ util.format(
+ '*** deferring %s until transition sendTunnReq_waitACK => idle',
+ data.inputType,
+ ),
+ )
+ this.deferUntilTransition('idle')
+ },
+ },
+ recvTunnReqIndication: {
+ _onEnter(this: KnxClient, datagram: Datagram) {
+ this.seqnumRecv = datagram.tunnstate.seqnum
+ this.acknowledge(datagram)
+ this.transition('idle')
+ this.emitEvent(datagram)
+ },
+ '*': function (this: KnxClient, data: any) {
+ this.log.debug(
+ util.format('*** deferring Until Transition %j', data),
+ )
+ this.deferUntilTransition('idle')
+ },
+ },
+ },
+})
+
+export default KnxFSM
diff --git a/src/IpRoutingConnection.js b/src/IpRoutingConnection.js
deleted file mode 100644
index 09117fe..0000000
--- a/src/IpRoutingConnection.js
+++ /dev/null
@@ -1,68 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const util = require('util');
-const dgram = require('dgram');
-const KnxLog = require('./KnxLog.js');
-/**
- Initializes a new KNX routing connection with provided values. Make
- sure the local system allows UDP messages to the multicast group.
-**/
-function IpRoutingConnection(instance) {
- const log = KnxLog.get();
-
- instance.BindSocket = function (cb) {
- const udpSocket = dgram.createSocket({ type: 'udp4', reuseAddr: true });
- udpSocket.on('listening', () => {
- log.debug(
- util.format(
- 'IpRoutingConnection %s:%d, adding membership for %s',
- instance.localAddress,
- udpSocket.address().port,
- this.remoteEndpoint.addr
- )
- );
- try {
- this.socket.addMembership(
- this.remoteEndpoint.addr,
- instance.localAddress
- );
- } catch (err) {
- log.warn('IPRouting connection: cannot add membership (%s)', err);
- }
- });
- // ROUTING multicast connections need to bind to the default port, 3671
- udpSocket.bind(3671, () => cb && cb(udpSocket));
- return udpSocket;
- };
-
- //
- /// Start the connection
- ///
- instance.Connect = function () {
- this.localAddress = this.getLocalAddress();
- this.socket = this.BindSocket((socket) => {
- socket.on('error', (errmsg) =>
- log.debug(util.format('Socket error: %j', errmsg))
- );
- socket.on('message', (msg, rinfo, callback) => {
- log.debug(
- 'Inbound multicast message from ' +
- rinfo.address +
- ': ' +
- msg.toString('hex')
- );
- this.onUdpSocketMessage(msg, rinfo, callback);
- });
- // start connection sequence
- this.transition('connecting');
- });
- return this;
- };
-
- return instance;
-}
-
-module.exports = IpRoutingConnection;
diff --git a/src/IpRoutingConnection.ts b/src/IpRoutingConnection.ts
new file mode 100644
index 0000000..de3d926
--- /dev/null
+++ b/src/IpRoutingConnection.ts
@@ -0,0 +1,69 @@
+import * as util from 'util'
+import * as dgram from 'dgram'
+import type { KnxClient } from './KnxClient'
+
+function IpRoutingConnection(instance: KnxClient): KnxClient {
+ instance.BindSocket = function (cb) {
+ const udpSocket = dgram.createSocket({ type: 'udp4', reuseAddr: true })
+ udpSocket.on('listening', () => {
+ this.log.debug(
+ util.format(
+ 'IpRoutingConnection %s:%d, adding membership for %s',
+ instance.localAddress,
+ udpSocket.address().port,
+ this.remoteEndpoint.addr,
+ ),
+ )
+ try {
+ this.socket.addMembership(
+ this.remoteEndpoint.addr,
+ instance.localAddress,
+ )
+ } catch (err) {
+ this.log.warn(
+ 'IPRouting connection: cannot add membership (%s)',
+ err,
+ )
+ }
+ })
+ // ROUTING multicast connections need to bind to the default port, 3671
+ udpSocket.bind(3671, () => {
+ if (cb) cb(udpSocket)
+ })
+ return udpSocket
+ }
+
+ //
+ /// Start the connection
+ ///
+ instance.Connect = function () {
+ this.localAddress = this.getLocalAddress()
+ this.socket = this.BindSocket((socket: dgram.Socket) => {
+ socket.on('error', (errmsg: string) =>
+ this.log.debug(util.format('Socket error: %j', errmsg)),
+ )
+ socket.on(
+ 'message',
+ (
+ msg: Buffer,
+ rinfo: dgram.RemoteInfo,
+ callback: () => void,
+ ) => {
+ this.log.debug(
+ `Inbound multicast message from ${
+ rinfo.address
+ }: ${msg.toString('hex')}`,
+ )
+ this.onUdpSocketMessage(msg, rinfo, callback)
+ },
+ )
+ // start connection sequence
+ this.transition('connecting')
+ })
+ return this
+ }
+
+ return instance
+}
+
+export default IpRoutingConnection
diff --git a/src/IpTunnelingConnection.js b/src/IpTunnelingConnection.js
deleted file mode 100644
index 32a3ca0..0000000
--- a/src/IpTunnelingConnection.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const dgram = require('dgram');
-const KnxLog = require('./KnxLog.js');
-
-function IpTunnelingConnection(instance) {
- const log = KnxLog.get();
-
- instance.BindSocket = function (cb) {
- const udpSocket = dgram.createSocket('udp4');
- udpSocket.bind(() => {
- log.debug(
- 'IpTunnelingConnection.BindSocket %s:%d',
- instance.localAddress,
- udpSocket.address().port
- );
- cb && cb(udpSocket);
- });
- return udpSocket;
- };
-
- instance.Connect = function () {
- this.localAddress = this.getLocalAddress();
- // create the socket
- this.socket = this.BindSocket((socket) => {
- socket.on('error', (errmsg) => log.debug('Socket error: %j', errmsg));
- socket.on('message', (msg, rinfo, callback) => {
- log.debug('Inbound message: %s', msg.toString('hex'));
- this.onUdpSocketMessage(msg, rinfo, callback);
- });
- // start connection sequence
- this.transition('connecting');
- });
- return this;
- };
-
- return instance;
-}
-
-module.exports = IpTunnelingConnection;
diff --git a/src/IpTunnelingConnection.ts b/src/IpTunnelingConnection.ts
new file mode 100644
index 0000000..95483e7
--- /dev/null
+++ b/src/IpTunnelingConnection.ts
@@ -0,0 +1,45 @@
+import dgram from 'dgram'
+import type { KnxClient } from './KnxClient'
+
+function IpTunnelingConnection(instance: KnxClient) {
+ instance.BindSocket = function (cb) {
+ const udpSocket = dgram.createSocket('udp4')
+ udpSocket.bind(() => {
+ this.log.debug(
+ 'IpTunnelingConnection.BindSocket %s:%d',
+ instance.localAddress,
+ udpSocket.address().port,
+ )
+ if (cb) cb(udpSocket)
+ })
+ return udpSocket
+ }
+
+ instance.Connect = function () {
+ this.localAddress = this.getLocalAddress()
+ // create the socket
+ this.socket = this.BindSocket((socket: dgram.Socket) => {
+ socket.on('error', (errmsg: string) =>
+ this.log.debug('Socket error: %j', errmsg),
+ )
+ socket.on(
+ 'message',
+ (
+ msg: Buffer,
+ rinfo: dgram.RemoteInfo,
+ callback: () => void,
+ ) => {
+ this.log.debug('Inbound message: %s', msg.toString('hex'))
+ this.onUdpSocketMessage(msg, rinfo, callback)
+ },
+ )
+ // start connection sequence
+ this.transition('connecting')
+ })
+ return this
+ }
+
+ return instance
+}
+
+export default IpTunnelingConnection
diff --git a/src/KnxClient.ts b/src/KnxClient.ts
new file mode 100644
index 0000000..6eafe32
--- /dev/null
+++ b/src/KnxClient.ts
@@ -0,0 +1,731 @@
+import os from 'os'
+import util from 'util'
+import * as ipaddr from 'ipaddr.js'
+import { keyText, KnxConstants } from './KnxConstants'
+import IpRoutingConnection from './IpRoutingConnection'
+import IpTunnelingConnection from './IpTunnelingConnection'
+import KnxLog, { KnxLogOptions } from './KnxLog'
+import knxProto from './KnxProtocol'
+import { Writer } from 'binary-protocol'
+import { Socket } from 'dgram'
+import { populateAPDU } from './dptlib'
+import { Logger, LogLevel } from 'log-driver'
+import { hasProp } from './utils'
+import KnxFSM from './FSM'
+
+// do not use import here or package.json would be loaded twice
+// eslint-disable-next-line @typescript-eslint/no-var-requires
+const pkgJson = require('../package.json')
+
+type KnxDeviceAddress = string
+
+type KnxGroupAddress = string
+
+/** The type of the KnxValue depends on the DPT that it is associated with */
+type KnxValue = number | string | boolean | Date
+
+type HandlersSpec = {
+ connected?: () => void
+ disconnected?: () => void
+ event?: (
+ evt: string,
+ src: KnxDeviceAddress,
+ dest: KnxGroupAddress,
+ value: Buffer,
+ ) => void
+ error?: (connstatus: any) => void
+}
+
+export type KnxOptions = {
+ /** ip address of the KNX router or interface */
+ ipAddr?: string
+ /** port of the KNX router or interface */
+ ipPort?: number
+ /** in case you need to specify the multicast interface (say if you have more than one) */
+ interface?: string
+ /** the KNX physical address we'd like to use */
+ physAddr?: string
+ /** set the log level for messsages printed on the console. This can be 'error', 'warn', 'info' (default), 'debug', or 'trace'. */
+ loglevel?: LogLevel
+ /** do not automatically connect, but use connection.Connect() to establish connection */
+ manualConnect?: boolean
+ /** use tunneling with multicast (router) - this is NOT supported by all routers! See README-resilience.md */
+ forceTunneling?: boolean
+ /** wait at least 10 millisec between each datagram */
+ minimumDelay?: number
+ /** enable this option to suppress the acknowledge flag with outgoing L_Data.req requests. LoxOne needs this */
+ suppress_ack_ldatareq?: boolean
+ /** 14/03/2020 In tunneling mode, echoes the sent message by emitting a new emitEvent, so other object with same group address, can receive the sent message. Default is false. */
+ localEchoInTunneling?: boolean
+ /** event handlers. You can also bind them later with connection.on(event, fn) */
+ handlers?: HandlersSpec
+} & KnxLogOptions
+
+export interface Datagram {
+ header_length: number
+ protocol_version: number
+ service_type: number
+ total_length: number
+ cemi?: {
+ dest_addr: string
+ src_addr: string
+ addinfo_length?: number
+ apdu: {
+ apci: string
+ data: any
+ tpci: number
+ bitlength?: number
+ apdu_length?: number
+ apdu_raw?: any
+ }
+ msgcode: number
+ ctrl?: {
+ frameType: number
+ reserved: number
+ repeat: number
+ broadcast: number
+ priority: number
+ acknowledge: number
+ confirm: number
+ destAddrType: number
+ hopCount: number
+ extendedFrame: number
+ }
+ }
+ tunnstate?: {
+ seqnum?: number
+ channel_id: number
+ tunnel_endpoint: string
+ rsvd?: number
+ }
+ tunn?: {
+ protocol_type: number
+ tunnel_endpoint: string
+ }
+ hpai?: {
+ header_length?: number
+ protocol_type: number
+ tunnel_endpoint: string
+ }
+ connstate?: {
+ state: number
+ channel_id: number
+ status?: number
+ }
+ cri?: {
+ connection_type: number
+ knx_layer: number
+ unused: number
+ }
+}
+
+export class KnxClient extends KnxFSM {
+ protected options: KnxOptions
+
+ protected ThreeLevelGroupAddressing: boolean
+
+ protected reconnection_cycles: number
+
+ protected sentTunnRequests: { [key: string]: Datagram }
+
+ protected useTunneling: boolean
+
+ protected localEchoInTunneling: boolean | undefined
+
+ protected channel_id?: any
+
+ protected conntime?: number
+
+ protected lastSentTime?: number
+
+ protected connecttimer?: NodeJS.Timeout
+
+ protected disconnecttimer?: NodeJS.Timeout
+
+ protected connstatetimer?: NodeJS.Timeout
+
+ protected idletimer?: NodeJS.Timeout
+
+ protected tunnelingAckTimer?: NodeJS.Timeout
+
+ protected seqnum: number
+
+ protected seqnumRecv: number
+
+ protected writer: Writer
+
+ protected usingMulticastTunneling: boolean
+
+ protected minimumDelay: number
+
+ public log: Logger
+
+ public remoteEndpoint: {
+ addrstring: string
+ addr: any
+ port: number
+ }
+
+ public socket: Socket
+
+ public localAddress: string | null
+
+ public BindSocket: (this: this, cb: (socket: Socket) => void) => Socket
+
+ public Connect: (this: this) => void
+
+ constructor(options: KnxOptions) {
+ super()
+
+ this.log = KnxLog.get(options)
+ this.log.info(
+ util.format(
+ 'Loading %s: %s, version: %s',
+ pkgJson.name,
+ pkgJson.description,
+ pkgJson.version,
+ ),
+ )
+
+ this.options = options || {}
+ this.localAddress = null
+ this.ThreeLevelGroupAddressing = true
+ this.reconnection_cycles = 0
+ this.sentTunnRequests = {}
+ this.useTunneling = options.forceTunneling || false
+ this.remoteEndpoint = {
+ addrstring: options.ipAddr || '224.0.23.12',
+ addr: ipaddr.parse(options.ipAddr || '224.0.23.12'),
+ port: options.ipPort || 3671,
+ }
+ const range = this.remoteEndpoint.addr.range()
+ this.localEchoInTunneling =
+ typeof options.localEchoInTunneling !== 'undefined'
+ ? options.localEchoInTunneling
+ : false
+ this.log.debug(
+ 'initializing %s connection to %s',
+ range,
+ this.remoteEndpoint.addrstring,
+ )
+ switch (range) {
+ case 'multicast':
+ if (this.localEchoInTunneling) {
+ this.localEchoInTunneling = false
+ this.log.debug(
+ 'localEchoInTunneling: true but DISABLED because i am on multicast',
+ )
+ }
+ IpRoutingConnection(this)
+ break
+ case 'unicast':
+ case 'private':
+ case 'loopback':
+ this.useTunneling = true
+ IpTunnelingConnection(this)
+ break
+ default:
+ throw Error(
+ util.format(
+ 'IP address % (%s) cannot be used for KNX',
+ options.ipAddr,
+ range,
+ ),
+ )
+ }
+
+ if (typeof options.handlers === 'object') {
+ for (const [key, value] of Object.entries(options.handlers)) {
+ if (typeof value === 'function') {
+ this.on(key, value)
+ }
+ }
+ }
+ // boot up the KNX connection unless told otherwise
+ if (!options.manualConnect) this.Connect()
+ }
+
+ /**
+ * --------------------------------
+ * KNX FSM Utils methods
+ * --------------------------------
+ */
+
+ /** ACK a datagram */
+ acknowledge(datagram: Datagram) {
+ const ack = this.prepareDatagram(
+ KnxConstants.SERVICE_TYPE.TUNNELING_ACK,
+ )
+ ack.tunnstate.seqnum = datagram.tunnstate.seqnum
+ this.send(ack, (err: any) => {})
+ }
+
+ emitEvent(datagram: Datagram) {
+ const evtName = datagram.cemi.apdu.apci
+ this.emit(
+ util.format('event_%s', datagram.cemi.dest_addr),
+ evtName,
+ datagram.cemi.src_addr,
+ datagram.cemi.apdu.data,
+ )
+ this.emit(
+ util.format('%s_%s', evtName, datagram.cemi.dest_addr),
+ datagram.cemi.src_addr,
+ datagram.cemi.apdu.data,
+ )
+ this.emit(
+ evtName,
+ datagram.cemi.src_addr,
+ datagram.cemi.dest_addr,
+ datagram.cemi.apdu.data,
+ )
+ this.emit(
+ 'event',
+ evtName,
+ datagram.cemi.src_addr,
+ datagram.cemi.dest_addr,
+ datagram.cemi.apdu.data,
+ )
+ }
+
+ getLocalAddress() {
+ const candidateInterfaces = this.getIPv4Interfaces()
+ if (this.options && this.options.interface) {
+ const iface = candidateInterfaces[this.options.interface]
+ if (!iface)
+ throw new Error(
+ `Interface ${this.options.interface} not found or has no useful IPv4 address!`,
+ )
+
+ return candidateInterfaces[this.options.interface].address
+ }
+ const first = Object.values(candidateInterfaces)[0]
+ if (first) return first.address
+
+ throw Error('No valid IPv4 interfaces detected')
+ }
+
+ getIPv4Interfaces() {
+ const candidateInterfaces: { [key: string]: any } = {}
+ const interfaces = os.networkInterfaces()
+ for (const [iface, addrs] of Object.entries(interfaces)) {
+ for (const addr of addrs) {
+ if ([4, 'IPv4'].indexOf(addr.family) > -1 && !addr.internal) {
+ this.log.trace(
+ util.format(
+ 'candidate interface: %s (%j)',
+ iface,
+ addr,
+ ),
+ )
+ candidateInterfaces[iface] = addr
+ }
+ }
+ }
+ return candidateInterfaces
+ }
+
+ /**
+ * --------------------------------
+ * Connection management methods
+ * --------------------------------
+ */
+
+ /** Bind incoming UDP packet handler */
+ onUdpSocketMessage(msg: Buffer, rinfo: any, callback: () => void): void {
+ // get the incoming packet's service type ...
+ try {
+ const dg = knxProto.parseDatagram(msg)
+ const descr = datagramDesc(dg)
+ KnxLog.get().trace(
+ '(%s): Received %s message: %j',
+ this.compositeState(),
+ descr,
+ dg,
+ )
+ if (
+ !isNaN(this.channel_id) &&
+ ((hasProp(dg, 'connstate') &&
+ dg.connstate.channel_id !== this.channel_id) ||
+ (hasProp(dg, 'tunnstate') &&
+ dg.tunnstate.channel_id !== this.channel_id))
+ ) {
+ KnxLog.get().trace(
+ '(%s): *** Ignoring %s datagram for other channel (own: %d)',
+ this.compositeState(),
+ descr,
+ this.channel_id,
+ )
+ } else {
+ // ... to drive the state machine (eg "inbound_TUNNELING_REQUEST_L_Data.ind")
+ const signal = util.format('inbound_%s', descr)
+ if (descr === 'DISCONNECT_REQUEST') {
+ KnxLog.get().info(
+ 'empty internal fsm queue due to %s: ',
+ signal,
+ )
+ this.clearQueue()
+ }
+ this.handle(signal, dg)
+ }
+ } catch (err) {
+ KnxLog.get().debug(
+ '(%s): Incomplete/unparseable UDP packet: %s: %s',
+ this.compositeState(),
+ err,
+ msg.toString('hex'),
+ )
+ }
+ }
+
+ AddConnState(datagram: Datagram): void {
+ datagram.connstate = {
+ channel_id: this.channel_id,
+ state: 0,
+ }
+ }
+
+ AddTunnState(datagram: Datagram): void {
+ // add the remote IP router's endpoint
+ datagram.tunnstate = {
+ channel_id: this.channel_id,
+ tunnel_endpoint: `${this.remoteEndpoint.addr}:${this.remoteEndpoint.port}`,
+ }
+ }
+
+ AddCEMI(datagram: Datagram, msgcode?: number): void {
+ const sendAck =
+ (msgcode || 0x11) === 0x11 && !this.options.suppress_ack_ldatareq // only for L_Data.req
+ datagram.cemi = {
+ msgcode: msgcode || 0x11, // default: L_Data.req for tunneling
+ ctrl: {
+ frameType: 1, // 0=extended 1=standard
+ reserved: 0, // always 0
+ repeat: 1, // the OPPOSITE: 1=do NOT repeat
+ broadcast: 1, // 0-system broadcast 1-broadcast
+ priority: 3, // 0-system 1-normal 2-urgent 3-low
+ acknowledge: sendAck ? 1 : 0,
+ confirm: 0, // FIXME: only for L_Data.con 0-ok 1-error
+ // 2nd byte
+ destAddrType: 1, // FIXME: 0-physical 1-groupaddr
+ hopCount: 6,
+ extendedFrame: 0,
+ },
+ src_addr: this.options.physAddr || '15.15.15',
+ dest_addr: '0/0/0', //
+ apdu: {
+ // default operation is GroupValue_Write
+ apci: 'GroupValue_Write',
+ tpci: 0,
+ data: 0,
+ },
+ }
+ }
+
+ /*
+ * submit an outbound request to the state machine
+ *
+ * type: service type
+ * datagram_template:
+ * if a datagram is passed, use this as
+ * if a function is passed, use this to DECORATE
+ * if NULL, then just make a new empty datagram. Look at AddXXX methods
+ */
+ Request(
+ type: number,
+ datagram_template: (datagram: Datagram) => void,
+ callback?: () => void,
+ ): void {
+ // populate skeleton datagram
+ const datagram = this.prepareDatagram(type)
+ // decorate the datagram, if a function is passed
+ if (typeof datagram_template === 'function') {
+ datagram_template(datagram)
+ }
+ // make sure that we override the datagram service type!
+ datagram.service_type = type
+ const st = keyText('SERVICE_TYPE', type)
+ // hand off the outbound request to the state machine
+ this.handle(`outbound_${st}`, datagram)
+ if (typeof callback === 'function') callback()
+ }
+
+ /** prepare a datagram for the given service type */
+ prepareDatagram(svcType: number): Datagram {
+ const datagram: Datagram = {
+ header_length: 6,
+ protocol_version: 16, // 0x10 == version 1.0
+ service_type: svcType,
+ total_length: null, // filled in automatically
+ }
+ //
+ AddHPAI(datagram)
+ //
+ switch (svcType) {
+ case KnxConstants.SERVICE_TYPE.CONNECT_REQUEST:
+ AddTunn(datagram)
+ AddCRI(datagram) // no break!
+ // eslint-disable-next-line no-fallthrough
+ case KnxConstants.SERVICE_TYPE.CONNECTIONSTATE_REQUEST:
+ case KnxConstants.SERVICE_TYPE.DISCONNECT_REQUEST:
+ this.AddConnState(datagram)
+ break
+ case KnxConstants.SERVICE_TYPE.ROUTING_INDICATION:
+ this.AddCEMI(datagram, KnxConstants.MESSAGECODES['L_Data.ind'])
+ break
+ case KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST:
+ AddTunn(datagram)
+ this.AddTunnState(datagram)
+ this.AddCEMI(datagram)
+ break
+ case KnxConstants.SERVICE_TYPE.TUNNELING_ACK:
+ this.AddTunnState(datagram)
+ break
+ default:
+ KnxLog.get().debug(
+ 'Do not know how to deal with svc type %d',
+ svcType,
+ )
+ }
+ return datagram
+ }
+
+ /**
+ * Send the datagram over the wire
+ */
+ send(datagram: Datagram, callback?: (err?: Error) => void): void {
+ let cemitype: string // TODO: set, but unused
+ try {
+ this.writer = knxProto.createWriter()
+ switch (datagram.service_type) {
+ case KnxConstants.SERVICE_TYPE.ROUTING_INDICATION:
+ case KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST:
+ // append the CEMI service type if this is a tunneling request...
+ cemitype = keyText('MESSAGECODES', datagram.cemi.msgcode)
+ break
+ }
+ const packet = this.writer.KNXNetHeader(datagram)
+ const buf = packet.buffer
+ const svctype = keyText('SERVICE_TYPE', datagram.service_type) // TODO: unused
+ const descr = datagramDesc(datagram)
+ KnxLog.get().trace(
+ '(%s): Sending %s ==> %j',
+ this.compositeState(),
+ descr,
+ datagram,
+ )
+ this.socket.send(
+ buf,
+ 0,
+ buf.length,
+ this.remoteEndpoint.port,
+ this.remoteEndpoint.addr.toString(),
+ (err) => {
+ KnxLog.get().trace(
+ '(%s): UDP sent %s: %s %s',
+ this.compositeState(),
+ err ? err.toString() : 'OK',
+ descr,
+ buf.toString('hex'),
+ )
+ if (typeof callback === 'function') callback(err)
+ },
+ )
+ } catch (e) {
+ KnxLog.get().warn(e)
+ if (typeof callback === 'function') callback(e)
+ }
+ }
+
+ write(
+ grpaddr: string,
+ value: any,
+ dptid?: number,
+ callback?: () => void,
+ ): void {
+ if (grpaddr == null || value == null) {
+ KnxLog.get().warn('You must supply both grpaddr and value!')
+ return
+ }
+ try {
+ // outbound request onto the state machine
+ const serviceType = this.useTunneling
+ ? KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST
+ : KnxConstants.SERVICE_TYPE.ROUTING_INDICATION
+ this.Request(
+ serviceType,
+ (datagram: Datagram) => {
+ populateAPDU(value, datagram.cemi.apdu, dptid)
+ datagram.cemi.dest_addr = grpaddr
+ },
+ callback,
+ )
+ } catch (e) {
+ KnxLog.get().warn(e)
+ }
+ }
+
+ respond(grpaddr: string, value: any, dptid: number): void {
+ if (grpaddr == null || value == null) {
+ KnxLog.get().warn('You must supply both grpaddr and value!')
+ return
+ }
+ const serviceType = this.useTunneling
+ ? KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST
+ : KnxConstants.SERVICE_TYPE.ROUTING_INDICATION
+ this.Request(serviceType, function (datagram: Datagram) {
+ populateAPDU(value, datagram.cemi.apdu, dptid)
+ // this is a READ request
+ datagram.cemi.apdu.apci = 'GroupValue_Response'
+ datagram.cemi.dest_addr = grpaddr
+ return datagram
+ })
+ }
+
+ writeRaw(
+ grpaddr: string,
+ value: Buffer,
+ bitlength?: number,
+ callback?: () => void,
+ ): void {
+ if (grpaddr == null || value == null) {
+ KnxLog.get().warn('You must supply both grpaddr and value!')
+ return
+ }
+ if (!Buffer.isBuffer(value)) {
+ KnxLog.get().warn('Value must be a buffer!')
+ return
+ }
+ // outbound request onto the state machine
+ const serviceType = this.useTunneling
+ ? KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST
+ : KnxConstants.SERVICE_TYPE.ROUTING_INDICATION
+ this.Request(
+ serviceType,
+ function (datagram: Datagram) {
+ datagram.cemi.apdu.data = value
+ datagram.cemi.apdu.bitlength = bitlength || value.byteLength * 8
+ datagram.cemi.dest_addr = grpaddr
+ },
+ callback,
+ )
+ }
+
+ /**
+ * Send a READ request to the bus
+ * you can pass a callback function which gets bound to the RESPONSE datagram event
+ * */
+ read(grpaddr: string, callback: (src: any, data: any) => void): void {
+ if (typeof callback === 'function') {
+ // when the response arrives:
+ const responseEvent = `GroupValue_Response_${grpaddr}`
+ KnxLog.get().trace(`Binding connection to ${responseEvent}`)
+ const binding = (src: any, data: any) => {
+ // unbind the event handler
+ this.off(responseEvent, binding)
+ // fire the callback
+ callback(src, data)
+ }
+ // prepare for the response
+ this.on(responseEvent, binding)
+ // clean up after 3 seconds just in case no one answers the read request
+ setTimeout(() => this.off(responseEvent, binding), 3000)
+ }
+ const serviceType = this.useTunneling
+ ? KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST
+ : KnxConstants.SERVICE_TYPE.ROUTING_INDICATION
+ this.Request(serviceType, function (datagram: Datagram) {
+ // this is a READ request
+ datagram.cemi.apdu.apci = 'GroupValue_Read'
+ datagram.cemi.dest_addr = grpaddr
+ return datagram
+ })
+ }
+
+ /**
+ * Disconnect from the KNX bus
+ */
+ Disconnect(cb?: () => void): void {
+ if (this.state === 'connecting') {
+ KnxLog.get().debug('Disconnecting directly')
+ this.transition('uninitialized')
+ if (cb) {
+ cb()
+ }
+ return
+ }
+
+ KnxLog.get().debug('waiting for Idle-State')
+ this.onIdle(() => {
+ KnxLog.get().trace('In Idle-State')
+
+ this.on('disconnected', () => {
+ KnxLog.get().debug('Disconnected from KNX')
+ if (cb) {
+ cb()
+ }
+ })
+
+ KnxLog.get().debug('Disconnecting from KNX')
+ this.transition('disconnecting')
+ })
+
+ // machina.js removeAllListeners equivalent:
+ // this.off();
+ }
+
+ onIdle(cb: () => void): void {
+ if (this.state === 'idle') {
+ KnxLog.get().trace('Connection is already Idle')
+ cb()
+ } else {
+ this.on('transition', function (data: any) {
+ if (data.toState === 'idle') {
+ KnxLog.get().trace('Connection just transitioned to Idle')
+ cb()
+ }
+ })
+ }
+ }
+}
+
+// return a descriptor for this datagram (TUNNELING_REQUEST_L_Data.ind)
+const datagramDesc = (dg: Datagram): string => {
+ let blurb = keyText('SERVICE_TYPE', dg.service_type)
+ if (
+ dg.service_type === KnxConstants.SERVICE_TYPE.TUNNELING_REQUEST ||
+ dg.service_type === KnxConstants.SERVICE_TYPE.ROUTING_INDICATION
+ ) {
+ blurb += `_${keyText('MESSAGECODES', dg.cemi.msgcode)}`
+ }
+ return blurb
+}
+
+// add the control udp local endpoint. UPDATE: not needed apparnently?
+const AddHPAI = (datagram: Datagram): void => {
+ datagram.hpai = {
+ protocol_type: 1, // UDP
+ // tunnel_endpoint: this.localAddress + ":" + this.control.address().port
+ tunnel_endpoint: '0.0.0.0:0',
+ }
+}
+
+// add the tunneling udp local endpoint UPDATE: not needed apparently?
+const AddTunn = (datagram: Datagram): void => {
+ datagram.tunn = {
+ protocol_type: 1, // UDP
+ tunnel_endpoint: '0.0.0.0:0',
+ // tunnel_endpoint: this.localAddress + ":" + this.tunnel.address().port
+ }
+}
+
+const AddCRI = (datagram: Datagram): void => {
+ // add the CRI
+ datagram.cri = {
+ connection_type: KnxConstants.CONNECTION_TYPE.TUNNEL_CONNECTION,
+ knx_layer: KnxConstants.KNX_LAYER.LINK_LAYER,
+ unused: 0,
+ }
+}
+
+export default KnxClient
diff --git a/src/KnxConstants.js b/src/KnxConstants.js
deleted file mode 100644
index 63db91b..0000000
--- a/src/KnxConstants.js
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const KnxLog = require('./KnxLog');
-
-// SOURCES:
-// http://www.eb-systeme.de/?page_id=479
-// http://knxnetipdissect.sourceforge.net/doc.html
-// http://dz.prosyst.com/pdoc/mBS_SH_SDK_7.3.0/modules/knx/api/com/prosyst/mbs/services/knx/ip/Constants.html
-
-const SERVICE_TYPE = {
- SEARCH_REQUEST: 0x0201,
- SEARCH_RESPONSE: 0x0202,
- DESCRIPTION_REQUEST: 0x0203,
- DESCRIPTION_RESPONSE: 0x0204,
- CONNECT_REQUEST: 0x0205,
- CONNECT_RESPONSE: 0x0206,
- CONNECTIONSTATE_REQUEST: 0x0207,
- CONNECTIONSTATE_RESPONSE: 0x0208,
- DISCONNECT_REQUEST: 0x0209,
- DISCONNECT_RESPONSE: 0x020a,
- DEVICE_CONFIGURATION_REQUEST: 0x0310,
- DEVICE_CONFIGURATION_ACK: 0x0311,
- TUNNELING_REQUEST: 0x0420,
- TUNNELING_ACK: 0x0421,
- ROUTING_INDICATION: 0x0530,
- ROUTING_LOST_MESSAGE: 0x0531,
- UNKNOWN: -1,
-};
-//
-const CONNECTION_TYPE = {
- DEVICE_MGMT_CONNECTION: 0x03,
- TUNNEL_CONNECTION: 0x04,
- REMOTE_LOGGING_CONNECTION: 0x06,
- REMOTE_CONFIGURATION_CONNECTION: 0x07,
- OBJECT_SERVER_CONNECTION: 0x08,
-};
-//
-const PROTOCOL_TYPE = {
- IPV4_UDP: 0x01,
- IPV4_TCP: 0x02,
-};
-//
-const KNX_LAYER = {
- LINK_LAYER: 0x02 /** Tunneling on link layer, establishes a link layer tunnel to the KNX network.*/,
- RAW_LAYER: 0x04 /** Tunneling on raw layer, establishes a raw tunnel to the KNX network. */,
- BUSMONITOR_LAYER: 0x80 /** Tunneling on busmonitor layer, establishes a busmonitor tunnel to the KNX network.*/,
-};
-
-const FRAMETYPE = {
- EXTENDED: 0x00,
- STANDARD: 0x01,
-};
-
-//https://github.com/calimero-project/calimero-core/blob/master/src/tuwien/auto/calimero/knxnetip/servicetype/ErrorCodes.java
-const RESPONSECODE = {
- NO_ERROR: 0x00, // E_NO_ERROR - The connection was established succesfully
- E_HOST_PROTOCOL_TYPE: 0x01,
- E_VERSION_NOT_SUPPORTED: 0x02,
- E_SEQUENCE_NUMBER: 0x04,
- E_CONNSTATE_LOST: 0x15, // typo in eibd/libserver/eibnetserver.cpp:394, forgot 0x prefix ??? "uchar res = 21;"
- E_CONNECTION_ID: 0x21, // - The KNXnet/IP server device could not find an active data connection with the given ID
- E_CONNECTION_TYPE: 0x22, // - The requested connection type is not supported by the KNXnet/IP server device
- E_CONNECTION_OPTION: 0x23, // - The requested connection options is not supported by the KNXnet/IP server device
- E_NO_MORE_CONNECTIONS: 0x24, // - The KNXnet/IP server could not accept the new data connection (Maximum reached)
- E_DATA_CONNECTION: 0x26, // - The KNXnet/IP server device detected an erro concerning the Dat connection with the given ID
- E_KNX_CONNECTION: 0x27, // - The KNXnet/IP server device detected an error concerning the KNX Bus with the given ID
- E_TUNNELING_LAYER: 0x29,
-};
-
-const MESSAGECODES = {
- 'L_Raw.req': 0x10,
- 'L_Data.req': 0x11,
- 'L_Poll_Data.req': 0x13,
- 'L_Poll_Data.con': 0x25,
- 'L_Data.ind': 0x29,
- 'L_Busmon.ind': 0x2b,
- 'L_Raw.ind': 0x2d,
- 'L_Data.con': 0x2e,
- 'L_Raw.con': 0x2f,
- 'ETS.Dummy1': 0xc1, // UNKNOWN: see https://bitbucket.org/ekarak/knx.js/issues/23
-};
-
-const APCICODES = [
- 'GroupValue_Read',
- 'GroupValue_Response',
- 'GroupValue_Write',
- 'PhysicalAddress_Write',
- 'PhysicalAddress_Read',
- 'PhysicalAddress_Response',
- 'ADC_Read',
- 'ADC_Response',
- 'Memory_Read',
- 'Memory_Response',
- 'Memory_Write',
- 'UserMemory',
- 'DeviceDescriptor_Read',
- 'DeviceDescriptor_Response',
- 'Restart',
- 'OTHER',
-];
-
-const KnxConstants = {
- SERVICE_TYPE,
- CONNECTION_TYPE,
- PROTOCOL_TYPE,
- KNX_LAYER,
- FRAMETYPE,
- RESPONSECODE,
- MESSAGECODES,
-};
-
-/* TODO helper function to print enum keys */
-const keyText = (mapref, value) => {
- // pass in map by name or value
- const map = KnxConstants[mapref] || mapref;
- if (typeof map !== 'object') throw 'Unknown map: ' + mapref;
- for (const [key, v] of Object.entries(map)) if (v == value) return key;
-
- KnxLog.get().trace('not found: %j', value);
-};
-
-module.exports = {
- ...KnxConstants,
- APCICODES,
- keyText,
-};
diff --git a/src/KnxConstants.ts b/src/KnxConstants.ts
new file mode 100644
index 0000000..384f8dd
--- /dev/null
+++ b/src/KnxConstants.ts
@@ -0,0 +1,119 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import KnxLog from './KnxLog'
+
+const SERVICE_TYPE = {
+ SEARCH_REQUEST: 0x0201,
+ SEARCH_RESPONSE: 0x0202,
+ DESCRIPTION_REQUEST: 0x0203,
+ DESCRIPTION_RESPONSE: 0x0204,
+ CONNECT_REQUEST: 0x0205,
+ CONNECT_RESPONSE: 0x0206,
+ CONNECTIONSTATE_REQUEST: 0x0207,
+ CONNECTIONSTATE_RESPONSE: 0x0208,
+ DISCONNECT_REQUEST: 0x0209,
+ DISCONNECT_RESPONSE: 0x020a,
+ DEVICE_CONFIGURATION_REQUEST: 0x0310,
+ DEVICE_CONFIGURATION_ACK: 0x0311,
+ TUNNELING_REQUEST: 0x0420,
+ TUNNELING_ACK: 0x0421,
+ ROUTING_INDICATION: 0x0530,
+ ROUTING_LOST_MESSAGE: 0x0531,
+ UNKNOWN: -1,
+}
+
+const CONNECTION_TYPE = {
+ DEVICE_MGMT_CONNECTION: 0x03,
+ TUNNEL_CONNECTION: 0x04,
+ REMOTE_LOGGING_CONNECTION: 0x06,
+ REMOTE_CONFIGURATION_CONNECTION: 0x07,
+ OBJECT_SERVER_CONNECTION: 0x08,
+}
+
+const PROTOCOL_TYPE = {
+ IPV4_UDP: 0x01,
+ IPV4_TCP: 0x02,
+}
+
+const KNX_LAYER = {
+ LINK_LAYER: 0x02,
+ RAW_LAYER: 0x04,
+ BUSMONITOR_LAYER: 0x80,
+}
+
+const FRAMETYPE = {
+ EXTENDED: 0x00,
+ STANDARD: 0x01,
+}
+
+const RESPONSECODE = {
+ NO_ERROR: 0x00,
+ E_HOST_PROTOCOL_TYPE: 0x01,
+ E_VERSION_NOT_SUPPORTED: 0x02,
+ E_SEQUENCE_NUMBER: 0x04,
+ E_CONNSTATE_LOST: 0x15,
+ E_CONNECTION_ID: 0x21,
+ E_CONNECTION_TYPE: 0x22,
+ E_CONNECTION_OPTION: 0x23,
+ E_NO_MORE_CONNECTIONS: 0x24,
+ E_DATA_CONNECTION: 0x26,
+ E_KNX_CONNECTION: 0x27,
+ E_TUNNELING_LAYER: 0x29,
+}
+
+const MESSAGECODES = {
+ 'L_Raw.req': 0x10,
+ 'L_Data.req': 0x11,
+ 'L_Poll_Data.req': 0x13,
+ 'L_Poll_Data.con': 0x25,
+ 'L_Data.ind': 0x29,
+ 'L_Busmon.ind': 0x2b,
+ 'L_Raw.ind': 0x2d,
+ 'L_Data.con': 0x2e,
+ 'L_Raw.con': 0x2f,
+ 'ETS.Dummy1': 0xc1,
+}
+
+export const APCICODES: string[] = [
+ 'GroupValue_Read',
+ 'GroupValue_Response',
+ 'GroupValue_Write',
+ 'PhysicalAddress_Write',
+ 'PhysicalAddress_Read',
+ 'PhysicalAddress_Response',
+ 'ADC_Read',
+ 'ADC_Response',
+ 'Memory_Read',
+ 'Memory_Response',
+ 'Memory_Write',
+ 'UserMemory',
+ 'DeviceDescriptor_Read',
+ 'DeviceDescriptor_Response',
+ 'Restart',
+ 'OTHER',
+]
+
+export const KnxConstants = {
+ SERVICE_TYPE,
+ CONNECTION_TYPE,
+ PROTOCOL_TYPE,
+ KNX_LAYER,
+ FRAMETYPE,
+ RESPONSECODE,
+ MESSAGECODES,
+ HEADER_SIZE_10: 0x6,
+ KNXNETIP_VERSION_10: 0x10,
+}
+
+/* TODO helper function to print enum keys */
+export function keyText(mapref: string | object, value: number): string {
+ const map = typeof mapref === 'string' ? KnxConstants[mapref] : mapref
+
+ if (typeof map !== 'object') throw Error(`Unknown map: ${mapref}`)
+ for (const [key, v] of Object.entries(map)) if (v === value) return key
+
+ KnxLog.get().trace('not found: %j', value)
+}
diff --git a/src/KnxLog.js b/src/KnxLog.js
deleted file mode 100644
index 024e27f..0000000
--- a/src/KnxLog.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-const util = require('util');
-let logger;
-
-const create = (options) => {
- const level =
- (options && (options.debug ? 'debug' : options.loglevel)) || 'info';
- //console.trace('new logger, level='+lvl);
- return require('log-driver')({
- level,
- format(lvl, msg /*string*/, ...a) {
- // lvl is the log level ie 'debug'
- const ts = new Date().toISOString().replace(/T/, ' ').replace(/Z$/, '');
- return a.length
- ? // if more than one item to log, assume msg is a fmt string
- util.format('[%s] %s ' + msg, lvl, ts, ...a)
- : // otherwise, msg is a plain string
- util.format('[%s] %s %s', lvl, ts, msg);
- },
- });
-};
-
-module.exports = {
- get: (options) => logger || (logger = create(options)),
-};
diff --git a/src/KnxLog.ts b/src/KnxLog.ts
new file mode 100644
index 0000000..396300d
--- /dev/null
+++ b/src/KnxLog.ts
@@ -0,0 +1,39 @@
+import util from 'util'
+import factory, { Logger, LogLevel } from 'log-driver'
+
+export interface KnxLogger {
+ get: (options?: KnxLogOptions) => Logger
+}
+
+let logger: Logger
+
+export interface KnxLogOptions {
+ debug?: boolean
+ loglevel?: LogLevel
+}
+
+const create = (options: KnxLogOptions): Logger => {
+ const level: LogLevel =
+ (options && (options.debug ? 'debug' : options.loglevel)) || 'info'
+ return factory({
+ level,
+ format(lvl: LogLevel, msg: string, ...a: any[]) {
+ const ts = new Date()
+ .toISOString()
+ .replace(/T/, ' ')
+ .replace(/Z$/, '')
+ return a.length
+ ? util.format(`[%s] %s ${msg}`, lvl, ts, ...a)
+ : util.format('[%s] %s %s', lvl, ts, msg)
+ },
+ })
+}
+
+const KnxLog: KnxLogger = {
+ get: (options?: KnxLogOptions): Logger => {
+ if (!logger || options) logger = create(options)
+ return logger
+ },
+}
+
+export default KnxLog
diff --git a/src/KnxProtocol.js b/src/KnxProtocol.js
deleted file mode 100644
index 54a3a7c..0000000
--- a/src/KnxProtocol.js
+++ /dev/null
@@ -1,696 +0,0 @@
-/**
- * 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.
-
-// 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;
diff --git a/src/KnxProtocol.ts b/src/KnxProtocol.ts
new file mode 100644
index 0000000..8a4b8c6
--- /dev/null
+++ b/src/KnxProtocol.ts
@@ -0,0 +1,734 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import util from 'util'
+import ipaddr from 'ipaddr.js'
+import { Parser } from 'binary-parser'
+import BinaryProtocol from 'binary-protocol'
+import * as KnxAddress from './Address'
+import { APCICODES, keyText, KnxConstants } from './KnxConstants'
+import KnxLog from './KnxLog'
+import type { Datagram } from './KnxClient'
+
+export class KnxProtocol extends BinaryProtocol {
+ lengths: { [key: string]: (value: any) => number }
+
+ twoLevelAddressing: boolean
+
+ debug: boolean
+
+ apduStruct: Parser
+
+ parseDatagram(buffer: Buffer): Datagram {
+ const reader = this.createReader(buffer)
+ reader.KNXNetHeader('knxnet')
+ return reader.next().knxnet
+ }
+}
+
+const proto = new KnxProtocol()
+// defaults
+proto.twoLevelAddressing = false
+proto.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: string, context: any) => {
+ const lf = proto.lengths[objectName]
+ return typeof lf === 'function' ? lf(context) : lf
+}
+
+proto.define('IPv4Endpoint', {
+ read(propertyName: string) {
+ 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: string) {
+ if (!value) throw Error('cannot write null value for IPv4Endpoint')
+
+ if (typeof value !== 'string' || !value.match(/\d*\.\d*\.\d*\.\d*:\d*/))
+ throw Error(
+ "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()))
+ this.UInt16BE(port)
+ },
+})
+
+proto.lengths['IPv4Endpoint'] = (value: string) => (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
+proto.define('CRI', {
+ read(propertyName: string) {
+ 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: Datagram['cri']) => {
+ switch (hdr.connection_type) {
+ case KnxConstants.CONNECTION_TYPE.DEVICE_MGMT_CONNECTION:
+ break // TODO
+ case KnxConstants.CONNECTION_TYPE.TUNNEL_CONNECTION:
+ break // TODO
+ default:
+ throw Error(
+ `Unsupported connection type: ${hdr.connection_type}`,
+ )
+ }
+ })
+ .popStack(propertyName, (data: Datagram['cri']) => {
+ if (proto.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: Datagram['cri']) {
+ 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)
+ },
+})
+proto.lengths['CRI'] = (value: Datagram['cri']) => (value ? 4 : 0)
+
+// connection state response/request
+proto.define('ConnState', {
+ read(propertyName: string) {
+ this.pushStack({ channel_id: null, status: null })
+ .UInt8('channel_id')
+ .UInt8('status')
+ .popStack(propertyName, (data: any) => {
+ if (proto.debug) KnxLog.get().trace('read ConnState: %j', data)
+ return data
+ })
+ },
+ write(value: Datagram['connstate']) {
+ if (!value)
+ return KnxLog.get().error('cannot write null value for ConnState')
+ this.UInt8(value.channel_id).UInt8(value.status)
+ },
+})
+proto.lengths['ConnState'] = (value: Datagram['connstate']) => (value ? 2 : 0)
+
+// connection state response/request
+proto.define('TunnState', {
+ read(propertyName: string) {
+ this.pushStack({
+ header_length: null,
+ channel_id: null,
+ seqnum: null,
+ rsvd: null,
+ })
+ .UInt8('header_length')
+ .UInt8('channel_id')
+ .UInt8('seqnum')
+ .UInt8('rsvd')
+ .tap((hdr: any) => {
+ if (proto.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: Datagram['tunnstate']) {
+ if (!value)
+ return KnxLog.get().error(
+ 'TunnState: cannot write null value for TunnState',
+ )
+ if (proto.debug) KnxLog.get().trace('writing TunnState: %j', value)
+ this.UInt8(0x04)
+ .UInt8(value.channel_id)
+ .UInt8(value.seqnum)
+ .UInt8(value.rsvd)
+ },
+})
+proto.lengths['TunnState'] = (value: Datagram['tunnstate']) => (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
+proto.define('HPAI', {
+ read(propertyName: string) {
+ this.pushStack({
+ header_length: 8,
+ protocol_type: null,
+ tunnel_endpoint: null,
+ })
+ .UInt8('header_length')
+ .UInt8('protocol_type')
+ .IPv4Endpoint('tunnel_endpoint')
+ .tap(function (hdr: Datagram['hpai']) {
+ if (this.buffer.length < hdr.header_length) {
+ if (proto.debug)
+ KnxLog.get().trace(
+ '%d %d %d',
+ this.buffer.length,
+ this.offset,
+ hdr.header_length,
+ )
+ throw Error('Incomplete KNXNet HPAI header')
+ }
+ if (proto.debug) {
+ KnxLog.get().trace(
+ 'read HPAI: %j, proto = %s',
+ hdr,
+ keyText('PROTOCOL_TYPE', hdr.protocol_type),
+ )
+ }
+ switch (hdr.protocol_type) {
+ case KnxConstants.PROTOCOL_TYPE.IPV4_TCP:
+ throw Error('TCP is not supported')
+ default:
+ }
+ })
+ .popStack(propertyName, (data) => data)
+ },
+ write(value: Datagram['hpai']) {
+ 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)
+ },
+})
+proto.lengths['HPAI'] = (value: Datagram['hpai']) => {
+ 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.
+
+// 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)
+proto.apduStruct = new Parser().bit6('tpci').bit4('apci').bit6('data')
+
+proto.define('APDU', {
+ read(propertyName: string) {
+ this.pushStack({
+ apdu_length: null,
+ apdu_raw: null,
+ tpci: null,
+ apci: null,
+ data: null,
+ })
+ .UInt8('apdu_length')
+ .tap(function (hdr: Datagram['cemi']['apdu']) {
+ // 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: Datagram['cemi']['apdu']) => {
+ // Parse the APDU. tcpi/apci bits split across byte boundary.
+ // Typical example of protocol designed by committee.
+ const apdu = proto.apduStruct.parse(hdr.apdu_raw)
+ hdr.tpci = apdu.tpci
+ hdr.apci = 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 (proto.debug)
+ KnxLog.get().trace(' unmarshalled APDU: %j', hdr)
+ })
+ .popStack(propertyName, (data) => data)
+ },
+ write(value: Datagram['cemi']['apdu']) {
+ if (!value) throw Error('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 (APCICODES.indexOf(value.apci) === -1)
+ return KnxLog.get().error('invalid APCI code: %j', value)
+ if (total_length < 3)
+ throw Error(
+ util.format('APDU is too small (%d bytes)', total_length),
+ )
+ if (total_length > 17)
+ throw Error(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 + 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 */
+proto.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 Error('APDU value is empty')
+ if (value.data.length > 14)
+ throw Error('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
+ }
+ if (
+ !isNaN(parseFloat(value.data)) &&
+ isFinite(value.data) &&
+ value.data >= 0 &&
+ value.data <= 63
+ ) {
+ return 3
+ }
+ 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 Error(
+ 'APDU payload must be a 6-bit int or an Array/Buffer (1 to 14 bytes)',
+ )
+}
+
+proto.define('CEMI', {
+ read(propertyName: string) {
+ 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: Datagram['cemi']) {
+ 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: Datagram['cemi']) {
+ // parse 16bit control field
+ hdr.ctrl = ctrlStruct.parse(hdr.ctrl as unknown as Buffer)
+ // 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 (proto.debug)
+ KnxLog.get().trace(
+ '--- unmarshalled APDU ==> %j',
+ hdr.apdu,
+ )
+ }
+ }
+ })
+ .popStack(propertyName, (data) => data)
+ },
+ write(value: Datagram['cemi']) {
+ if (!value) throw Error('cannot write null CEMI value')
+ if (proto.debug) KnxLog.get().trace('CEMI.write: \n\t%j', value)
+ if (value.ctrl === null) throw Error('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))
+ .raw(KnxAddress.parse(value.dest_addr, value.ctrl.destAddrType))
+ // 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 Error('no APDU supplied')
+ this.APDU(value.apdu)
+ }
+ }
+ },
+})
+
+proto.lengths['CEMI'] = (value: Datagram['cemi']) => {
+ if (!value) return 0
+ const apdu_length = knxlen('APDU', value.apdu)
+ if (proto.debug)
+ KnxLog.get().trace('knxlen of cemi: %j == %d', value, 8 + apdu_length)
+ return 8 + apdu_length
+}
+
+proto.define('KNXNetHeader', {
+ read(propertyName: string) {
+ 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: Datagram) {
+ if (proto.debug)
+ KnxLog.get().trace('read KNXNetHeader :%j', hdr)
+ if (this.buffer.length + hdr.header_length < this.total_length)
+ throw Error(
+ 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',
+ keyText('SERVICE_TYPE', hdr.service_type),
+ )
+ }
+ }
+ })
+ .popStack(propertyName, (data) => {
+ if (proto.debug)
+ KnxLog.get().trace(JSON.stringify(data, null, 4))
+ return data
+ })
+ },
+ write(value: Datagram) {
+ if (!value) throw Error('cannot write null KNXNetHeader value')
+ value.total_length = knxlen('KNXNetHeader', value)
+ if (proto.debug) KnxLog.get().trace('writing KnxHeader:', value)
+ this.UInt8(KnxConstants.HEADER_SIZE_10) // header length (6 bytes constant)
+ .UInt8(KnxConstants.KNXNETIP_VERSION_10) // protocol version 1.0
+ .UInt16BE(value.service_type)
+ .UInt16BE(value.total_length)
+ switch (value.service_type) {
+ 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 Error(
+ util.format(
+ 'write KNXNetHeader: unhandled serviceType = %s (%j)',
+ keyText('SERVICE_TYPE', value.service_type),
+ value,
+ ),
+ )
+ }
+ }
+ },
+})
+proto.lengths['KNXNetHeader'] = (value: Datagram) => {
+ if (!value) throw Error('Must supply a valid KNXNetHeader value')
+ switch (value.service_type) {
+ // case SERVICE_TYPE.SEARCH_REQUEST:
+ case KnxConstants.SERVICE_TYPE.CONNECT_REQUEST:
+ return (
+ KnxConstants.HEADER_SIZE_10 +
+ 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 (
+ KnxConstants.HEADER_SIZE_10 +
+ 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 (
+ KnxConstants.HEADER_SIZE_10 +
+ knxlen('TunnState', value.tunnstate) +
+ knxlen('CEMI', value.cemi)
+ )
+ case KnxConstants.SERVICE_TYPE.ROUTING_INDICATION:
+ return KnxConstants.HEADER_SIZE_10 + knxlen('CEMI', value.cemi)
+ }
+}
+
+export default proto
diff --git a/src/devices/BinarySwitch.js b/src/devices/BinarySwitch.js
deleted file mode 100644
index 2c82079..0000000
--- a/src/devices/BinarySwitch.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * knx.js - a pure Javascript library for KNX
- * (C) 2016 Elias Karakoulakis
- */
-
-const Datapoint = require('../Datapoint');
-const Log = require('../KnxLog');
-
-class BinarySwitch {
- constructor(options, conn) {
- if (!options || !options.ga) throw 'must supply at least { ga }!';
-
- this.control_ga = options.ga;
- this.status_ga = options.status_ga;
- if (conn) this.bind(conn);
- this.log = Log.get();
- }
- bind(conn) {
- if (!conn) this.log.warn('must supply a valid KNX connection to bind to');
- this.conn = conn;
- this.control = new Datapoint({ ga: this.control_ga }, conn);
- if (this.status_ga)
- this.status = new Datapoint({ ga: this.status_ga }, conn);
- }
- // EventEmitter proxy for status ga (if its set), otherwise proxy control ga
- on(...args) {
- const tgt = this.status_ga ? this.status : this.control;
- try {
- tgt.on(...args);
- } catch (err) {
- this.log.error(err);
- }
- }
- switchOn() {
- if (!this.conn)
- this.log.warn('must supply a valid KNX connection to bind to');
- this.control.write(1);
- }
- switchOff() {
- if (!this.conn)
- this.log.warn('must supply a valid KNX connection to bind to');
- this.control.write(0);
- }
- write(v) {
- if (!this.conn)
- this.log.warn('must supply a valid KNX connection to bind to');
- this.control.write(v);
- }
-}
-
-module.exports = BinarySwitch;
diff --git a/src/devices/BinarySwitch.ts b/src/devices/BinarySwitch.ts
new file mode 100644
index 0000000..19851de
--- /dev/null
+++ b/src/devices/BinarySwitch.ts
@@ -0,0 +1,63 @@
+import { Logger } from 'log-driver'
+import Datapoint from '../Datapoint'
+import KnxLog from '../KnxLog'
+
+export default class BinarySwitch {
+ private control_ga: string
+
+ private status_ga: string
+
+ private conn: any
+
+ private control: Datapoint
+
+ private status: Datapoint
+
+ private log: Logger
+
+ constructor(options: { ga: string; status_ga: string }, conn: any) {
+ if (!options || !options.ga) throw Error('must supply at least { ga }!')
+
+ this.control_ga = options.ga
+ this.status_ga = options.status_ga
+ if (conn) this.bind(conn)
+ this.log = KnxLog.get()
+ }
+
+ bind(conn: any) {
+ if (!conn)
+ this.log.warn('must supply a valid KNX connection to bind to')
+ this.conn = conn
+ this.control = new Datapoint({ ga: this.control_ga }, conn)
+ if (this.status_ga)
+ this.status = new Datapoint({ ga: this.status_ga }, conn)
+ }
+
+ // EventEmitter proxy for status ga (if its set), otherwise proxy control ga
+ on(...args: any[]) {
+ const tgt = this.status_ga ? this.status : this.control
+ try {
+ tgt.on.call(tgt, ...args)
+ } catch (err) {
+ this.log.error(err)
+ }
+ }
+
+ switchOn() {
+ if (!this.conn)
+ this.log.warn('must supply a valid KNX connection to bind to')
+ this.control.write(1)
+ }
+
+ switchOff() {
+ if (!this.conn)
+ this.log.warn('must supply a valid KNX connection to bind to')
+ this.control.write(0)
+ }
+
+ write(v: any) {
+ if (!this.conn)
+ this.log.warn('must supply a valid KNX connection to bind to')
+ this.control.write(v)
+ }
+}
diff --git a/src/devices/index.js b/src/devices/index.js
deleted file mode 100644
index 94acf6a..0000000
--- a/src/devices/index.js
+++ /dev/null
@@ -1 +0,0 @@
-exports.BinarySwitch = require('./BinarySwitch.js');
diff --git a/src/devices/index.ts b/src/devices/index.ts
new file mode 100644
index 0000000..5f3898e
--- /dev/null
+++ b/src/devices/index.ts
@@ -0,0 +1,5 @@
+import BinarySwitch from './BinarySwitch'
+
+export default {
+ BinarySwitch,
+}
diff --git a/src/dptlib/dpt1.js b/src/dptlib/dpt1.js
deleted file mode 100644
index 71b888f..0000000
--- a/src/dptlib/dpt1.js
+++ /dev/null
@@ -1,223 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-const custom_truthiness = (value) => {
- const f = parseFloat(value);
- return !isNaN(f) && isFinite(f)
- ? // numeric values (in native and string form) are truthy if NOT zero
- f !== 0.0
- : // non-numeric value truthiness is Boolean true or the string 'true'.
- value === true || value === 'true';
-};
-
-exports.formatAPDU = (value) => Buffer.from([custom_truthiness(value)]);
-
-exports.fromBuffer = (buf) => {
- if (buf.length !== 1)
- return log.warn(
- 'DPT1.fromBuffer: buf should be 1 byte (got %d bytes)',
- buf.length
- );
- return buf[0] !== 0;
-};
-
-// DPT basetype info hash
-exports.basetype = {
- bitlength: 1,
- valuetype: 'basic',
- desc: '1-bit value',
-};
-
-// DPT subtypes info hash
-exports.subtypes = {
- // 1.001 on/off
- '001': {
- use: 'G',
- name: 'DPT_Switch',
- desc: 'switch',
- enc: { 0: 'Off', 1: 'On' },
- },
-
- // 1.002 boolean
- '002': {
- use: 'G',
- name: 'DPT_Bool',
- desc: 'bool',
- enc: { 0: 'false', 1: 'true' },
- },
-
- // 1.003 enable
- '003': {
- use: 'G',
- name: 'DPT_Enable',
- desc: 'enable',
- enc: { 0: 'disable', 1: 'enable' },
- },
-
- // 1.004 ramp
- '004': {
- use: 'FB',
- name: 'DPT_Ramp',
- desc: 'ramp',
- enc: { 0: 'No ramp', 1: 'Ramp' },
- },
-
- // 1.005 alarm
- '005': {
- use: 'FB',
- name: 'DPT_Alarm',
- desc: 'alarm',
- enc: { 0: 'No alarm', 1: 'Alarm' },
- },
-
- // 1.006 binary value
- '006': {
- use: 'FB',
- name: 'DPT_BinaryValue',
- desc: 'binary value',
- enc: { 0: 'Low', 1: 'High' },
- },
-
- // 1.007 step
- '007': {
- use: 'FB',
- name: 'DPT_Step',
- desc: 'step',
- enc: { 0: 'Decrease', 1: 'Increase' },
- },
-
- // 1.008 up/down
- '008': {
- use: 'G',
- name: 'DPT_UpDown',
- desc: 'up/down',
- enc: { 0: 'Up', 1: 'Down' },
- },
-
- // 1.009 open/close
- '009': {
- use: 'G',
- name: 'DPT_OpenClose',
- desc: 'open/close',
- enc: { 0: 'Open', 1: 'Close' },
- },
-
- // 1.010 start/stop
- '010': {
- use: 'G',
- name: 'DPT_Start',
- desc: 'start/stop',
- enc: { 0: 'Stop', 1: 'Start' },
- },
-
- // 1.011 state
- '011': {
- use: 'FB',
- name: 'DPT_State',
- desc: 'state',
- enc: { 0: 'Inactive', 1: 'Active' },
- },
-
- // 1.012 invert
- '012': {
- use: 'FB',
- name: 'DPT_Invert',
- desc: 'invert',
- enc: { 0: 'Not inverted', 1: 'inverted' },
- },
-
- // 1.013 dim send style
- '013': {
- use: 'FB',
- name: 'DPT_DimSendStyle',
- desc: 'dim send style',
- enc: { 0: 'Start/stop', 1: 'Cyclically' },
- },
-
- // 1.014 input source
- '014': {
- use: 'FB',
- name: 'DPT_InputSource',
- desc: 'input source',
- enc: { 0: 'Fixed', 1: 'Calculated' },
- },
-
- // 1.015 reset
- '015': {
- use: 'G',
- name: 'DPT_Reset',
- desc: 'reset',
- enc: { 0: 'no action(dummy)', 1: 'reset command(trigger)' },
- },
-
- // 1.016 acknowledge
- '016': {
- use: 'G',
- name: 'DPT_Ack',
- desc: 'ack',
- enc: { 0: 'no action(dummy)', 1: 'acknowledge command(trigger)' },
- },
-
- // 1.017 trigger
- '017': {
- use: 'G',
- name: 'DPT_Trigger',
- desc: 'trigger',
- enc: { 0: 'trigger', 1: 'trigger' },
- },
-
- // 1.018 occupied
- '018': {
- use: 'G',
- name: 'DPT_Occupancy',
- desc: 'occupancy',
- enc: { 0: 'not occupied', 1: 'occupied' },
- },
-
- // 1.019 open window or door
- '019': {
- use: 'G',
- name: 'DPT_WindowDoor',
- desc: 'open window/door',
- enc: { 0: 'closed', 1: 'open' },
- },
-
- // 1.021 and/or
- '021': {
- use: 'FB',
- name: 'DPT_LogicalFunction',
- desc: 'and/or',
- enc: { 0: 'logical function OR', 1: 'logical function AND' },
- },
-
- // 1.022 scene A/B
- '022': {
- use: 'FB',
- name: 'DPT_Scene_AB',
- desc: 'scene A/B',
- enc: { 0: 'scene A', 1: 'scene B' },
- },
-
- // 1.023 shutter/blinds mode
- '023': {
- use: 'FB',
- name: 'DPT_ShutterBlinds_Mode',
- desc: 'shutter/blinds mode',
- enc: {
- 0: 'only move Up/Down mode (shutter)',
- 1: 'move Up/Down + StepStop mode (blind)',
- },
- },
-
- // 1.100 cooling/heating ---FIXME---
- 100: {
- use: '???',
- name: 'DPT_Heat/Cool',
- desc: 'heat/cool',
- enc: { 0: '???', 1: '???' },
- },
-};
diff --git a/src/dptlib/dpt1.ts b/src/dptlib/dpt1.ts
new file mode 100644
index 0000000..34a3462
--- /dev/null
+++ b/src/dptlib/dpt1.ts
@@ -0,0 +1,233 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+const custom_truthiness = (value: string | boolean): boolean => {
+ const f = parseFloat(value as string)
+ return !isNaN(f) && isFinite(f)
+ ? // numeric values (in native and string form) are truthy if NOT zero
+ f !== 0.0
+ : // non-numeric value truthiness is Boolean true or the string 'true'.
+ value === true || value === 'true'
+}
+
+const config: DatapointConfig = {
+ id: 'DPT1',
+ formatAPDU: (value) => Buffer.from([custom_truthiness(value) ? 1 : 0]),
+
+ fromBuffer: (buf) => {
+ if (buf.length !== 1) {
+ Log.get().warn(
+ 'DPT1.fromBuffer: buf should be 1 byte (got %d bytes)',
+ buf.length,
+ )
+
+ return null
+ }
+
+ return buf[0] !== 0
+ },
+
+ // DPT basetype info hash
+ basetype: {
+ bitlength: 1,
+ valuetype: 'basic',
+ desc: '1-bit value',
+ },
+
+ // DPT subtypes info hash
+ subtypes: {
+ // 1.001 on/off
+ '001': {
+ use: 'G',
+ name: 'DPT_Switch',
+ desc: 'switch',
+ enc: { 0: 'Off', 1: 'On' },
+ },
+
+ // 1.002 boolean
+ '002': {
+ use: 'G',
+ name: 'DPT_Bool',
+ desc: 'bool',
+ enc: { 0: 'false', 1: 'true' },
+ },
+
+ // 1.003 enable
+ '003': {
+ use: 'G',
+ name: 'DPT_Enable',
+ desc: 'enable',
+ enc: { 0: 'disable', 1: 'enable' },
+ },
+
+ // 1.004 ramp
+ '004': {
+ use: 'FB',
+ name: 'DPT_Ramp',
+ desc: 'ramp',
+ enc: { 0: 'No ramp', 1: 'Ramp' },
+ },
+
+ // 1.005 alarm
+ '005': {
+ use: 'FB',
+ name: 'DPT_Alarm',
+ desc: 'alarm',
+ enc: { 0: 'No alarm', 1: 'Alarm' },
+ },
+
+ // 1.006 binary value
+ '006': {
+ use: 'FB',
+ name: 'DPT_BinaryValue',
+ desc: 'binary value',
+ enc: { 0: 'Low', 1: 'High' },
+ },
+
+ // 1.007 step
+ '007': {
+ use: 'FB',
+ name: 'DPT_Step',
+ desc: 'step',
+ enc: { 0: 'Decrease', 1: 'Increase' },
+ },
+
+ // 1.008 up/down
+ '008': {
+ use: 'G',
+ name: 'DPT_UpDown',
+ desc: 'up/down',
+ enc: { 0: 'Up', 1: 'Down' },
+ },
+
+ // 1.009 open/close
+ '009': {
+ use: 'G',
+ name: 'DPT_OpenClose',
+ desc: 'open/close',
+ enc: { 0: 'Open', 1: 'Close' },
+ },
+
+ // 1.010 start/stop
+ '010': {
+ use: 'G',
+ name: 'DPT_Start',
+ desc: 'start/stop',
+ enc: { 0: 'Stop', 1: 'Start' },
+ },
+
+ // 1.011 state
+ '011': {
+ use: 'FB',
+ name: 'DPT_State',
+ desc: 'state',
+ enc: { 0: 'Inactive', 1: 'Active' },
+ },
+
+ // 1.012 invert
+ '012': {
+ use: 'FB',
+ name: 'DPT_Invert',
+ desc: 'invert',
+ enc: { 0: 'Not inverted', 1: 'inverted' },
+ },
+
+ // 1.013 dim send style
+ '013': {
+ use: 'FB',
+ name: 'DPT_DimSendStyle',
+ desc: 'dim send style',
+ enc: { 0: 'Start/stop', 1: 'Cyclically' },
+ },
+
+ // 1.014 input source
+ '014': {
+ use: 'FB',
+ name: 'DPT_InputSource',
+ desc: 'input source',
+ enc: { 0: 'Fixed', 1: 'Calculated' },
+ },
+
+ // 1.015 reset
+ '015': {
+ use: 'G',
+ name: 'DPT_Reset',
+ desc: 'reset',
+ enc: { 0: 'no action(dummy)', 1: 'reset command(trigger)' },
+ },
+
+ // 1.016 acknowledge
+ '016': {
+ use: 'G',
+ name: 'DPT_Ack',
+ desc: 'ack',
+ enc: { 0: 'no action(dummy)', 1: 'acknowledge command(trigger)' },
+ },
+
+ // 1.017 trigger
+ '017': {
+ use: 'G',
+ name: 'DPT_Trigger',
+ desc: 'trigger',
+ enc: { 0: 'trigger', 1: 'trigger' },
+ },
+
+ // 1.018 occupied
+ '018': {
+ use: 'G',
+ name: 'DPT_Occupancy',
+ desc: 'occupancy',
+ enc: { 0: 'not occupied', 1: 'occupied' },
+ },
+
+ // 1.019 open window or door
+ '019': {
+ use: 'G',
+ name: 'DPT_WindowDoor',
+ desc: 'open window/door',
+ enc: { 0: 'closed', 1: 'open' },
+ },
+
+ // 1.021 and/or
+ '021': {
+ use: 'FB',
+ name: 'DPT_LogicalFunction',
+ desc: 'and/or',
+ enc: { 0: 'logical function OR', 1: 'logical function AND' },
+ },
+
+ // 1.022 scene A/B
+ '022': {
+ use: 'FB',
+ name: 'DPT_Scene_AB',
+ desc: 'scene A/B',
+ enc: { 0: 'scene A', 1: 'scene B' },
+ },
+
+ // 1.023 shutter/blinds mode
+ '023': {
+ use: 'FB',
+ name: 'DPT_ShutterBlinds_Mode',
+ desc: 'shutter/blinds mode',
+ enc: {
+ 0: 'only move Up/Down mode (shutter)',
+ 1: 'move Up/Down + StepStop mode (blind)',
+ },
+ },
+
+ // 1.100 cooling/heating ---FIXME---
+ 100: {
+ use: '???',
+ name: 'DPT_Heat/Cool',
+ desc: 'heat/cool',
+ enc: { 0: '???', 1: '???' },
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt10.js b/src/dptlib/dpt10.js
deleted file mode 100644
index 92a83d4..0000000
--- a/src/dptlib/dpt10.js
+++ /dev/null
@@ -1,97 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-//
-// DPT10.*: time (3 bytes)
-//
-const util = require('util');
-const dowTimeRegexp = /((\d)\/)?(\d{1,2}):(\d{1,2}):(\d{1,2})/;
-
-// DPTFrame to parse a DPT10 frame.
-// Always 8-bit aligned.
-
-exports.formatAPDU = (value) => {
- let dow, hour, minute, second;
- // day of week. NOTE: JS Sunday = 0
- switch (typeof value) {
- case 'string':
- // try to parse
- match = dowTimeRegexp.exec(value);
- if (match) {
- const currentDoW = ((new Date().getDay() - 7) % 7) + 7;
- dow = match[2] != undefined ? parseInt(match[2]) : currentDoW;
- hour = parseInt(match[3]);
- minute = parseInt(match[4]);
- second = parseInt(match[5]);
- } else {
- log.warn('DPT10: invalid time format (%s)', value);
- }
- break;
- case 'object':
- if (value.constructor.name != 'Date') {
- log.warn('Must supply a Date or String for DPT10 time');
- break;
- }
- case 'number':
- value = new Date(value);
- default:
- dow = ((value.getDay() - 7) % 7) + 7;
- hour = value.getHours();
- minute = value.getMinutes();
- second = value.getSeconds();
- }
-
- return Buffer.from([(dow << 5) + hour, minute, second]);
-};
-
-// return a JS Date from a DPT10 payload, with DOW/hour/month/seconds set to the buffer values.
-// The week/month/year are inherited from the current timestamp.
-exports.fromBuffer = (buf) => {
- if (buf.length != 3) return log.warn('DPT10: Buffer should be 3 bytes long');
- const [dnh, minutes, seconds] = buf;
- const dow = (dnh & 0b11100000) >> 5;
- const hours = dnh & 0b00011111;
- if (
- hours < 0 ||
- hours > 23 ||
- minutes < 0 ||
- minutes > 59 ||
- seconds < 0 ||
- seconds > 59
- )
- return log.warn(
- 'DPT10: buffer %j (decoded as %d:%d:%d) is not a valid time',
- buf,
- hours,
- minutes,
- seconds
- );
-
- const d = new Date();
- if (d.getDay() !== dow)
- // adjust day of month to get the day of week right
- d.setDate(d.getDate() + dow - d.getDay());
- // TODO: Shouldn't this be UTCHours?
- d.setHours(hours, minutes, seconds);
- return d;
-};
-
-// DPT10 base type info
-exports.basetype = {
- bitlength: 24,
- valuetype: 'composite',
- desc: 'day of week + time of day',
-};
-
-// DPT10 subtypes info
-exports.subtypes = {
- // 10.001 time of day
- '001': {
- name: 'DPT_TimeOfDay',
- desc: 'time of day',
- },
-};
diff --git a/src/dptlib/dpt10.ts b/src/dptlib/dpt10.ts
new file mode 100644
index 0000000..2903e6d
--- /dev/null
+++ b/src/dptlib/dpt10.ts
@@ -0,0 +1,127 @@
+/* eslint-disable no-fallthrough */
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT10.*: time (3 bytes)
+//
+const dowTimeRegexp = /((\d)\/)?(\d{1,2}):(\d{1,2}):(\d{1,2})/
+
+// DPTFrame to parse a DPT10 frame.
+// Always 8-bit aligned.
+
+const config: DatapointConfig = {
+ id: 'DPT10',
+ formatAPDU: (value) => {
+ let dow: number
+ let hour: number
+ let minute: number
+ let second: number
+ // day of week. NOTE: JS Sunday = 0
+ switch (typeof value) {
+ case 'string':
+ {
+ // try to parse
+ const match = dowTimeRegexp.exec(value)
+ if (match) {
+ const currentDoW = ((new Date().getDay() - 7) % 7) + 7
+ dow =
+ match[2] !== undefined
+ ? parseInt(match[2])
+ : currentDoW
+ hour = parseInt(match[3])
+ minute = parseInt(match[4])
+ second = parseInt(match[5])
+ } else {
+ Log.get().warn('DPT10: invalid time format (%s)', value)
+ }
+ }
+ break
+ case 'object':
+ if (value.constructor.name !== 'Date') {
+ Log.get().warn(
+ 'Must supply a Date or String for DPT10 time',
+ )
+ break
+ }
+ case 'number':
+ value = new Date(value)
+
+ default:
+ dow = ((value.getDay() - 7) % 7) + 7
+ hour = value.getHours()
+ minute = value.getMinutes()
+ second = value.getSeconds()
+ }
+
+ return Buffer.from([(dow << 5) + hour, minute, second])
+ },
+
+ // return a JS Date from a DPT10 payload, with DOW/hour/month/seconds set to the buffer values.
+ // The week/month/year are inherited from the current timestamp.
+ fromBuffer: (buf) => {
+ if (buf.length !== 3) {
+ Log.get().error(
+ 'DPT10: Buffer should be 3 bytes long, got',
+ buf.length,
+ )
+ return null
+ }
+
+ const d = new Date()
+ let dow = (buf[0] & 0b11100000) >> 5 // Day of week
+ const hours = buf[0] & 0b00011111
+ const minutes = buf[1]
+ const seconds = buf[2]
+ if (
+ hours >= 0 &&
+ hours <= 23 &&
+ minutes >= 0 &&
+ minutes <= 59 &&
+ seconds >= 0 &&
+ seconds <= 59
+ ) {
+ // 18/10/2021 if dow = 0, then the KNX device has not sent this optional value.
+ if (d.getDay() !== dow && dow > 0) {
+ if (dow === 7) dow = 0 // 18/10/2021 fix for the Sunday
+ // adjust day of month to get the day of week right
+ d.setDate(d.getDate() + dow - d.getDay())
+ }
+ d.setHours(hours)
+ d.setMinutes(minutes)
+ d.setSeconds(seconds)
+ } else {
+ Log.get().warn(
+ 'DPT10: buffer %j (decoded as %d:%d:%d) is not a valid time',
+ buf,
+ hours,
+ minutes,
+ seconds,
+ )
+ }
+ return d
+ },
+
+ // DPT10 base type info
+ basetype: {
+ bitlength: 24,
+ valuetype: 'composite',
+ desc: 'day of week + time of day',
+ },
+
+ // DPT10 subtypes info
+ subtypes: {
+ // 10.001 time of day
+ '001': {
+ name: 'DPT_TimeOfDay',
+ desc: 'time of day',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt11.js b/src/dptlib/dpt11.js
deleted file mode 100644
index 3b47bdf..0000000
--- a/src/dptlib/dpt11.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-const util = require('util');
-//
-// DPT11.*: date
-//
-exports.formatAPDU = (value) => {
- if (value == null) return log.error('cannot write null value for DPT11');
- switch (typeof value) {
- case 'string':
- case 'number':
- value = new Date(value);
- break;
- case 'object':
- // this expects the month property to be zero-based (January = 0, etc.)
- if (value instanceof Date) break;
- const { year, month, day } = value;
- value = new Date(parseInt(year), parseInt(month), parseInt(day));
- }
- if (isNaN(value.getDate()))
- return log.error(
- 'Must supply a numeric timestamp, Date or String object for DPT11 Date'
- );
-
- const year = value.getFullYear();
- return Buffer.from([
- value.getDate(),
- value.getMonth() + 1,
- year - (year >= 2000 ? 2000 : 1900),
- ]);
-};
-
-exports.fromBuffer = (buf) => {
- if (buf.length != 3) return log.error('Buffer should be 3 bytes long');
- const day = buf[0] & 31; //0b00011111);
- const month = buf[1] & 15; //0b00001111);
- let year = buf[2] & 127; //0b01111111);
- year = year + (year > 89 ? 1900 : 2000);
- if (
- day < 1 ||
- day > 31 ||
- month < 1 ||
- month > 12 ||
- year < 1990 ||
- year > 2089
- ) {
- log.error(
- '%j => %d/%d/%d is not valid date according to DPT11, setting to 1990/01/01',
- buf,
- day,
- month,
- year
- );
- //return new Date(1990, 01, 01);
- throw new Error('Error converting date buffer to Date object.');
- }
- return new Date(year, month - 1, day);
-};
-
-// DPT11 base type info
-exports.basetype = {
- bitlength: 24,
- valuetype: 'composite',
- desc: '3-byte date value',
-};
-
-// DPT11 subtypes info
-exports.subtypes = {
- // 11.001 date
- '001': {
- name: 'DPT_Date',
- desc: 'Date',
- },
-};
diff --git a/src/dptlib/dpt11.ts b/src/dptlib/dpt11.ts
new file mode 100644
index 0000000..4b95793
--- /dev/null
+++ b/src/dptlib/dpt11.ts
@@ -0,0 +1,87 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT11.*: date
+//
+const config: DatapointConfig = {
+ id: 'DPT11',
+ formatAPDU: (value) => {
+ if (value == null)
+ return Log.get().error('cannot write null value for DPT11')
+ switch (typeof value) {
+ case 'string':
+ case 'number':
+ value = new Date(value)
+ break
+ case 'object': {
+ // this expects the month property to be zero-based (January = 0, etc.)
+ if (value instanceof Date) break
+ const { year, month, day } = value
+ value = new Date(parseInt(year), parseInt(month), parseInt(day))
+ }
+ }
+ if (isNaN(value.getDate()))
+ return Log.get().error(
+ 'Must supply a numeric timestamp, Date or String object for DPT11 Date',
+ )
+
+ const year = value.getFullYear()
+ return Buffer.from([
+ value.getDate(),
+ value.getMonth() + 1,
+ year - (year >= 2000 ? 2000 : 1900),
+ ])
+ },
+
+ fromBuffer: (buf) => {
+ if (buf.length !== 3)
+ return Log.get().error('Buffer should be 3 bytes long')
+ const day = buf[0] & 31 // 0b00011111);
+ const month = buf[1] & 15 // 0b00001111);
+ let year = buf[2] & 127 // 0b01111111);
+ year += year > 89 ? 1900 : 2000
+ if (
+ day < 1 ||
+ day > 31 ||
+ month < 1 ||
+ month > 12 ||
+ year < 1990 ||
+ year > 2089
+ ) {
+ Log.get().error(
+ '%j => %d/%d/%d is not valid date according to DPT11, setting to 1990/01/01',
+ buf,
+ day,
+ month,
+ year,
+ )
+ // return new Date(1990, 01, 01);
+ throw new Error('Error converting date buffer to Date object.')
+ }
+ return new Date(year, month - 1, day)
+ },
+
+ // DPT11 base type info
+ basetype: {
+ bitlength: 24,
+ valuetype: 'composite',
+ desc: '3-byte date value',
+ },
+
+ // DPT11 subtypes info
+ subtypes: {
+ // 11.001 date
+ '001': {
+ name: 'DPT_Date',
+ desc: 'Date',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt12.js b/src/dptlib/dpt12.js
deleted file mode 100644
index 7534e2a..0000000
--- a/src/dptlib/dpt12.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-//
-// DPT12.*: 4-byte unsigned value
-//
-
-
-// DPT12 base type info
-exports.basetype = {
- bitlength : 32,
- signedness: "unsigned",
- valuetype : "basic",
- desc : "4-byte unsigned value"
-}
-
-// DPT12 subtype info
-exports.subtypes = {
- // 12.001 counter pulses
- "001" : {
- "name" : "DPT_Value_4_Ucount", "desc" : "counter pulses"
- }
-}
diff --git a/src/dptlib/dpt12.ts b/src/dptlib/dpt12.ts
new file mode 100644
index 0000000..79f7612
--- /dev/null
+++ b/src/dptlib/dpt12.ts
@@ -0,0 +1,31 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+
+//
+// DPT12.*: 4-byte unsigned value
+//
+
+// DPT12 base type info
+const config: DatapointConfig = {
+ id: 'DPT12',
+ basetype: {
+ bitlength: 32,
+ signedness: 'unsigned',
+ valuetype: 'basic',
+ desc: '4-byte unsigned value',
+ },
+ // DPT12 subtype info
+ subtypes: {
+ // 12.001 counter pulses
+ '001': {
+ name: 'DPT_Value_4_Ucount',
+ desc: 'counter pulses',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt13.js b/src/dptlib/dpt13.js
deleted file mode 100644
index e8e412c..0000000
--- a/src/dptlib/dpt13.js
+++ /dev/null
@@ -1,73 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-//
-// DPT13: 4-byte signed value
-//
-
-// DPT13 base type info
-exports.basetype = {
- "bitlength" : 32,
- "signedness": "signed",
- "valuetype" : "basic",
- "desc" : "4-byte signed value",
- "range" : [-Math.pow(2, 31), Math.pow(2, 31)-1]
-}
-
-// DPT13 subtypes
-exports.subtypes = {
- // 13.001 counter pulses (signed)
- "001" : {
- "name" : "DPT_Value_4_Count", "desc" : "counter pulses (signed)",
- "unit" : "pulses"
- },
-
- "002" : {
- "name" : "DPT_Value_Activation_Energy", "desc" : "activation energy (J/mol)" ,
- "unit" : "J/mol"
- },
-
- // 13.010 active energy (Wh)
- "010" : {
- "name" : "DPT_ActiveEnergy", "desc" : "active energy (Wh)",
- "unit" : "Wh"
- },
-
- // 13.011 apparent energy (VAh)
- "011" : {
- "name" : "DPT_ApparantEnergy", "desc" : "apparent energy (VAh)",
- "unit" : "VAh"
- },
-
- // 13.012 reactive energy (VARh)
- "012" : {
- "name" : "DPT_ReactiveEnergy", "desc" : "reactive energy (VARh)",
- "unit" : "VARh"
- },
-
- // 13.013 active energy (KWh)
- "013" : {
- "name" : "DPT_ActiveEnergy_kWh", "desc" : "active energy (kWh)",
- "unit" : "kWh"
- },
-
- // 13.014 apparent energy (kVAh)
- "014" : {
- "name" : "DPT_ApparantEnergy_kVAh", "desc" : "apparent energy (kVAh)",
- "unit" : "VAh"
- },
-
- // 13.015 reactive energy (kVARh)
- "015" : {
- "name" : "DPT_ReactiveEnergy_kVARh", "desc" : "reactive energy (kVARh)",
- "unit" : "kVARh"
- },
-
- // 13.100 time lag(s)
- "100" : {
- "name" : "DPT_LongDeltaTimeSec", "desc" : "time lag(s)",
- "unit" : "s"
- },
-}
diff --git a/src/dptlib/dpt13.ts b/src/dptlib/dpt13.ts
new file mode 100644
index 0000000..9466fb3
--- /dev/null
+++ b/src/dptlib/dpt13.ts
@@ -0,0 +1,89 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+
+//
+// DPT13: 4-byte signed value
+//
+
+// DPT13 base type info
+const config: DatapointConfig = {
+ id: 'DPT13',
+ basetype: {
+ bitlength: 32,
+ signedness: 'signed',
+ valuetype: 'basic',
+ desc: '4-byte signed value',
+ range: [-(2 ** 31), 2 ** 31 - 1],
+ },
+
+ // DPT13 subtypes
+ subtypes: {
+ // 13.001 counter pulses (signed)
+ '001': {
+ name: 'DPT_Value_4_Count',
+ desc: 'counter pulses (signed)',
+ unit: 'pulses',
+ },
+
+ '002': {
+ name: 'DPT_Value_Activation_Energy',
+ desc: 'activation energy (J/mol)',
+ unit: 'J/mol',
+ },
+
+ // 13.010 active energy (Wh)
+ '010': {
+ name: 'DPT_ActiveEnergy',
+ desc: 'active energy (Wh)',
+ unit: 'Wh',
+ },
+
+ // 13.011 apparent energy (VAh)
+ '011': {
+ name: 'DPT_ApparantEnergy',
+ desc: 'apparent energy (VAh)',
+ unit: 'VAh',
+ },
+
+ // 13.012 reactive energy (VARh)
+ '012': {
+ name: 'DPT_ReactiveEnergy',
+ desc: 'reactive energy (VARh)',
+ unit: 'VARh',
+ },
+
+ // 13.013 active energy (KWh)
+ '013': {
+ name: 'DPT_ActiveEnergy_kWh',
+ desc: 'active energy (kWh)',
+ unit: 'kWh',
+ },
+
+ // 13.014 apparent energy (kVAh)
+ '014': {
+ name: 'DPT_ApparantEnergy_kVAh',
+ desc: 'apparent energy (kVAh)',
+ unit: 'VAh',
+ },
+
+ // 13.015 reactive energy (kVARh)
+ '015': {
+ name: 'DPT_ReactiveEnergy_kVARh',
+ desc: 'reactive energy (kVARh)',
+ unit: 'kVARh',
+ },
+
+ // 13.100 time lag(s)
+ '100': {
+ name: 'DPT_LongDeltaTimeSec',
+ desc: 'time lag(s)',
+ unit: 's',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt14.js b/src/dptlib/dpt14.js
deleted file mode 100644
index 2dbdc18..0000000
--- a/src/dptlib/dpt14.js
+++ /dev/null
@@ -1,165 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-//
-// DPT14.*: 4-byte floating point value
-//
-
-/* In sharp contrast to DPT9 (16-bit floating point - JS spec does not support),
- * the case for 32-bit floating point is simple...
- */
-
-exports.formatAPDU = (value) => {
- if (value == null || typeof value != 'number')
- log.error('DPT14: Must supply a number value');
- const apdu_data = Buffer.alloc(4);
- apdu_data.writeFloatBE(value, 0);
- return apdu_data;
-};
-
-exports.fromBuffer = (buf) => {
- if (buf.length != 4) log.warn('DPT14: Buffer should be 4 bytes long');
- return buf.readFloatBE(0);
-};
-
-// DPT14 base type info
-exports.basetype = {
- bitlength: 32,
- valuetype: 'basic',
- range: [0, Math.pow(2, 32)],
- desc: '32-bit floating point value',
-};
-
-// DPT14 subtypes info
-exports.subtypes = {
- // TODO
- '007': {
- name: 'DPT_Value_AngleDeg°',
- desc: 'angle, degree',
- unit: '°',
- },
-
- '019': {
- name: 'DPT_Value_Electric_Current',
- desc: 'electric current',
- unit: 'A',
- },
-
- '027': {
- name: 'DPT_Value_Electric_Potential',
- desc: 'electric potential',
- unit: 'V',
- },
-
- '028': {
- name: 'DPT_Value_Electric_PotentialDifference',
- desc: 'electric potential difference',
- unit: 'V',
- },
-
- '031': {
- name: 'DPT_Value_Energ',
- desc: 'energy',
- unit: 'J',
- },
-
- '032': {
- name: 'DPT_Value_Force',
- desc: 'force',
- unit: 'N',
- },
-
- '033': {
- name: 'DPT_Value_Frequency',
- desc: 'frequency',
- unit: 'Hz',
- },
-
- '036': {
- name: 'DPT_Value_Heat_FlowRate',
- desc: 'heat flow rate',
- unit: 'W',
- },
-
- '037': {
- name: 'DPT_Value_Heat_Quantity',
- desc: 'heat, quantity of',
- unit: 'J',
- },
-
- '038': {
- name: 'DPT_Value_Impedance',
- desc: 'impedance',
- unit: 'Ω',
- },
-
- '039': {
- name: 'DPT_Value_Length',
- desc: 'length',
- unit: 'm',
- },
-
- '051': {
- name: 'DPT_Value_Mass',
- desc: 'mass',
- unit: 'kg',
- },
-
- '056': {
- name: 'DPT_Value_Power',
- desc: 'power',
- unit: 'W',
- },
-
- '065': {
- name: 'DPT_Value_Speed',
- desc: 'speed',
- unit: 'm/s',
- },
-
- '066': {
- name: 'DPT_Value_Stress',
- desc: 'stress',
- unit: 'Pa',
- },
-
- '067': {
- name: 'DPT_Value_Surface_Tension',
- desc: 'surface tension',
- unit: '1/Nm',
- },
-
- '068': {
- name: 'DPT_Value_Common_Temperature',
- desc: 'temperature, common',
- unit: '°C',
- },
-
- '069': {
- name: 'DPT_Value_Absolute_Temperature',
- desc: 'temperature (absolute)',
- unit: 'K',
- },
-
- '070': {
- name: 'DPT_Value_TemperatureDifference',
- desc: 'temperature difference',
- unit: 'K',
- },
-
- '078': {
- name: 'DPT_Value_Weight',
- desc: 'weight',
- unit: 'N',
- },
-
- '079': {
- name: 'DPT_Value_Work',
- desc: 'work',
- unit: 'J',
- },
-};
diff --git a/src/dptlib/dpt14.ts b/src/dptlib/dpt14.ts
new file mode 100644
index 0000000..6cfd7a6
--- /dev/null
+++ b/src/dptlib/dpt14.ts
@@ -0,0 +1,170 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT14.*: 4-byte floating point value
+//
+
+/* In sharp contrast to DPT9 (16-bit floating point - JS spec does not support),
+ * the case for 32-bit floating point is simple...
+ */
+const config: DatapointConfig = {
+ id: 'DPT14',
+ formatAPDU: (value) => {
+ if (value == null || typeof value !== 'number')
+ Log.get().error('DPT14: Must supply a number value')
+ const apdu_data = Buffer.alloc(4)
+ apdu_data.writeFloatBE(value, 0)
+ return apdu_data
+ },
+
+ fromBuffer: (buf) => {
+ if (buf.length !== 4)
+ Log.get().warn('DPT14: Buffer should be 4 bytes long')
+ return buf.readFloatBE(0)
+ },
+
+ // DPT14 base type info
+ basetype: {
+ bitlength: 32,
+ valuetype: 'basic',
+ range: [0, 2 ** 32],
+ desc: '32-bit floating point value',
+ },
+
+ // DPT14 subtypes info
+ subtypes: {
+ // TODO
+ '007': {
+ name: 'DPT_Value_AngleDeg°',
+ desc: 'angle, degree',
+ unit: '°',
+ },
+
+ '019': {
+ name: 'DPT_Value_Electric_Current',
+ desc: 'electric current',
+ unit: 'A',
+ },
+
+ '027': {
+ name: 'DPT_Value_Electric_Potential',
+ desc: 'electric potential',
+ unit: 'V',
+ },
+
+ '028': {
+ name: 'DPT_Value_Electric_PotentialDifference',
+ desc: 'electric potential difference',
+ unit: 'V',
+ },
+
+ '031': {
+ name: 'DPT_Value_Energ',
+ desc: 'energy',
+ unit: 'J',
+ },
+
+ '032': {
+ name: 'DPT_Value_Force',
+ desc: 'force',
+ unit: 'N',
+ },
+
+ '033': {
+ name: 'DPT_Value_Frequency',
+ desc: 'frequency',
+ unit: 'Hz',
+ },
+
+ '036': {
+ name: 'DPT_Value_Heat_FlowRate',
+ desc: 'heat flow rate',
+ unit: 'W',
+ },
+
+ '037': {
+ name: 'DPT_Value_Heat_Quantity',
+ desc: 'heat, quantity of',
+ unit: 'J',
+ },
+
+ '038': {
+ name: 'DPT_Value_Impedance',
+ desc: 'impedance',
+ unit: 'Ω',
+ },
+
+ '039': {
+ name: 'DPT_Value_Length',
+ desc: 'length',
+ unit: 'm',
+ },
+
+ '051': {
+ name: 'DPT_Value_Mass',
+ desc: 'mass',
+ unit: 'kg',
+ },
+
+ '056': {
+ name: 'DPT_Value_Power',
+ desc: 'power',
+ unit: 'W',
+ },
+
+ '065': {
+ name: 'DPT_Value_Speed',
+ desc: 'speed',
+ unit: 'm/s',
+ },
+
+ '066': {
+ name: 'DPT_Value_Stress',
+ desc: 'stress',
+ unit: 'Pa',
+ },
+
+ '067': {
+ name: 'DPT_Value_Surface_Tension',
+ desc: 'surface tension',
+ unit: '1/Nm',
+ },
+
+ '068': {
+ name: 'DPT_Value_Common_Temperature',
+ desc: 'temperature, common',
+ unit: '°C',
+ },
+
+ '069': {
+ name: 'DPT_Value_Absolute_Temperature',
+ desc: 'temperature (absolute)',
+ unit: 'K',
+ },
+
+ '070': {
+ name: 'DPT_Value_TemperatureDifference',
+ desc: 'temperature difference',
+ unit: 'K',
+ },
+
+ '078': {
+ name: 'DPT_Value_Weight',
+ desc: 'weight',
+ unit: 'N',
+ },
+
+ '079': {
+ name: 'DPT_Value_Work',
+ desc: 'work',
+ unit: 'J',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt15.js b/src/dptlib/dpt15.js
deleted file mode 100644
index b50e64c..0000000
--- a/src/dptlib/dpt15.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-//
-// DPT15.*: Access data
-//
-
-// TODO: implement fromBuffer, formatAPDU
-
-// DPT15 base type info
-exports.basetype = {
- "bitlength" : 32,
- "valuetype" : "basic",
- "desc" : "4-byte access control data"
-}
-
-// DPT15 subtypes info
-exports.subtypes = {
- "000" : {
- "name" : "DPT_Access_Data"
- }
-}
diff --git a/src/dptlib/dpt15.ts b/src/dptlib/dpt15.ts
new file mode 100644
index 0000000..91e2de8
--- /dev/null
+++ b/src/dptlib/dpt15.ts
@@ -0,0 +1,33 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+
+//
+// DPT15.*: Access data
+//
+
+// TODO: implement fromBuffer, formatAPDU
+
+// DPT15 base type info
+
+const config: DatapointConfig = {
+ id: 'DPT15',
+ basetype: {
+ bitlength: 32,
+ valuetype: 'basic',
+ desc: '4-byte access control data',
+ },
+
+ // DPT15 subtypes info
+ subtypes: {
+ '000': {
+ name: 'DPT_Access_Data',
+ desc: 'Access Data',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt16.js b/src/dptlib/dpt16.js
deleted file mode 100644
index 142d099..0000000
--- a/src/dptlib/dpt16.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-//
-// DPT16: ASCII string (max 14 chars)
-//
-
-exports.formatAPDU = (value) => {
- if (typeof value !== 'string') return log.warn('Must supply a string value');
-
- const buf = Buffer.alloc(14);
- buf.write(value, 'ascii');
- return buf;
-};
-
-exports.fromBuffer = (buf) => buf.toString('ascii');
-// DPT16 basetype info
-exports.basetype = {
- bitlength: 14 * 8,
- valuetype: 'basic',
- desc: '14-character string',
-};
-
-// DPT9 subtypes
-exports.subtypes = {
- // 16.000 ASCII string
- '000': {
- use: 'G',
- name: 'DPT_String_ASCII',
- desc: 'ASCII string',
- force_encoding: 'US-ASCII',
- },
-
- // 16.001 ISO-8859-1 string
- '001': {
- use: 'G',
- name: 'DPT_String_8859_1',
- desc: 'ISO-8859-1 string',
- force_encoding: 'ISO-8859-1',
- },
-};
diff --git a/src/dptlib/dpt16.ts b/src/dptlib/dpt16.ts
new file mode 100644
index 0000000..52ef12b
--- /dev/null
+++ b/src/dptlib/dpt16.ts
@@ -0,0 +1,52 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT16: ASCII string (max 14 chars)
+//
+
+const config: DatapointConfig = {
+ id: 'DPT16',
+ formatAPDU: (value) => {
+ if (typeof value !== 'string')
+ return Log.get().warn('Must supply a string value')
+
+ const buf = Buffer.alloc(14)
+ buf.write(value, 'ascii')
+ return buf
+ },
+
+ fromBuffer: (buf) => buf.toString('ascii'),
+ // DPT16 basetype info
+ basetype: {
+ bitlength: 14 * 8,
+ valuetype: 'basic',
+ desc: '14-character string',
+ },
+
+ // DPT9 subtypes
+ subtypes: {
+ // 16.000 ASCII string
+ '000': {
+ use: 'G',
+ name: 'DPT_String_ASCII',
+ desc: 'ASCII string',
+ force_encoding: 'US-ASCII',
+ },
+
+ // 16.001 ISO-8859-1 string
+ '001': {
+ use: 'G',
+ name: 'DPT_String_8859_1',
+ desc: 'ISO-8859-1 string',
+ force_encoding: 'ISO-8859-1',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt17.js b/src/dptlib/dpt17.js
deleted file mode 100644
index 29aa625..0000000
--- a/src/dptlib/dpt17.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-//
-// DPT17: Scene number
-//
-
-// TODO: implement fromBuffer, formatAPDU
-
-// DPT17 basetype info
-exports.basetype = {
- bitlength : 8,
- valuetype : 'basic',
- desc : "scene number"
-}
-
-// DPT17 subtypes
-exports.subtypes = {
- // 17.001 Scene number
- "001" : { use : "G",
- name : "DPT_SceneNumber", desc : "Scene Number",
- },
-}
diff --git a/src/dptlib/dpt17.ts b/src/dptlib/dpt17.ts
new file mode 100644
index 0000000..5cfd0b7
--- /dev/null
+++ b/src/dptlib/dpt17.ts
@@ -0,0 +1,29 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+
+//
+// DPT17: Scene number
+//
+
+// TODO: implement fromBuffer, formatAPDU
+
+const config: DatapointConfig = {
+ id: 'DPT17',
+ // DPT17 basetype info
+ basetype: {
+ bitlength: 8,
+ valuetype: 'basic',
+ desc: 'scene number',
+ },
+ // DPT17 subtypes
+ subtypes: {
+ // 17.001 Scene number
+ '001': { use: 'G', name: 'DPT_SceneNumber', desc: 'Scene Number' },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt18.js b/src/dptlib/dpt18.js
deleted file mode 100644
index e0ef060..0000000
--- a/src/dptlib/dpt18.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-//
-// DPT18: 8-bit Scene Control
-//
-
-/*
- class DPT18_Frame < DPTFrame
- bit1 :exec_learn, {
- :display_name : "Execute=0, Learn = 1"
- }
- bit1 :pad, {
- :display_name : "Reserved bit"
- }
- bit6 :data, {
- :display_name : "Scene number"
- }
- end
-*/
-
-// TODO: implement fromBuffer, formatAPDU
-const log = require('log-driver').logger;
-
-exports.formatAPDU = function (value) {
- if (value == null) log.warn("DPT18: cannot write null value");
- else {
- var apdu_data = new Buffer(1);
- if (typeof value == 'object' &&
- value.hasOwnProperty("save_recall") &&
- value.hasOwnProperty("scenenumber")) {
- var sSceneNumberbinary = ((value.scenenumber - 1) >>> 0).toString(2);
- var sVal = value.save_recall + "0" + sSceneNumberbinary.padStart(6, "0");
- //console.log("BANANA SEND HEX " + sVal.toString("hex").toUpperCase())
- apdu_data[0] = parseInt(sVal, 2);// 0b10111111;
- } else {
- log.error("DPT18: Must supply a value object of {save_recall, scenenumber}");
- }
- return apdu_data;
- }
-}
-
-exports.fromBuffer = function (buf) {
- //console.log("BANANA BUFF RECEIVE HEX " + buf.toString("hex").toUpperCase())
- if (buf.length != 1) {
- log.error("DP18: Buffer should be 1 byte long");
- } else {
- var sBit = (parseInt(buf.toString("hex").toUpperCase(), 16).toString(2)).padStart(8, '0'); // Get bit from hex
- //console.log("BANANA BUFF RECEIVE BIT " + sBit)
- return {
- save_recall: sBit.substring(0, 1),
- scenenumber: parseInt(sBit.substring(2), 2) + 1
- }
- };
-}
-
-// DPT18 basetype info
-exports.basetype = {
- bitlength: 8,
- valuetype: 'composite',
- desc: "8-bit Scene Activate/Learn + number"
-}
-
-// DPT9 subtypes
-exports.subtypes = {
- // 9.001 temperature (oC)
- "001": {
- name: "DPT_SceneControl", desc: "scene control"
- }
-}
-
-
-
-/*
-02/April/2020 Supergiovane
-USE:
-Input must be an object: {save_recall, scenenumber}
-save_recall: 0 = recall scene, 1 = save scene
-scenenumber: the scene number, example 1
-Example: {save_recall=0, scenenumber=2}
-*/
-
diff --git a/src/dptlib/dpt18.ts b/src/dptlib/dpt18.ts
new file mode 100644
index 0000000..b9f7994
--- /dev/null
+++ b/src/dptlib/dpt18.ts
@@ -0,0 +1,86 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { hasProp } from '../utils'
+
+//
+// DPT18: 8-bit Scene Control
+//
+
+/*
+ class DPT18_Frame < DPTFrame
+ bit1 :exec_learn, {
+ :display_name : "Execute=0, Learn = 1"
+ }
+ bit1 :pad, {
+ :display_name : "Reserved bit"
+ }
+ bit6 :data, {
+ :display_name : "Scene number"
+ }
+ end
+*/
+
+const config: DatapointConfig = {
+ id: 'DPT18',
+ formatAPDU(value) {
+ if (!value) Log.get().warn('DPT18: cannot write null value')
+ else {
+ const apdu_data = Buffer.alloc(1)
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'save_recall') &&
+ hasProp(value, 'scenenumber')
+ ) {
+ const sSceneNumberbinary = (
+ (value.scenenumber - 1) >>>
+ 0
+ ).toString(2)
+ const sVal = `${
+ value.save_recall
+ }0${sSceneNumberbinary.padStart(6, '0')}`
+ apdu_data[0] = parseInt(sVal, 2) // 0b10111111;
+ } else {
+ Log.get().error(
+ 'DPT18: Must supply a value object of {save_recall, scenenumber}',
+ )
+ }
+ return apdu_data
+ }
+ },
+
+ fromBuffer(buf) {
+ if (buf.length !== 1) {
+ Log.get().error('DP18: Buffer should be 1 byte long')
+ } else {
+ const sBit = parseInt(buf.toString('hex').toUpperCase(), 16)
+ .toString(2)
+ .padStart(8, '0') // Get bit from hex
+ return {
+ save_recall: sBit.substring(0, 1),
+ scenenumber: parseInt(sBit.substring(2), 2) + 1,
+ }
+ }
+ },
+ // DPT18 basetype info
+ basetype: {
+ bitlength: 8,
+ valuetype: 'composite',
+ desc: '8-bit Scene Activate/Learn + number',
+ },
+
+ // DPT9 subtypes
+ subtypes: {
+ // 9.001 temperature (oC)
+ '001': {
+ name: 'DPT_SceneControl',
+ desc: 'scene control',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt19.js b/src/dptlib/dpt19.js
deleted file mode 100644
index 863ccda..0000000
--- a/src/dptlib/dpt19.js
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-// TODO: implement fromBuffer, formatAPDU
-
-//
-// DPT19: 8-byte Date and Time
-//
-
-exports.formatAPDU = (value) => {
- if (!(value instanceof Date))
- return log.error('DPT19: Must supply a Date object');
-
- // Sunday is 0 in Javascript, but 7 in KNX.
- const day = value.getDay() === 0 ? 7 : value.getDay();
- return Buffer.from([
- value.getFullYear() - 1900,
- value.getMonth() + 1,
- value.getDate(),
- (day << 5) + value.getHours(),
- value.getMinutes(),
- value.getSeconds(),
- 0,
- 0,
- ]);
-};
-
-exports.fromBuffer = (buf) => {
- if (buf.length !== 8) return log.warn('DPT19: Buffer should be 8 bytes long');
- return new Date(
- buf[0] + 1900,
- buf[1] - 1,
- buf[2],
- buf[3] & 0b00011111,
- buf[4],
- buf[5]
- );
-};
-
-exports.basetype = {
- bitlength: 64,
- valuetype: 'composite',
- desc: '8-byte Date+Time',
-};
-
-exports.subtypes = {
- // 19.001
- '001': {
- name: 'DPT_DateTime',
- desc: 'datetime',
- },
-};
diff --git a/src/dptlib/dpt19.ts b/src/dptlib/dpt19.ts
new file mode 100644
index 0000000..a0346da
--- /dev/null
+++ b/src/dptlib/dpt19.ts
@@ -0,0 +1,61 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT19: 8-byte Date and Time
+//
+
+const config: DatapointConfig = {
+ id: 'DPT19',
+ formatAPDU: (value) => {
+ if (typeof value !== 'object' || value.constructor.name !== 'Date')
+ return Log.get().error('DPT19: Must supply a Date object')
+
+ // Sunday is 0 in Javascript, but 7 in KNX.
+ const day = value.getDay() === 0 ? 7 : value.getDay()
+ return Buffer.from([
+ value.getFullYear() - 1900,
+ value.getMonth() + 1,
+ value.getDate(),
+ (day << 5) + value.getHours(),
+ value.getMinutes(),
+ value.getSeconds(),
+ 0,
+ 0,
+ ])
+ },
+
+ fromBuffer: (buf) => {
+ if (buf.length !== 8)
+ return Log.get().warn('DPT19: Buffer should be 8 bytes long')
+ return new Date(
+ buf[0] + 1900,
+ buf[1] - 1,
+ buf[2],
+ buf[3] & 0b00011111,
+ buf[4],
+ buf[5],
+ )
+ },
+
+ basetype: {
+ bitlength: 64,
+ valuetype: 'composite',
+ desc: '8-byte Date+Time',
+ },
+
+ subtypes: {
+ // 19.001
+ '001': {
+ name: 'DPT_DateTime',
+ desc: 'datetime',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt2.js b/src/dptlib/dpt2.js
deleted file mode 100644
index 25e5812..0000000
--- a/src/dptlib/dpt2.js
+++ /dev/null
@@ -1,136 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-// DPT2 frame description.
-// Always 8-bit aligned.
-exports.formatAPDU = (value) => {
- if (value == null) return log.error('DPT2: cannot write null value');
-
- if (
- typeof value === 'object' &&
- value.hasOwnProperty('priority') &&
- value.hasOwnProperty('data')
- )
- return Buffer.from([(value.priority << 1) + (value.data & 0b00000001)]);
-
- log.error('DPT2: Must supply an value {priority:, data:}');
- // FIXME: should this return zero buffer when error? Or nothing?
- return Buffer.from([0]);
-};
-
-exports.fromBuffer = (buf) => {
- if (buf.length !== 1) return log.error('Buffer should be 1 byte long');
-
- return {
- priority: (buf[0] & 0b00000010) >> 1,
- data: buf[0] & 0b00000001,
- };
-};
-
-// DPT basetype info hash
-exports.basetype = {
- bitlength: 2,
- valuetype: 'composite',
- desc: '1-bit value with priority',
-};
-
-// DPT subtypes info hash
-exports.subtypes = {
- // 2.001 switch control
- '001': {
- use: 'G',
- name: 'DPT_Switch_Control',
- desc: 'switch with priority',
- enc: { 0: 'Off', 1: 'On' },
- },
- // 2.002 boolean control
- '002': {
- use: 'G',
- name: 'DPT_Bool_Control',
- desc: 'boolean with priority',
- enc: { 0: 'false', 1: 'true' },
- },
- // 2.003 enable control
- '003': {
- use: 'FB',
- name: 'DPT_Emable_Control',
- desc: 'enable with priority',
- enc: { 0: 'Disabled', 1: 'Enabled' },
- },
-
- // 2.004 ramp control
- '004': {
- use: 'FB',
- name: 'DPT_Ramp_Control',
- desc: 'ramp with priority',
- enc: { 0: 'No ramp', 1: 'Ramp' },
- },
-
- // 2.005 alarm control
- '005': {
- use: 'FB',
- name: 'DPT_Alarm_Control',
- desc: 'alarm with priority',
- enc: { 0: 'No alarm', 1: 'Alarm' },
- },
-
- // 2.006 binary value control
- '006': {
- use: 'FB',
- name: 'DPT_BinaryValue_Control',
- desc: 'binary value with priority',
- enc: { 0: 'Off', 1: 'On' },
- },
-
- // 2.007 step control
- '007': {
- use: 'FB',
- name: 'DPT_Step_Control',
- desc: 'step with priority',
- enc: { 0: 'Off', 1: 'On' },
- },
-
- // 2.008 Direction1 control
- '008': {
- use: 'FB',
- name: 'DPT_Direction1_Control',
- desc: 'direction 1 with priority',
- enc: { 0: 'Off', 1: 'On' },
- },
-
- // 2.009 Direction2 control
- '009': {
- use: 'FB',
- name: 'DPT_Direction2_Control',
- desc: 'direction 2 with priority',
- enc: { 0: 'Off', 1: 'On' },
- },
-
- // 2.010 start control
- '001': {
- use: 'FB',
- name: 'DPT_Start_Control',
- desc: 'start with priority',
- enc: { 0: 'No control', 1: 'No control', 2: 'Off', 3: 'On' },
- },
-
- // 2.011 state control
- '001': {
- use: 'FB',
- name: 'DPT_Switch_Control',
- desc: 'switch',
- enc: { 0: 'No control', 1: 'No control', 2: 'Off', 3: 'On' },
- },
-
- // 2.012 invert control
- '001': {
- use: 'FB',
- name: 'DPT_Switch_Control',
- desc: 'switch',
- enc: { 0: 'No control', 1: 'No control', 2: 'Off', 3: 'On' },
- },
-};
diff --git a/src/dptlib/dpt2.ts b/src/dptlib/dpt2.ts
new file mode 100644
index 0000000..7c52087
--- /dev/null
+++ b/src/dptlib/dpt2.ts
@@ -0,0 +1,155 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { hasProp } from '../utils'
+import type { DatapointConfig } from '.'
+import Log from '../KnxLog'
+
+interface Dpt2Value {
+ priority: boolean | number
+ data: boolean | number
+}
+
+const config: DatapointConfig = {
+ id: 'DPT2',
+
+ // DPT2 frame description.
+ // Always 8-bit aligned.
+ formatAPDU: (value: Dpt2Value) => {
+ if (!value) return Log.get().error('DPT2: cannot write null value')
+
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'priority') &&
+ hasProp(value, 'data')
+ )
+ return Buffer.from([
+ ((value.priority as number) << 1) +
+ (value.data as number & 0b00000001),
+ ])
+
+ Log.get().error(
+ 'DPT2: Must supply an value {priority:, data:}',
+ )
+ // FIXME: should this return zero buffer when error? Or nothing?
+ return Buffer.from([0])
+ },
+
+ fromBuffer: (buf) => {
+ if (buf.length !== 1)
+ return Log.get().error('Buffer should be 1 byte long')
+
+ return {
+ priority: (buf[0] & 0b00000010) >> 1,
+ data: buf[0] & 0b00000001,
+ }
+ },
+
+ // DPT basetype info hash
+ basetype: {
+ bitlength: 2,
+ valuetype: 'composite',
+ desc: '1-bit value with priority',
+ },
+
+ // DPT subtypes info hash
+ subtypes: {
+ // 2.001 switch control
+ '001': {
+ use: 'G',
+ name: 'DPT_Switch_Control',
+ desc: 'switch with priority',
+ enc: { 0: 'Off', 1: 'On' },
+ },
+ // 2.002 boolean control
+ '002': {
+ use: 'G',
+ name: 'DPT_Bool_Control',
+ desc: 'boolean with priority',
+ enc: { 0: 'false', 1: 'true' },
+ },
+ // 2.003 enable control
+ '003': {
+ use: 'FB',
+ name: 'DPT_Emable_Control',
+ desc: 'enable with priority',
+ enc: { 0: 'Disabled', 1: 'Enabled' },
+ },
+
+ // 2.004 ramp control
+ '004': {
+ use: 'FB',
+ name: 'DPT_Ramp_Control',
+ desc: 'ramp with priority',
+ enc: { 0: 'No ramp', 1: 'Ramp' },
+ },
+
+ // 2.005 alarm control
+ '005': {
+ use: 'FB',
+ name: 'DPT_Alarm_Control',
+ desc: 'alarm with priority',
+ enc: { 0: 'No alarm', 1: 'Alarm' },
+ },
+
+ // 2.006 binary value control
+ '006': {
+ use: 'FB',
+ name: 'DPT_BinaryValue_Control',
+ desc: 'binary value with priority',
+ enc: { 0: 'Off', 1: 'On' },
+ },
+
+ // 2.007 step control
+ '007': {
+ use: 'FB',
+ name: 'DPT_Step_Control',
+ desc: 'step with priority',
+ enc: { 0: 'Off', 1: 'On' },
+ },
+
+ // 2.008 Direction1 control
+ '008': {
+ use: 'FB',
+ name: 'DPT_Direction1_Control',
+ desc: 'direction 1 with priority',
+ enc: { 0: 'Off', 1: 'On' },
+ },
+
+ // 2.009 Direction2 control
+ '009': {
+ use: 'FB',
+ name: 'DPT_Direction2_Control',
+ desc: 'direction 2 with priority',
+ enc: { 0: 'Off', 1: 'On' },
+ },
+
+ // 2.010 start control
+ '010': {
+ use: 'FB',
+ name: 'DPT_Start_Control',
+ desc: 'start with priority',
+ enc: { 0: 'No control', 1: 'No control', 2: 'Off', 3: 'On' },
+ },
+
+ // 2.011 state control
+ '011': {
+ use: 'FB',
+ name: 'DPT_Switch_Control',
+ desc: 'switch',
+ enc: { 0: 'No control', 1: 'No control', 2: 'Off', 3: 'On' },
+ },
+
+ // 2.012 invert control
+ '012': {
+ use: 'FB',
+ name: 'DPT_Switch_Control',
+ desc: 'switch',
+ enc: { 0: 'No control', 1: 'No control', 2: 'Off', 3: 'On' },
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt20.js b/src/dptlib/dpt20.js
deleted file mode 100644
index cb239ca..0000000
--- a/src/dptlib/dpt20.js
+++ /dev/null
@@ -1,40 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-//
-// DPT20: 1-byte HVAC
-//
-// FIXME: help needed
-exports.formatAPDU = (value) => {
- log.debug('./knx/src/dpt20.js : input value = ' + value);
- return Buffer.from([value]);
-};
-
-exports.fromBuffer = (buf) => {
- if (buf.length !== 1) throw 'Buffer should be 1 bytes long';
- const ret = buf.readUInt8(0);
- log.debug(' dpt20.js fromBuffer : ' + ret);
- return ret;
-};
-
-exports.basetype = {
- bitlength: 8,
- range: [,],
- valuetype: 'basic',
- desc: '1-byte',
-};
-
-exports.subtypes = {
- // 20.102 HVAC mode
- 102: {
- name: 'HVAC_Mode',
- desc: '',
- unit: '',
- scalar_range: [,],
- range: [,],
- },
-};
diff --git a/src/dptlib/dpt20.ts b/src/dptlib/dpt20.ts
new file mode 100644
index 0000000..1c964ce
--- /dev/null
+++ b/src/dptlib/dpt20.ts
@@ -0,0 +1,56 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT20: 1-byte HVAC
+//
+// FIXME: help needed
+
+const config: DatapointConfig = {
+ id: 'DPT20',
+ formatAPDU: (value) => {
+ const apdu_data = Buffer.alloc(1)
+ apdu_data[0] = value
+ Log.get().debug(
+ `./knx/src/dpt20.js : input value = ${value} apdu_data = ${apdu_data}`,
+ )
+ return apdu_data
+ },
+
+ fromBuffer: (buf) => {
+ if (buf.length !== 1) {
+ Log.get().warn(
+ 'DPT20: Buffer should be 1 byte long, got',
+ buf.length,
+ )
+ return null
+ }
+ const ret = buf.readUInt8(0)
+ return ret
+ },
+
+ basetype: {
+ bitlength: 8,
+ range: [undefined, undefined],
+ valuetype: 'basic',
+ desc: '1-byte',
+ },
+
+ subtypes: {
+ // 20.102 HVAC mode
+ 102: {
+ name: 'HVAC_Mode',
+ desc: '',
+ unit: '',
+ scalar_range: [undefined, undefined], // TODO: verify
+ range: [undefined, undefined], // TODO: verify
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt21.js b/src/dptlib/dpt21.js
deleted file mode 100644
index 92093ec..0000000
--- a/src/dptlib/dpt21.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const log = require('log-driver').logger;
-
-//
-// DPT21: 1-byte status
-//
-// 001
-// - OutofService b0
-// - Overridden b1
-// - Inalarm b2
-// - AlarmUnAck b3
-// - reseverd b4-7
-
-// FIXME: help needed
-exports.formatAPDU = function(value) {
- if (value == null) return log.error('DPT21: cannot write null value');
- log.debug('./knx/src/dpt21.js : input value = ' + value);
-
- //var apdu_data = new Buffer(1);
- //apdu_data[0] = value;
- if ( typeof value === 'object' )
- return Buffer.from([(value.outofservice) +
- (value.fault << 1) +
- (value.overridden << 2) +
- (value.inalarm << 3) +
- (value.alarmeunack << 4) ]);
-
- log.error('DPT21: Must supply a value which is an object');
- //return apdu_data;
- return Buffer.from([0]);
-}
-
-exports.fromBuffer = function(buf) {
- if (buf.length != 1) return log.error ("Buffer should be 1 bytes long");
- //if (buf.length != 1) throw "Buffer should be 1 bytes long";
- log.debug(' dpt21.js fromBuffer : ' + buf);
-
- //var ret = buf.readUInt8(0);
-
- return {
- outofservice: (buf[0] & 0b00000001),
- fault: (buf[0] & 0b00000010) >> 1,
- overridden: (buf[0] & 0b00000100) >> 2,
- inalarm: (buf[0] & 0b00001000) >> 3,
- alarmunack: (buf[0] & 0b00010000) >> 4 };
- //return ret;
-}
-
-
-exports.basetype = {
- "bitlength" : 8,
- "range" : [ , ],
- "valuetype" : "composite",
- "desc" : "1-byte"
-}
-
-exports.subtypes = {
- // 21.001 status - 5 bits
- "001" : {
- "name" : "DPT_StatusGen",
- "desc" : "General Status",
- "unit" : "",
- "scalar_range" : [ , ],
- "range" : [ , ]
- },
- // 21.002 control - 3 bits
- "002" : {
- "name" : "DPT_Device_Control",
- "desc" : "Device Control",
- "unit" : "",
- "scalar_range" : [ , ],
- "range" : [ , ]
- }
-}
diff --git a/src/dptlib/dpt21.ts b/src/dptlib/dpt21.ts
new file mode 100644
index 0000000..68d56eb
--- /dev/null
+++ b/src/dptlib/dpt21.ts
@@ -0,0 +1,84 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT21: 1-byte status
+//
+// 001
+// - OutofService b0
+// - Overridden b1
+// - Inalarm b2
+// - AlarmUnAck b3
+// - reseverd b4-7
+
+const config: DatapointConfig = {
+ id: 'DPT21',
+ formatAPDU(value) {
+ if (value == null)
+ return Log.get().error('DPT21: cannot write null value')
+ Log.get().debug(`./knx/src/dpt21.js : input value = ${value}`)
+
+ // var apdu_data = Buffer.alloc(1);
+ // apdu_data[0] = value;
+ if (typeof value === 'object')
+ return Buffer.from([
+ value.outofservice +
+ (value.fault << 1) +
+ (value.overridden << 2) +
+ (value.inalarm << 3) +
+ (value.alarmeunack << 4),
+ ])
+
+ Log.get().error('DPT21: Must supply a value which is an object')
+ // return apdu_data;
+ return Buffer.from([0])
+ },
+
+ fromBuffer(buf) {
+ if (buf.length !== 1)
+ return Log.get().error('Buffer should be 1 bytes long')
+ // if (buf.length != 1) throw "Buffer should be 1 bytes long";
+ Log.get().debug(` dpt21.js fromBuffer : ${buf}`)
+
+ // var ret = buf.readUInt8(0);
+
+ return {
+ outofservice: buf[0] & 0b00000001,
+ fault: (buf[0] & 0b00000010) >> 1,
+ overridden: (buf[0] & 0b00000100) >> 2,
+ inalarm: (buf[0] & 0b00001000) >> 3,
+ alarmunack: (buf[0] & 0b00010000) >> 4,
+ }
+ // return ret;
+ },
+ basetype: {
+ bitlength: 8,
+ range: [undefined, undefined],
+ valuetype: 'composite',
+ desc: '1-byte',
+ },
+ subtypes: {
+ // 21.001 status - 5 bits
+ '001': {
+ name: 'DPT_StatusGen',
+ desc: 'General Status',
+ unit: '',
+ scalar_range: [undefined, undefined],
+ range: [undefined, undefined],
+ },
+ // 21.002 control - 3 bits
+ '002': {
+ name: 'DPT_Device_Control',
+ desc: 'Device Control',
+ unit: '',
+ scalar_range: [undefined, undefined],
+ range: [undefined, undefined],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt213.ts b/src/dptlib/dpt213.ts
new file mode 100644
index 0000000..440f025
--- /dev/null
+++ b/src/dptlib/dpt213.ts
@@ -0,0 +1,145 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { frexp, hasProp, ldexp } from '../utils'
+
+//
+// DPT213: Data Type 4x 16-Signed Value
+//
+
+function getHex(_value: number) {
+ try {
+ const arr = frexp(_value)
+ const mantissa = arr[0]
+ const exponent = arr[1]
+ // find the minimum exponent that will upsize the normalized mantissa (0,5 to 1 range)
+ // in order to fit in 11 bits ([-2048, 2047])
+ let max_mantissa = 0
+ let e: number
+ for (e = exponent; e >= -15; e--) {
+ max_mantissa = ldexp(100 * mantissa, e)
+ if (max_mantissa > -2048 && max_mantissa < 2047) break
+ }
+ const sign = mantissa < 0 ? 1 : 0
+ const mant = mantissa < 0 ? ~(max_mantissa ^ 2047) : max_mantissa
+ const exp = exponent - e
+ return [(sign << 7) + (exp << 3) + (mant >> 8), mant % 256]
+ } catch (error) {
+ // noop
+ }
+}
+
+function getFloat(_value0: number, _value1: number) {
+ const sign = _value0 >> 7
+ const exponent = (_value0 & 0b01111000) >> 3
+ let mantissa = 256 * (_value0 & 0b00000111) + _value1
+ mantissa = sign === 1 ? ~(mantissa ^ 2047) : mantissa
+ return parseFloat(ldexp(0.01 * mantissa, exponent).toPrecision(15))
+}
+
+// 07/01/2021 Supergiovane
+// Send to BUS
+const config: DatapointConfig = {
+ id: 'DPT213',
+ formatAPDU(value) {
+ const apdu_data = Buffer.alloc(8) // 4 x 2 bytes
+
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'Comfort') &&
+ value.Comfort >= -272 &&
+ value.Comfort <= 655.34 &&
+ hasProp(value, 'Standby') &&
+ value.Standby >= -272 &&
+ value.Standby <= 655.34 &&
+ hasProp(value, 'Economy') &&
+ value.Economy >= -272 &&
+ value.Economy <= 655.34 &&
+ hasProp(value, 'BuildingProtection') &&
+ value.BuildingProtection >= -272 &&
+ value.BuildingProtection <= 655.34
+ ) {
+ // Comfort
+ const ArrComfort = getHex(value.Comfort)
+ apdu_data[0] = ArrComfort[0]
+ apdu_data[1] = ArrComfort[1]
+
+ // Standby
+ const ArrStandby = getHex(value.Standby)
+ apdu_data[2] = ArrStandby[0]
+ apdu_data[3] = ArrStandby[1]
+
+ // Economy
+ const ArrEconomy = getHex(value.Economy)
+ apdu_data[4] = ArrEconomy[0]
+ apdu_data[5] = ArrEconomy[1]
+
+ // BuildingProtection
+ const ArrBuildingProtection = getHex(value.BuildingProtection)
+ apdu_data[6] = ArrBuildingProtection[0]
+ apdu_data[7] = ArrBuildingProtection[1]
+ // console.log(apdu_data);
+ return apdu_data
+ }
+ Log.get().error(
+ 'DPT213: Must supply a payload like, for example: {Comfort:21, Standby:20, Economy:14, BuildingProtection:8}',
+ )
+ },
+
+ // RX from BUS
+ fromBuffer(buf) {
+ if (buf.length !== 8) {
+ Log.get().warn(
+ 'DPT213.fromBuffer: buf should be 4x2 bytes long (got %d bytes)',
+ buf.length,
+ )
+ return null
+ }
+ // Preparo per l'avvento di Gozer il gozeriano.
+ const nComfort = getFloat(buf[0], buf[1])
+ const nStandby = getFloat(buf[2], buf[3])
+ const nEconomy = getFloat(buf[4], buf[5])
+ const nbProt = getFloat(buf[6], buf[7])
+ return {
+ Comfort: nComfort,
+ Standby: nStandby,
+ Economy: nEconomy,
+ BuildingProtection: nbProt,
+ }
+ },
+
+ // DPT213 basetype info
+ basetype: {
+ bitlength: 4 * 16,
+ valuetype: 'basic',
+ desc: '4x 16-Bit Signed Value',
+ },
+
+ // DPT213 subtypes
+ subtypes: {
+ 100: {
+ desc: 'DPT_TempRoomSetpSet[4]',
+ name: 'Room temperature setpoint (Comfort, Standby, Economy, Building protection)',
+ unit: '°C',
+ range: [-272, 655.34],
+ },
+ 101: {
+ desc: 'DPT_TempDHWSetpSet[4]',
+ name: 'Room temperature setpoint DHW (LegioProtect, Normal, Reduced, Off/FrostProtect)',
+ unit: '°C',
+ range: [-272, 655.34],
+ },
+ 102: {
+ desc: 'DPT_TempRoomSetpSetShift[4]',
+ name: 'Room temperature setpoint shift (Comfort, Standby, Economy, Building protection)',
+ unit: '°C',
+ range: [-272, 655.34],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt22.ts b/src/dptlib/dpt22.ts
new file mode 100644
index 0000000..30f9676
--- /dev/null
+++ b/src/dptlib/dpt22.ts
@@ -0,0 +1,171 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { hasProp } from '../utils'
+
+//
+// DPT22: 2-byte RHCC status
+//
+
+function reverseString(str: string) {
+ let newString = ''
+ for (let i = str.length - 1; i >= 0; i--) {
+ newString += str[i]
+ }
+ return newString
+}
+
+interface DPT22Value {
+ Fault: boolean
+ StatusEcoH: boolean
+ TempFlowLimit: boolean
+ TempReturnLimit: boolean
+ StatusMorningBoostH: boolean
+ StatusStartOptim: boolean
+ StatusStopOptim: boolean
+ HeatingDisabled: boolean
+ HeatCoolMode: boolean
+ StatusEcoC: boolean
+ StatusPreCool: boolean
+ CoolingDisabled: boolean
+ DewPointStatus: boolean
+ FrostAlarm: boolean
+ OverheatAlarm: boolean
+ reserved: boolean
+}
+
+const config: DatapointConfig = {
+ id: 'DPT22',
+ formatAPDU: (value: DPT22Value) => {
+ // Send to BUS
+ const apdu_data = Buffer.alloc(2)
+ if (!value) {
+ Log.get().error('DPT232: cannot write null value')
+ } else {
+ if (typeof value === 'object') {
+ if (!hasProp(value, 'Fault')) value.Fault = false
+ if (!hasProp(value, 'StatusEcoH')) value.StatusEcoH = false
+ if (!hasProp(value, 'TempFlowLimit'))
+ value.TempFlowLimit = false
+ if (!hasProp(value, 'TempReturnLimit'))
+ value.TempReturnLimit = false
+ if (!hasProp(value, 'StatusMorningBoostH'))
+ value.StatusMorningBoostH = false
+ if (!hasProp(value, 'StatusStartOptim'))
+ value.StatusStartOptim = false
+ if (!hasProp(value, 'StatusStopOptim'))
+ value.StatusStopOptim = false
+ if (!hasProp(value, 'HeatingDisabled'))
+ value.HeatingDisabled = false
+ if (!hasProp(value, 'HeatCoolMode')) value.HeatCoolMode = false
+ if (!hasProp(value, 'StatusEcoC')) value.StatusEcoC = false
+ if (!hasProp(value, 'StatusPreCool'))
+ value.StatusPreCool = false
+ if (!hasProp(value, 'CoolingDisabled'))
+ value.CoolingDisabled = false
+ if (!hasProp(value, 'DewPointStatus'))
+ value.DewPointStatus = false
+ if (!hasProp(value, 'FrostAlarm')) value.FrostAlarm = false
+ if (!hasProp(value, 'OverheatAlarm'))
+ value.OverheatAlarm = false
+ if (!hasProp(value, 'reserved')) value.reserved = true
+ } else {
+ Log.get().error(
+ 'DPT22: Must supply a correct payload. See wiki.',
+ )
+ }
+ let firstHex = ''
+ let secondHex = ''
+ firstHex = firstHex.concat(
+ ...[
+ value.Fault,
+ value.StatusEcoH,
+ value.TempFlowLimit,
+ value.TempReturnLimit,
+ value.StatusMorningBoostH,
+ value.StatusStartOptim,
+ value.StatusStopOptim,
+ value.HeatingDisabled,
+ ].map((v) => {
+ return Number(v).toString()
+ }),
+ )
+ secondHex = secondHex.concat(
+ ...[
+ value.HeatCoolMode,
+ value.StatusEcoC,
+ value.StatusPreCool,
+ value.CoolingDisabled,
+ value.DewPointStatus,
+ value.FrostAlarm,
+ value.OverheatAlarm,
+ value.reserved,
+ ].map((v) => {
+ return Number(v).toString()
+ }),
+ )
+ apdu_data[0] = parseInt(reverseString(secondHex), 2)
+ apdu_data[1] = parseInt(reverseString(firstHex), 2)
+ return apdu_data
+ }
+ },
+ fromBuffer: (buf) => {
+ // RX from BUS
+ if (buf.length !== 2) {
+ Log.get().warn(
+ 'DPT22: Buffer should be 2 bytes long, got',
+ buf.length,
+ )
+ return null
+ }
+ const byte1 = reverseString(buf[1].toString(2).padStart(8, '0')).split(
+ '',
+ )
+ const byte2 = reverseString(buf[0].toString(2).padStart(8, '0')).split(
+ '',
+ )
+ const value: DPT22Value = {
+ // byte1
+ Fault: byte1[0] === '1',
+ StatusEcoH: byte1[1] === '1',
+ TempFlowLimit: byte1[2] === '1',
+ TempReturnLimit: byte1[3] === '1',
+ StatusMorningBoostH: byte1[4] === '1',
+ StatusStartOptim: byte1[5] === '1',
+ StatusStopOptim: byte1[6] === '1',
+ HeatingDisabled: byte1[7] === '1',
+ // byte2
+ HeatCoolMode: byte2[0] === '1',
+ StatusEcoC: byte2[1] === '1',
+ StatusPreCool: byte2[2] === '1',
+ CoolingDisabled: byte2[3] === '1',
+ DewPointStatus: byte2[4] === '1',
+ FrostAlarm: byte2[5] === '1',
+ OverheatAlarm: byte2[6] === '1',
+ reserved: byte2[7] === '1',
+ }
+
+ return value
+ },
+ basetype: {
+ bitlength: 16,
+ range: [undefined, undefined],
+ valuetype: 'basic',
+ desc: '2-byte',
+ },
+ subtypes: {
+ // 22.101 DPT_StatusRHCC
+ 101: {
+ name: 'RHCC Status',
+ desc: 'RHCC Status',
+ unit: '',
+ scalar_range: [undefined, undefined],
+ range: [undefined, undefined],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt232.js b/src/dptlib/dpt232.js
deleted file mode 100644
index cb91c23..0000000
--- a/src/dptlib/dpt232.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2019 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-//
-// DPT232: 3-byte RGB color array
-// MSB: Red, Green, LSB: Blue
-//
-exports.formatAPDU = (value) => {
- if (value == null) return log.error('DPT232: cannot write null value');
-
- if (typeof value === 'object') {
- const { red, green, blue } = value;
- if (
- red >= 0 &&
- red <= 255 &&
- green >= 0 &&
- green <= 255 &&
- blue >= 0 &&
- blue <= 255
- )
- return Buffer.from([red, green, blue]);
- }
- log.error(
- 'DPT232: Must supply an value {red:0..255, green:0.255, blue:0.255}'
- );
-};
-
-exports.fromBuffer = (buf) => {
- const [red, green, blue] = buf;
- return { red, green, blue };
-};
-
-exports.basetype = {
- bitlength: 3 * 8,
- valuetype: 'basic',
- desc: 'RGB array',
-};
-
-exports.subtypes = {
- 600: {
- name: 'RGB',
- desc: 'RGB color triplet',
- unit: '',
- scalar_range: [,],
- range: [,],
- },
-};
diff --git a/src/dptlib/dpt232.ts b/src/dptlib/dpt232.ts
new file mode 100644
index 0000000..1197f0e
--- /dev/null
+++ b/src/dptlib/dpt232.ts
@@ -0,0 +1,57 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2019 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT232: 3-byte RGB color array
+// MSB: Red, Green, LSB: Blue
+//
+const config: DatapointConfig = {
+ id: 'DPT232',
+ formatAPDU: (value) => {
+ if (value == null)
+ return Log.get().error('DPT232: cannot write null value')
+
+ if (typeof value === 'object') {
+ const { red, green, blue } = value
+ if (
+ red >= 0 &&
+ red <= 255 &&
+ green >= 0 &&
+ green <= 255 &&
+ blue >= 0 &&
+ blue <= 255
+ )
+ return Buffer.from([red, green, blue])
+ }
+ Log.get().error(
+ 'DPT232: Must supply an value {red:0..255, green:0.255, blue:0.255}',
+ )
+ },
+
+ fromBuffer: (buf) => {
+ const [red, green, blue] = buf
+ return { red, green, blue }
+ },
+ basetype: {
+ bitlength: 3 * 8,
+ valuetype: 'basic',
+ desc: 'RGB array',
+ },
+
+ subtypes: {
+ 600: {
+ name: 'RGB',
+ desc: 'RGB color triplet',
+ unit: '',
+ scalar_range: [undefined, undefined],
+ range: [undefined, undefined],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt235.ts b/src/dptlib/dpt235.ts
new file mode 100644
index 0000000..79aa3c1
--- /dev/null
+++ b/src/dptlib/dpt235.ts
@@ -0,0 +1,96 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { hasProp, hex2bin } from '../utils'
+
+//
+// DPT235: DPT_Tariff_ActiveEnergy
+//
+
+// 08/09/2020 Supergiovane
+// Send to BUS
+const config: DatapointConfig = {
+ id: 'DPT235',
+ formatAPDU(value) {
+ try {
+ const apdu_data = Buffer.alloc(6) // 3 x 2 bytes
+
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'activeElectricalEnergy') &&
+ hasProp(value, 'tariff') &&
+ hasProp(value, 'validityTariff') &&
+ hasProp(value, 'validityEnergy')
+ ) {
+ // activeElectricalEnergy
+ const nbuff = Buffer.alloc(4)
+ nbuff.writeInt32BE(value.activeElectricalEnergy)
+ apdu_data[0] = nbuff[0]
+ apdu_data[1] = nbuff[1]
+ apdu_data[2] = nbuff[2]
+ apdu_data[3] = nbuff[3]
+
+ // tariff
+ const tariff = parseInt(value.tariff)
+ apdu_data[4] = tariff
+
+ // Validity
+ const validity = parseInt(
+ `000000${
+ value.validityTariff ? '1' : '0'
+ }${value.validityEnergy ? '1' : '0'}`,
+ 2,
+ )
+ apdu_data[5] = validity
+ return apdu_data
+ }
+ Log.get().error(
+ 'DPT235: Must supply a payload like, for example: {activeElectricalEnergy:1540, tariff:20, validityTariff:true, validityEnergy:true}',
+ )
+ } catch (error) {
+ Log.get().error(`DPT235: exports.formatAPDU error ${error.message}`)
+ }
+ },
+
+ // RX from BUS
+ fromBuffer(buf) {
+ try {
+ // Preparo per l'avvento di Gozer il gozeriano.
+ const activeElectricalEnergy = buf.subarray(0, 4).readInt32BE() // First 4x8 bits signed integer
+ const tariff = parseInt(buf.subarray(4, 5)[0] as any) // next 8 bit unsigned value
+ const validity = hex2bin(buf.subarray(5, 6)[0].toString(16)) // Next 8 bit, only the latest 2 bits are used.
+ const validityTariff = validity.substring(6, 7) === '1'
+ const validityEnergy = validity.substring(7, 8) === '1'
+ return {
+ activeElectricalEnergy,
+ tariff,
+ validityTariff,
+ validityEnergy,
+ }
+ } catch (error) {
+ Log.get().error(`DPT235: exports.fromBuffer error ${error.message}`)
+ }
+ },
+
+ // DPT basetype info
+ basetype: {
+ bitlength: 48,
+ valuetype: 'basic',
+ desc: '6 octect Tariff_ActiveEnergy',
+ },
+
+ // DPT subtypes
+ subtypes: {
+ '001': {
+ desc: 'DPT_Tariff_ActiveEnergy',
+ name: 'Tariff of active Energy (Energy+Tariff+Validity)',
+ unit: 'Tariff',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt237.js b/src/dptlib/dpt237.js
deleted file mode 100644
index e1163cb..0000000
--- a/src/dptlib/dpt237.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const log = require('log-driver').logger;
-
-//
-// DPT237: 2-byte unsigned value
-//
-exports.formatAPDU = function(value) {
- if (value == null) return log.error('DPT237: cannot write null value');
-
- log.trace('dpt278.js : input value = ' + value);
-
- var apdu_data = new Buffer(2);
-
- //console.log("Buffer lenght: ", apdu_data.length);
- if ( typeof value === 'object' &&
- value.hasOwnProperty('addresstype') &&
- value.hasOwnProperty('address') &&
- value.hasOwnProperty('readresponse') &&
- value.hasOwnProperty('lampfailure') &&
- value.hasOwnProperty('ballastfailure') &&
- value.hasOwnProperty('convertorerror')
- ) {
- // these are reversed as [0] is high and [1] low
- apdu_data[1] = [(value.address & 0b00011111) +
- (value.addresstype << 5) +
- (value.readresponse << 6) +
- (value.lampfailure << 7)];
- apdu_data[0] = [(value.ballastfailure & 0b00000001) +
- (value.convertorerror << 1) ];
-
- return apdu_data;
- }
-
- log.error('DPT237: Must supply an value {address:[0,63] or [0,15], address type:{0,1}, ...}');
-
- return apdu_data;
-}
-
-exports.fromBuffer = function(buf) {
- if (buf.length !== 2) return log.error('Buffer should be 2 byte long');
-
- return { address: (buf[1] & 0b00011111),
- addresstype: (buf[1] & 0b00100000) >> 5,
- readresponse: (buf[1] & 0b01000000) >> 6,
- lampfailure: (buf[1] & 0b10000000) >> 7,
- ballastfailure: (buf[0] & 0b00000001),
- convertorerror: (buf[0] & 0b00000010) >> 1 };
-}
-
-
-exports.basetype = {
- "bitlength" : 16,
- "range" : [ , ],
- "valuetype" : "composite",
- "desc" : "2-byte"
-}
-
-exports.subtypes = {
- // 237.600 HVAC mode
- "600" : {
- "name" : "HVAC_Mode",
- "desc" : "",
- "unit" : "",
- "scalar_range" : [ , ],
- "range" : [ , ]
- },
-
-}
diff --git a/src/dptlib/dpt237.ts b/src/dptlib/dpt237.ts
new file mode 100644
index 0000000..d1c1720
--- /dev/null
+++ b/src/dptlib/dpt237.ts
@@ -0,0 +1,86 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { hasProp } from '../utils'
+
+//
+// DPT237: 2-byte unsigned value
+//
+const config: DatapointConfig = {
+ id: 'DPT237',
+ formatAPDU(value) {
+ if (value == null)
+ return Log.get().error('DPT237: cannot write null value')
+
+ Log.get().trace(`dpt278.js : input value = ${value}`)
+
+ const apdu_data = Buffer.alloc(2)
+
+ // console.log("Buffer lenght: ", apdu_data.length);
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'addresstype') &&
+ hasProp(value, 'address') &&
+ hasProp(value, 'readresponse') &&
+ hasProp(value, 'lampfailure') &&
+ hasProp(value, 'ballastfailure') &&
+ hasProp(value, 'convertorerror')
+ ) {
+ // these are reversed as [0] is high and [1] low
+ apdu_data[1] =
+ (value.address & 0b00011111) +
+ (value.addresstype << 5) +
+ (value.readresponse << 6) +
+ (value.lampfailure << 7)
+ apdu_data[0] =
+ (value.ballastfailure & 0b00000001) +
+ (value.convertorerror << 1)
+
+ return apdu_data
+ }
+
+ Log.get().error(
+ 'DPT237: Must supply an value {address:[0,63] or [0,15], address type:{0,1}, ...}',
+ )
+
+ return apdu_data
+ },
+
+ fromBuffer(buf) {
+ if (buf.length !== 2)
+ return Log.get().error('Buffer should be 2 byte long')
+
+ return {
+ address: buf[1] & 0b00011111,
+ addresstype: (buf[1] & 0b00100000) >> 5,
+ readresponse: (buf[1] & 0b01000000) >> 6,
+ lampfailure: (buf[1] & 0b10000000) >> 7,
+ ballastfailure: buf[0] & 0b00000001,
+ convertorerror: (buf[0] & 0b00000010) >> 1,
+ }
+ },
+
+ basetype: {
+ bitlength: 16,
+ range: [undefined, undefined],
+ valuetype: 'composite',
+ desc: '2-byte',
+ },
+
+ subtypes: {
+ // 237.600 HVAC mode
+ '600': {
+ name: 'HVAC_Mode',
+ desc: '',
+ unit: '',
+ scalar_range: [undefined, undefined],
+ range: [undefined, undefined],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt238.js b/src/dptlib/dpt238.js
deleted file mode 100644
index 7bf2c93..0000000
--- a/src/dptlib/dpt238.js
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-//
-// DPT238: 1-byte unsigned value
-//
-// DPT5 is the only (AFAIK) DPT with scalar datatypes (5.001 and 5.003)
-exports.formatAPDU = (value) => {
- const apdu_data = Buffer.from([value]);
- log.trace(
- 'dpt238.js : input value = ' + value + ' apdu_data = ' + apdu_data
- );
- return apdu_data;
-};
-
-exports.fromBuffer = (buf) => buf[0];
-
-exports.basetype = {
- bitlength: 8,
- range: [,],
- valuetype: 'basic',
- desc: '1-byte',
-};
-
-exports.subtypes = {
- // 20.102 HVAC mode
- 102: {
- name: 'HVAC_Mode',
- desc: '',
- unit: '',
- scalar_range: [,],
- range: [,],
- },
-
- // 5.003 angle (degrees 0=0, ff=360)
- '003': {
- name: 'DPT_Angle',
- desc: 'angle degrees',
- unit: '°',
- scalar_range: [0, 360],
- },
-
- // 5.004 percentage (0..255%)
- '004': {
- name: 'DPT_Percent_U8',
- desc: 'percent',
- unit: '%',
- },
-
- // 5.005 ratio (0..255)
- '005': {
- name: 'DPT_DecimalFactor',
- desc: 'ratio',
- unit: 'ratio',
- },
-
- // 5.006 tariff (0..255)
- '006': {
- name: 'DPT_Tariff',
- desc: 'tariff',
- unit: 'tariff',
- },
-
- // 5.010 counter pulses (0..255)
- '010': {
- name: 'DPT_Value_1_Ucount',
- desc: 'counter pulses',
- unit: 'pulses',
- },
-};
diff --git a/src/dptlib/dpt238.ts b/src/dptlib/dpt238.ts
new file mode 100644
index 0000000..d759a8d
--- /dev/null
+++ b/src/dptlib/dpt238.ts
@@ -0,0 +1,80 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT238: 1-byte unsigned value
+//
+// DPT5 is the only (AFAIK) DPT with scalar datatypes (5.001 and 5.003)
+const config: DatapointConfig = {
+ id: 'DPT238',
+ formatAPDU: (value) => {
+ const apdu_data = Buffer.from([value])
+ Log.get().trace(
+ `dpt238.js : input value = ${value} apdu_data = ${apdu_data}`,
+ )
+ return apdu_data
+ },
+
+ fromBuffer: (buf) => buf[0],
+
+ basetype: {
+ bitlength: 8,
+ range: [undefined, undefined],
+ valuetype: 'basic',
+ desc: '1-byte',
+ },
+
+ subtypes: {
+ // 20.102 HVAC mode
+ 102: {
+ name: 'HVAC_Mode',
+ desc: '',
+ unit: '',
+ scalar_range: [undefined, undefined],
+ range: [undefined, undefined],
+ },
+
+ // 5.003 angle (degrees 0=0, ff=360)
+ '003': {
+ name: 'DPT_Angle',
+ desc: 'angle degrees',
+ unit: '°',
+ scalar_range: [0, 360],
+ },
+
+ // 5.004 percentage (0..255%)
+ '004': {
+ name: 'DPT_Percent_U8',
+ desc: 'percent',
+ unit: '%',
+ },
+
+ // 5.005 ratio (0..255)
+ '005': {
+ name: 'DPT_DecimalFactor',
+ desc: 'ratio',
+ unit: 'ratio',
+ },
+
+ // 5.006 tariff (0..255)
+ '006': {
+ name: 'DPT_Tariff',
+ desc: 'tariff',
+ unit: 'tariff',
+ },
+
+ // 5.010 counter pulses (0..255)
+ '010': {
+ name: 'DPT_Value_1_Ucount',
+ desc: 'counter pulses',
+ unit: 'pulses',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt242.ts b/src/dptlib/dpt242.ts
new file mode 100644
index 0000000..7386bc0
--- /dev/null
+++ b/src/dptlib/dpt242.ts
@@ -0,0 +1,113 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { hasProp, hex2bin } from '../utils'
+
+interface DPT242Value {
+ x: number
+ y: number
+ brightness: number
+ isColorValid: boolean
+ isBrightnessValid: boolean
+}
+
+//
+// DPT242: 3-byte RGB xyY
+//
+const config: DatapointConfig = {
+ id: 'DPT242',
+ formatAPDU(value) {
+ if (!value) {
+ Log.get().error('DPT242: cannot write null value')
+ } else {
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'isColorValid') &&
+ hasProp(value, 'isBrightnessValid') &&
+ hasProp(value, 'x') &&
+ value.x >= 0 &&
+ value.x <= 65535 &&
+ hasProp(value, 'y') &&
+ value.y >= 0 &&
+ value.y <= 65535 &&
+ hasProp(value, 'brightness') &&
+ value.brightness >= 0 &&
+ value.brightness <= 100
+ ) {
+ // noop
+ } else {
+ Log.get().error(
+ 'DPT242: Must supply an value {x:0-65535, y:0-65535, brightness:0-100, isColorValid:true/false, isBrightnessValid:true/false}',
+ )
+ }
+
+ const bufferTotal = Buffer.alloc(6)
+ const bufX = Buffer.alloc(2)
+ const bufY = Buffer.alloc(2)
+ const bufBrightness = Buffer.alloc(2)
+ const isColorValid = value.isColorValid ? '1' : '0'
+ const isBrightnessValid = value.isBrightnessValid ? '1' : '0'
+ bufX.writeUInt16BE(value.x) // buf.writeUInt16LE(number);
+ bufY.writeUInt16BE(value.y)
+ bufBrightness.writeUInt16BE(value.brightness)
+ bufBrightness[0] = parseInt(
+ `000000${isColorValid}${isBrightnessValid}`,
+ 2,
+ ) // .toString(16) as any // these are Colour and Brighness validities
+ bufferTotal[0] = bufX[0]
+ bufferTotal[1] = bufX[1]
+ bufferTotal[2] = bufY[0]
+ bufferTotal[3] = bufY[1]
+ bufferTotal[4] = bufBrightness[1]
+ bufferTotal[5] = bufBrightness[0]
+
+ return bufferTotal
+ }
+ },
+
+ fromBuffer(buf) {
+ if (buf.length !== 6) {
+ Log.get().error(
+ 'DPT242: Buffer should be 6 bytes long, got',
+ buf.length,
+ )
+ return null
+ }
+ const bufTotale = buf.toString('hex')
+ // console.log("bufTotale STRINGA: " + bufTotale);
+ const x = bufTotale.substring(0, 4)
+ const y = bufTotale.substring(4, 8)
+ const brightness = bufTotale.substring(8, 10) // these are Colour and Brighness validities (2 bit) //00000011
+ const CB = bufTotale.substring(10, 12)
+ const isColorValid = hex2bin(CB).substring(6, 7) === '1'
+ const isBrightnessValid = hex2bin(CB).substring(7, 8) === '1'
+ const ret: DPT242Value = {
+ x: parseInt(x, 16),
+ y: parseInt(y, 16),
+ brightness: parseInt(brightness, 16),
+ isColorValid,
+ isBrightnessValid,
+ }
+ return ret
+ },
+ basetype: {
+ bitlength: 3 * 16,
+ valuetype: 'basic',
+ desc: 'RGB xyY',
+ },
+ subtypes: {
+ 600: {
+ desc: 'RGB xyY',
+ name: 'RGB color xyY',
+ unit: '',
+ scalar_range: [undefined, undefined],
+ range: [undefined, undefined],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt249.ts b/src/dptlib/dpt249.ts
new file mode 100644
index 0000000..f349d74
--- /dev/null
+++ b/src/dptlib/dpt249.ts
@@ -0,0 +1,110 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { hasProp, hex2bin } from '../utils'
+
+//
+// DPT249: 3-byte RGB xyY
+// The info about validity of Colour and Brighness are omitted.
+//
+const config: DatapointConfig = {
+ id: 'DPT249',
+ formatAPDU(value) {
+ if (!value) {
+ Log.get().error('DPT249: cannot write null value')
+ } else {
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'isTimePeriodValid') &&
+ hasProp(value, 'isAbsoluteColourTemperatureValid') &&
+ hasProp(value, 'isAbsoluteBrightnessValid') &&
+ hasProp(value, 'transitionTime') &&
+ hasProp(value, 'colourTemperature') &&
+ value.colourTemperature >= 0 &&
+ value.colourTemperature <= 65535 &&
+ hasProp(value, 'absoluteBrightness') &&
+ value.absoluteBrightness >= 0 &&
+ value.absoluteBrightness <= 100
+ ) {
+ // noop
+ } else {
+ Log.get().error(
+ 'DPT249: Must supply an value, for example {transitionTime:100, colourTemperature:1000, absoluteBrightness:80, isTimePeriodValid:true, isAbsoluteColourTemperatureValid:true, isAbsoluteBrightnessValid:true}',
+ )
+ }
+
+ const bufferTotal = Buffer.alloc(6)
+ const transitionTime = Buffer.alloc(2)
+ const colourTemperature = Buffer.alloc(2)
+ const absoluteBrightness = Buffer.alloc(2)
+ const isTimePeriodValid = value.isTimePeriodValid ? '1' : '0'
+ const isAbsoluteColourTemperatureValid =
+ value.isAbsoluteColourTemperatureValid ? '1' : '0'
+ const isAbsoluteBrightnessValid = value.isAbsoluteBrightnessValid
+ ? '1'
+ : '0'
+ transitionTime.writeUInt16BE(value.transitionTime) // buf.writeUInt16LE(number);
+ colourTemperature.writeUInt16BE(value.colourTemperature)
+ absoluteBrightness.writeUInt16BE(value.absoluteBrightness)
+ absoluteBrightness[0] = parseInt(
+ `00000${isTimePeriodValid}${isAbsoluteColourTemperatureValid}${isAbsoluteBrightnessValid}`,
+ 2,
+ ) // .toString(16) // these are Colour and Brighness validities
+ bufferTotal[0] = transitionTime[0]
+ bufferTotal[1] = transitionTime[1]
+ bufferTotal[2] = colourTemperature[0]
+ bufferTotal[3] = colourTemperature[1]
+ bufferTotal[4] = absoluteBrightness[1]
+ bufferTotal[5] = absoluteBrightness[0]
+ return bufferTotal
+ }
+ },
+ fromBuffer(buf) {
+ if (buf.length !== 6) {
+ Log.get().error(
+ 'DPT249: Buffer should be 6 bytes long, got',
+ buf.length,
+ )
+ return null
+ }
+ const bufTotale = buf.toString('hex')
+ const transitionTime = bufTotale.substring(0, 4)
+ const colourTemperature = bufTotale.substring(4, 8)
+ const absoluteBrightness = bufTotale.substring(8, 10) // This is 1 Byte of validities (3 bit) //00000111
+ const CB = bufTotale.substring(10, 12)
+ const isTimePeriodValid = hex2bin(CB).substring(5, 6) === '1'
+ const isAbsoluteColourTemperatureValid =
+ hex2bin(CB).substring(6, 7) === '1'
+ const isAbsoluteBrightnessValid = hex2bin(CB).substring(7, 8) === '1'
+ const ret = {
+ transitionTime: parseInt(transitionTime, 16),
+ colourTemperature: parseInt(colourTemperature, 16),
+ absoluteBrightness: parseInt(absoluteBrightness, 16),
+ isTimePeriodValid,
+ isAbsoluteColourTemperatureValid,
+ isAbsoluteBrightnessValid,
+ }
+ return ret
+ },
+ basetype: {
+ bitlength: 2 * 16 + 2 * 8,
+ valuetype: 'basic',
+ desc: 'PDT_GENERIC_06',
+ },
+
+ subtypes: {
+ 600: {
+ desc: 'DPT_Brightness_Colour_Temperature_Transition',
+ name: 'Brightness Colour Temperature Transition',
+ unit: '',
+ scalar_range: [undefined, undefined],
+ range: [undefined, undefined],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt251.ts b/src/dptlib/dpt251.ts
new file mode 100644
index 0000000..e5b32fd
--- /dev/null
+++ b/src/dptlib/dpt251.ts
@@ -0,0 +1,112 @@
+/**
+
+* (C) 2020 Supergiovane
+*/
+
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { hasProp } from '../utils'
+
+// Structure of DPT 251.600
+// Byte 0: R value
+// Byte 1: G value
+// Byte 2: B value
+// Byte 3: W value
+// Byte 4: 0x00 (reserved)
+// Byte 5:
+// Bit 0: W value valid?
+// Bit 1: B value valid?
+// Bit 2: G value valid?
+// Bit 3: R value valid?
+// Bit 4-7: 0
+// The bitfield which defines whether an value(R, G, B, W) is valid or not should be in the last byte of the array and not at the beginning.This can be verified by trying to send a DPT 251.600 telegram in the BUS monitor of the ETS5 application.
+
+const config: DatapointConfig = {
+ id: 'DPT251',
+ formatAPDU(value) {
+ if (!value) {
+ Log.get().error('DPT251: cannot write null value')
+ } else {
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'white') &&
+ value.white >= 0 &&
+ value.white <= 255 &&
+ hasProp(value, 'red') &&
+ value.red >= 0 &&
+ value.red <= 255 &&
+ hasProp(value, 'green') &&
+ value.green >= 0 &&
+ value.green <= 255 &&
+ hasProp(value, 'blue') &&
+ value.blue >= 0 &&
+ value.blue <= 255 &&
+ hasProp(value, 'mR') &&
+ hasProp(value, 'mG') &&
+ hasProp(value, 'mB') &&
+ hasProp(value, 'mW')
+ ) {
+ // noop
+ } else {
+ Log.get().error(
+ 'DPT251: Must supply a value payload: {red:0-255, green:0-255, blue:0-255, white:0-255, mR:0-1, mG:0-1, mB:0-1, mW:0-1}',
+ )
+ }
+ const bitVal = parseInt(
+ `0000${value.mR}${value.mG}${value.mB}${value.mW}`,
+ 2,
+ )
+
+ return Buffer.from([
+ Math.floor(value.red),
+ Math.floor(value.green),
+ Math.floor(value.blue),
+ Math.floor(value.white),
+ Math.floor(0),
+ Math.floor(bitVal),
+ ])
+ }
+ },
+ fromBuffer(buf) {
+ if (buf.length !== 6) {
+ Log.get().error(
+ 'DPT251: Buffer should be 6 bytes long, got',
+ buf.length,
+ )
+ return null
+ }
+ const valByte = buf[5].toString(2) // Get validity bits
+ const ret = {
+ red: buf[0],
+ green: buf[1],
+ blue: buf[2],
+ white: buf[3],
+ mR: parseInt(valByte[0]) || 0,
+ mG: parseInt(valByte[1]) || 0,
+ mB: parseInt(valByte[2]) || 0,
+ mW: parseInt(valByte[3]) || 0,
+ }
+ return ret
+ },
+ basetype: {
+ bitlength: 6 * 8,
+ valuetype: 'basic',
+ desc: 'RGBW array',
+ },
+ subtypes: {
+ 600: {
+ desc: 'RGBW [payload:{red:255, green:200, blue:30, white:50, mR:1, mG:1, mB:1, mW:1}]',
+ name: 'RGB color triplet + White + Validity',
+ unit: '',
+ scalar_range: [undefined, undefined],
+ range: [undefined, undefined],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt275.ts b/src/dptlib/dpt275.ts
new file mode 100644
index 0000000..c84fd7d
--- /dev/null
+++ b/src/dptlib/dpt275.ts
@@ -0,0 +1,81 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { hasProp } from '../utils'
+import dpt9 from './dpt9'
+
+//
+// 4x DPT9.* 2-byte floating point value
+//
+const config: DatapointConfig = {
+ id: 'DPT275',
+ formatAPDU(value) {
+ // Get the javascript object and create a telegram for the KNX bus.
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'comfort') &&
+ hasProp(value, 'standby') &&
+ hasProp(value, 'economy') &&
+ hasProp(value, 'buildingProtection')
+ ) {
+ const comfort = dpt9.formatAPDU(value.comfort) as Buffer
+ const standby = dpt9.formatAPDU(value.standby) as Buffer
+ const economy = dpt9.formatAPDU(value.economy) as Buffer
+ const buildingProtection = dpt9.formatAPDU(
+ value.buildingProtection,
+ ) as Buffer
+ return Buffer.concat([
+ comfort,
+ standby,
+ economy,
+ buildingProtection,
+ ])
+ }
+ Log.get().error(
+ 'DPT275.formatAPDU: Must supply all values, for example {comfort:22, standby:21.5, economy:21, buildingProtection:15}',
+ )
+ },
+ fromBuffer(buf) {
+ // Get the telegram from the KNX bus and create a javascript object.
+ if (buf.length !== 8) {
+ Log.get().warn(
+ 'DPT275.fromBuffer: buf should be 8 bytes long (got %d bytes)',
+ buf.length,
+ )
+ return null
+ }
+ const comfort = dpt9.fromBuffer(buf.slice(0, 2))
+ const standby = dpt9.fromBuffer(buf.slice(2, 4))
+ const economy = dpt9.fromBuffer(buf.slice(4, 6))
+ const buildingProtection = dpt9.fromBuffer(buf.slice(6, 8))
+ return {
+ comfort,
+ standby,
+ economy,
+ buildingProtection,
+ }
+ },
+
+ // DPT275 basetype info
+ basetype: {
+ bitlength: 64,
+ valuetype: 'basic',
+ desc: 'Quadruple setpoints (comfort,standby,economy,buildingProtection) (4 float with 16 Bit)',
+ },
+
+ // DPT9 subtypes
+ subtypes: {
+ 100: {
+ name: 'Quadruple setpoints (comfort,standby,economy,buildingProtection) (4 float with 16 Bit)',
+ desc: 'DPT_TempRoomSetpSetF16[4]',
+ unit: '°C',
+ range: [-273, 670760],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt28.ts b/src/dptlib/dpt28.ts
new file mode 100644
index 0000000..5446934
--- /dev/null
+++ b/src/dptlib/dpt28.ts
@@ -0,0 +1,66 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+//
+// DPT28: ASCII string (variable length) UTF-8
+//
+
+// Write to BUS
+const config: DatapointConfig = {
+ id: 'DPT28',
+ formatAPDU(value) {
+ if (typeof value !== 'string') {
+ Log.get().error(
+ 'Must supply a string value. Autoconversion to string',
+ )
+ try {
+ value = value.toString()
+ } catch (error) {
+ value = 'DPT Err'
+ }
+ }
+
+ try {
+ const buf = Buffer.alloc(14)
+ if (this.subtypeid === '001') buf.write(value, 'utf-8')
+ return buf
+ } catch (error) {
+ return error.message
+ }
+ },
+
+ // Read from BUS
+ fromBuffer(buf) {
+ // nog length check because this is variable
+ // if (buf.length != 14) {
+ // knxLog.get().get().error('DPT28: Buffer should be 14 byte long, got', buf.length)
+ // return null
+ // }
+ if (this.subtypeid === '001') return buf.toString('utf-8')
+ },
+
+ // DPT28 basetype info
+ basetype: {
+ bitlength: 14 * 8,
+ valuetype: 'basic',
+ desc: '14-character string',
+ },
+
+ // DPT28 subtypes
+ subtypes: {
+ // 28.001 UTF-8 string
+ '001': {
+ use: 'G',
+ desc: 'DPT_String_UTF-8',
+ name: 'UTF-8 string',
+ force_encoding: 'UTF-8',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt29.ts b/src/dptlib/dpt29.ts
new file mode 100644
index 0000000..d0de42f
--- /dev/null
+++ b/src/dptlib/dpt29.ts
@@ -0,0 +1,61 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+
+//
+// DPT29: 8-byte signed value
+//
+
+const config: DatapointConfig = {
+ id: 'DPT29',
+ formatAPDU(value) {
+ if (typeof value === 'string') value = BigInt(value)
+ const apdu_data = Buffer.allocUnsafe(8)
+ // Writing big integer value into buffer
+ // by using writeBigInt64BE() method
+ apdu_data.writeBigInt64BE(value, 0)
+ return apdu_data
+ },
+ fromBuffer(buf) {
+ return buf.readBigInt64BE(0)
+
+ // const bufInt = (buf.readUInt32BE(0) << 8) + buf.readUInt32BE(4)
+ // return bufInt.toString(16)
+ },
+ basetype: {
+ bitlength: 64,
+ signedness: 'signed',
+ valuetype: 'basic',
+ desc: '8-byte V64 signed value',
+ // range: [-9223372036854775808, 9223372036854775807],
+ },
+
+ // DPT29 subtypes
+ subtypes: {
+ '010': {
+ use: 'G',
+ desc: 'DPT_ActiveEnergy_64',
+ name: 'Active energy V64 (Wh)',
+ unit: 'Wh',
+ },
+
+ '011': {
+ use: 'G',
+ desc: 'DPT_ApparantEnergy_V64',
+ name: 'Apparant energy V64 (VAh)',
+ unit: 'VAh',
+ },
+
+ '012': {
+ use: 'G',
+ desc: 'DPT_ReactiveEnergy_V64',
+ name: 'Reactive energy V64 (VARh)',
+ unit: 'VARh',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt3.js b/src/dptlib/dpt3.js
deleted file mode 100644
index f934745..0000000
--- a/src/dptlib/dpt3.js
+++ /dev/null
@@ -1,78 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-//
-// DPT3.*: 4-bit dimming/blinds control
-//
-exports.formatAPDU = (value) => {
- if (value == null) return log.warn('DPT3: cannot write null value');
-
- if (
- typeof value == 'object' &&
- value.hasOwnProperty('decr_incr') &&
- value.hasOwnProperty('data')
- )
- return Buffer.from([(value.decr_incr << 3) + (value.data & 0b00000111)]);
-
- log.error('Must supply a value object of {decr_incr, data}');
- // FIXME: should this return zero buffer when error? Or nothing?
- return Buffer.from([0]);
-};
-
-exports.fromBuffer = (buf) => {
- if (buf.length != 1) return log.error('DPT3: Buffer should be 1 byte long');
-
- return {
- decr_incr: (buf[0] & 0b00001000) >> 3,
- data: buf[0] & 0b00000111,
- };
-};
-
-exports.basetype = {
- bitlength: 4,
- valuetype: 'composite',
- desc: '4-bit relative dimming control',
-};
-
-exports.subtypes = {
- // 3.007 dimming control
- '007': {
- name: 'DPT_Control_Dimming',
- desc: 'dimming control',
- },
-
- // 3.008 blind control
- '008': {
- name: 'DPT_Control_Blinds',
- desc: 'blinds control',
- },
-};
-
-/*
- 2.6.3.5 Behavior
-Status
-off dimming actuator switched off
-on dimming actuator switched on, constant brightness, at least
- minimal brightness dimming
-dimming actuator switched on, moving from actual value in direction of
- set value
-Events
- position = 0 off command
- position = 1 on command
- control = up dX command, dX more bright dimming
- control = down dX command, dX less bright dimming
- control = stop stop command
- value = 0 dimming value = off
- value = x% dimming value = x% (not zero)
- value_reached actual value reached set value
-
-The step size dX for up and down dimming may be 1/1, 1/2, 1/4, 1/8, 1/16, 1/32 and 1/64 of
-the full dimming range (0 - FFh).
-
-3.007 dimming control
-3.008 blind control
-*/
diff --git a/src/dptlib/dpt3.ts b/src/dptlib/dpt3.ts
new file mode 100644
index 0000000..5944e27
--- /dev/null
+++ b/src/dptlib/dpt3.ts
@@ -0,0 +1,88 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { hasProp } from '../utils'
+import type { DatapointConfig } from '.'
+
+import Log from '../KnxLog'
+
+interface Dpt3Value {
+ decr_incr: number
+ data: number
+}
+
+const config: DatapointConfig = {
+ id: 'DPT3',
+ formatAPDU: (value: Dpt3Value) => {
+ if (!value) return Log.get().warn('DPT3: cannot write null value')
+
+ if (
+ typeof value === 'object' &&
+ hasProp(value, 'decr_incr') &&
+ hasProp(value, 'data')
+ )
+ return Buffer.from([
+ (value.decr_incr << 3) + (value.data & 0b00000111),
+ ])
+
+ Log.get().error('Must supply a value object of {decr_incr, data}')
+ // FIXME: should this return zero buffer when error? Or nothing?
+ return Buffer.from([0])
+ },
+ fromBuffer: (buf: Buffer) => {
+ if (buf.length !== 1)
+ return Log.get().error('DPT3: Buffer should be 1 byte long')
+
+ return {
+ decr_incr: (buf[0] & 0b00001000) >> 3,
+ data: buf[0] & 0b00000111,
+ }
+ },
+ basetype: {
+ bitlength: 4,
+ valuetype: 'composite',
+ desc: '4-bit relative dimming control',
+ },
+ subtypes: {
+ // 3.007 dimming control
+ '007': {
+ name: 'DPT_Control_Dimming',
+ desc: 'dimming control',
+ },
+
+ // 3.008 blind control
+ '008': {
+ name: 'DPT_Control_Blinds',
+ desc: 'blinds control',
+ },
+ },
+}
+
+export default config
+
+/*
+ 2.6.3.5 Behavior
+Status
+off dimming actuator switched off
+on dimming actuator switched on, constant brightness, at least
+ minimal brightness dimming
+dimming actuator switched on, moving from actual value in direction of
+ set value
+Events
+ position = 0 off command
+ position = 1 on command
+ control = up dX command, dX more bright dimming
+ control = down dX command, dX less bright dimming
+ control = stop stop command
+ value = 0 dimming value = off
+ value = x% dimming value = x% (not zero)
+ value_reached actual value reached set value
+
+The step size dX for up and down dimming may be 1/1, 1/2, 1/4, 1/8, 1/16, 1/32 and 1/64 of
+the full dimming range (0 - FFh).
+
+3.007 dimming control
+3.008 blind control
+*/
diff --git a/src/dptlib/dpt4.js b/src/dptlib/dpt4.js
deleted file mode 100644
index 90b43eb..0000000
--- a/src/dptlib/dpt4.js
+++ /dev/null
@@ -1,49 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-//
-// DPT4: 8-bit character
-//
-exports.formatAPDU = (value) => {
- if (value == null) return log.warn('DPT4: cannot write null value');
-
- if (typeof value !== 'string')
- return log.warn('DPT4: Must supply a character or string');
-
- const apdu_data = value.charCodeAt(0);
- if (apdu_data > 255) return log.warn('DPT4: must supply an ASCII character');
-
- return Buffer.from([apdu_data]);
-};
-
-exports.fromBuffer = (buf) => {
- if (buf.length != 1) return log.warn('DPT4: Buffer should be 1 byte long');
-
- return String.fromCharCode(buf[0]);
-};
-
-exports.basetype = {
- bitlength: 8,
- valuetype: 'basic',
- desc: '8-bit character',
-};
-
-exports.subtypes = {
- // 4.001 character (ASCII)
- '001': {
- name: 'DPT_Char_ASCII',
- desc: 'ASCII character (0-127)',
- range: [0, 127],
- use: 'G',
- },
- // 4.002 character (ISO-8859-1)
- '002': {
- name: 'DPT_Char_8859_1',
- desc: 'ISO-8859-1 character (0..255)',
- use: 'G',
- },
-};
diff --git a/src/dptlib/dpt4.ts b/src/dptlib/dpt4.ts
new file mode 100644
index 0000000..066dd51
--- /dev/null
+++ b/src/dptlib/dpt4.ts
@@ -0,0 +1,55 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+import Log from '../KnxLog'
+
+//
+// DPT4: 8-bit character
+//
+
+const config: DatapointConfig = {
+ id: 'DPT4',
+ formatAPDU: (value: string): Buffer | void => {
+ if (!value) return Log.get().warn('DPT4: cannot write null value')
+
+ if (typeof value !== 'string')
+ return Log.get().warn('DPT4: Must supply a character or string')
+
+ const apdu_data: number = value.charCodeAt(0)
+ if (apdu_data > 255)
+ return Log.get().warn('DPT4: must supply an ASCII character')
+
+ return Buffer.from([apdu_data])
+ },
+ fromBuffer: (buf: Buffer): string | void => {
+ if (buf.length !== 1)
+ return Log.get().warn('DPT4: Buffer should be 1 byte long')
+
+ return String.fromCharCode(buf[0])
+ },
+ basetype: {
+ bitlength: 8,
+ valuetype: 'basic',
+ desc: '8-bit character',
+ },
+ subtypes: {
+ // 4.001 character (ASCII)
+ '001': {
+ name: 'DPT_Char_ASCII',
+ desc: 'ASCII character (0-127)',
+ range: [0, 127],
+ use: 'G',
+ },
+ // 4.002 character (ISO-8859-1)
+ '002': {
+ name: 'DPT_Char_8859_1',
+ desc: 'ISO-8859-1 character (0..255)',
+ use: 'G',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt5.js b/src/dptlib/dpt5.js
deleted file mode 100644
index 9ed990c..0000000
--- a/src/dptlib/dpt5.js
+++ /dev/null
@@ -1,54 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-//
-// DPT5: 8-bit unsigned value
-//
-// DPT5 is the only (AFAIK) DPT with scalar datatypes (5.001 and 5.003)
-exports.basetype = {
- "bitlength" : 8,
- "signedness": "unsigned",
- "range" : [0, 255],
- "valuetype" : "basic",
- "desc" : "8-bit unsigned value"
-}
-
-exports.subtypes = {
- // 5.001 percentage (0=0..ff=100%)
- "001" : {
- "name" : "DPT_Scaling", "desc" : "percent",
- "unit" : "%", "scalar_range" : [0, 100]
- },
-
- // 5.003 angle (degrees 0=0, ff=360)
- "003" : {
- "name" : "DPT_Angle", "desc" : "angle degrees",
- "unit" : "°", "scalar_range" : [0, 360]
- },
-
- // 5.004 percentage (0..255%)
- "004" : {
- "name" : "DPT_Percent_U8", "desc" : "percent",
- "unit" : "%",
- },
-
- // 5.005 ratio (0..255)
- "005" : {
- "name" : "DPT_DecimalFactor", "desc" : "ratio",
- "unit" : "ratio",
- },
-
- // 5.006 tariff (0..255)
- "006" : {
- "name" : "DPT_Tariff", "desc" : "tariff",
- "unit" : "tariff",
- },
-
- // 5.010 counter pulses (0..255)
- "010" : {
- "name" : "DPT_Value_1_Ucount", "desc" : "counter pulses",
- "unit" : "pulses",
- },
-}
diff --git a/src/dptlib/dpt5.ts b/src/dptlib/dpt5.ts
new file mode 100644
index 0000000..16e3946
--- /dev/null
+++ b/src/dptlib/dpt5.ts
@@ -0,0 +1,70 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+
+//
+// DPT5: 8-bit unsigned value
+//
+// DPT5 is the only (AFAIK) DPT with scalar datatypes (5.001 and 5.003)
+
+const config: DatapointConfig = {
+ id: 'DPT5',
+ basetype: {
+ bitlength: 8,
+ signedness: 'unsigned',
+ range: [0, 255],
+ valuetype: 'basic',
+ desc: '8-bit unsigned value',
+ },
+
+ subtypes: {
+ // 5.001 percentage (0=0..ff=100%)
+ '001': {
+ name: 'DPT_Scaling',
+ desc: 'percent',
+ unit: '%',
+ scalar_range: [0, 100],
+ },
+
+ // 5.003 angle (degrees 0=0, ff=360)
+ '003': {
+ name: 'DPT_Angle',
+ desc: 'angle degrees',
+ unit: '°',
+ scalar_range: [0, 360],
+ },
+
+ // 5.004 percentage (0..255%)
+ '004': {
+ name: 'DPT_Percent_U8',
+ desc: 'percent',
+ unit: '%',
+ },
+
+ // 5.005 ratio (0..255)
+ '005': {
+ name: 'DPT_DecimalFactor',
+ desc: 'ratio',
+ unit: 'ratio',
+ },
+
+ // 5.006 tariff (0..255)
+ '006': {
+ name: 'DPT_Tariff',
+ desc: 'tariff',
+ unit: 'tariff',
+ },
+
+ // 5.010 counter pulses (0..255)
+ '010': {
+ name: 'DPT_Value_1_Ucount',
+ desc: 'counter pulses',
+ unit: 'pulses',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt6.js b/src/dptlib/dpt6.js
deleted file mode 100644
index c99cabf..0000000
--- a/src/dptlib/dpt6.js
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-// Bitstruct to parse a DPT6 frame (8-bit signed integer)
-// Always 8-bit aligned.
-
-// DPT Basetype info
-exports.basetype = {
- "bitlength" : 8,
- "signedness": "signed",
- "valuetype" : "basic",
- "desc" : "8-bit signed value",
- "range" : [-128, 127]
-}
-
-// DPT subtypes info
-exports.subtypes = {
- // 6.001 percentage (-128%..127%)
- "001" : {
- "name" : "DPT_Switch", "desc" : "percent",
- "unit" : "%",
- },
-
- // 6.002 counter pulses (-128..127)
- "010" : {
- "name" : "DPT_Bool", "desc" : "counter pulses",
- "unit" : "pulses"
- },
-
- //
-}
diff --git a/src/dptlib/dpt6.ts b/src/dptlib/dpt6.ts
new file mode 100644
index 0000000..febfb35
--- /dev/null
+++ b/src/dptlib/dpt6.ts
@@ -0,0 +1,43 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+
+// Bitstruct to parse a DPT6 frame (8-bit signed integer)
+// Always 8-bit aligned.
+
+// DPT Basetype info
+
+const config: DatapointConfig = {
+ id: 'DPT6',
+ basetype: {
+ bitlength: 8,
+ signedness: 'signed',
+ valuetype: 'basic',
+ desc: '8-bit signed value',
+ range: [-128, 127],
+ },
+
+ // DPT subtypes info
+ subtypes: {
+ // 6.001 percentage (-128%..127%)
+ '001': {
+ name: 'DPT_Switch',
+ desc: 'percent',
+ unit: '%',
+ },
+
+ // 6.002 counter pulses (-128..127)
+ '010': {
+ name: 'DPT_Bool',
+ desc: 'counter pulses',
+ unit: 'pulses',
+ },
+
+ //
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt60001.ts b/src/dptlib/dpt60001.ts
new file mode 100644
index 0000000..e07ba80
--- /dev/null
+++ b/src/dptlib/dpt60001.ts
@@ -0,0 +1,345 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+
+function toRadix(value: number, radix: number) {
+ if (!Number.isSafeInteger(value)) {
+ Log.get().error('value must be a safe integer')
+ }
+
+ const digits = Math.ceil(64 / Math.log2(radix))
+ const twosComplement =
+ value < 0 ? BigInt(radix) ** BigInt(digits) + BigInt(value) : value
+
+ return twosComplement.toString(radix).padStart(digits, '0')
+}
+
+function griesserSectorCode(Byte0: number, Byte1: number) {
+ const SectorCode = Byte0 + (Byte1 & 3) * 256
+ return SectorCode
+}
+
+function griesserCommandCode(Byte1: number) {
+ const command = (Byte1 / 4) >> 0
+ return command
+}
+
+function griesserParameter(
+ command: number,
+ Byte2: number,
+ Byte3: number,
+ Byte4: number,
+ Byte5: number,
+) {
+ let commandOperation: string
+ switch (command) {
+ case 1: // drive command
+ switch (Byte2 & 31) {
+ case 0:
+ return 'no driving movement'
+ case 1:
+ return 'upper end position'
+ case 2:
+ return 'lower end position'
+ case 3:
+ if (Byte3 >= 1 && Byte3 <= 4) {
+ return `fixed position P${Byte3} approach`
+ }
+ return `Unknown value for Pn ${Byte3}`
+
+ default:
+ return `Unknown drive command${Byte2}`
+ }
+ case 4: // set/delete lock
+ if (Byte2 === 0) {
+ return 'no lock'
+ }
+ switch (Byte2 & 3) {
+ case 1:
+ return 'driving command'
+ case 2:
+ return 'button lock'
+ case 3:
+ return 'driving command- and button lock'
+ }
+ if (Byte3 === 0) {
+ return 'delete lock'
+ }
+ return 'set lock'
+
+ case 5: // operation code
+ if (Byte2 <= 6) {
+ commandOperation = 'groupoperation'
+ } else if (Byte2 >= 128 && Byte2 <= 134) {
+ commandOperation = 'localoperation'
+ } else {
+ commandOperation = `unknown command Byte3 for ${Byte2}`
+ }
+ if ((Byte2 & 127) === 0) {
+ return [commandOperation, 'long up']
+ }
+ if ((Byte2 & 127) === 1) {
+ return [commandOperation, 'long down']
+ }
+ if ((Byte2 & 127) === 2) {
+ return [commandOperation, 'short up']
+ }
+ if ((Byte2 & 127) === 3) {
+ return [commandOperation, 'short down']
+ }
+ if ((Byte2 & 127) === 4) {
+ return [commandOperation, 'stop']
+ }
+ if ((Byte2 & 127) === 5) {
+ return [commandOperation, 'long-short up']
+ }
+ if ((Byte2 & 127) === 6) {
+ return [commandOperation, 'long-short down']
+ }
+ return [commandOperation, `unknown command Byte3 for ${Byte2}`]
+
+ case 22: // driving range limits for automatic button commands
+ return [
+ `min. angle: ${Byte2}`,
+ `max. angle: ${Byte3}`,
+ `min. height: ${Byte4}`,
+ `max. height: ${Byte5}`,
+ ]
+ default:
+ return `unknown value for command: ${command}`
+ }
+}
+
+function griesserSectors(SectorCode: number) {
+ let SectorMin: number
+ let SectorMax: number
+ let dA: number
+ let a: number
+ let SectorCodeMin: number
+ let SectorCodeMax: number
+ dA = 1
+ a = SectorCode
+ if (a > 0) {
+ while ((a & 1) === 0) {
+ a = (a / 2) >> 0
+ dA *= 2
+ }
+ dA -= 1
+ SectorMin = SectorCode - dA
+ SectorMax = SectorCode + dA
+ } else {
+ SectorMin = 0
+ SectorMax = 0
+ }
+ if (SectorMin === 0) {
+ SectorCodeMin = 0
+ } else {
+ SectorCodeMin = (((SectorMin - 1) / 2) >> 0) + 1
+ }
+ if (SectorMax === 0) {
+ SectorCodeMax = 0
+ } else {
+ SectorCodeMax = (((SectorMax - 1) / 2) >> 0) + 1
+ }
+ if (SectorCodeMax === SectorCodeMin) {
+ return [SectorCodeMin]
+ }
+ const Sectors = []
+ for (let i = SectorCodeMin; i <= SectorCodeMax; i++) {
+ Sectors.push(i)
+ }
+ return Sectors
+}
+
+function griesserSectorToSectorCode(sectors: number[]) {
+ if (sectors.length === 1) {
+ return sectors[0] + sectors[0] - 1
+ }
+ return Math.min(...sectors) + Math.max(...sectors) - 1
+}
+
+function griesserCommandToCommandCode(command: string) {
+ switch (command) {
+ case 'operation code':
+ return 5
+ default:
+ Log.get().error(`not implemented yet: ${command}`)
+ }
+}
+
+function griesserCommandToCommandCodeP1(command: string) {
+ switch (command) {
+ case 'long up':
+ return 128
+ case 'long down':
+ return 129
+ case 'short up':
+ return 130
+ case 'short down':
+ return 131
+ case 'stop':
+ return 132
+ case 'long-short up':
+ return 133
+ case 'long-short down':
+ return 134
+ default:
+ Log.get().error(`unknown command: ${command}`)
+ }
+}
+
+function griesserCommand(command: number) {
+ switch (command) {
+ case 1:
+ return 'drive command'
+ case 2:
+ return 'value correction'
+ case 3:
+ return 'automatic state'
+ case 4:
+ return 'set/delete lock'
+ case 5:
+ return 'operation code'
+ case 6:
+ return 'set scene'
+ case 7:
+ return 'special command'
+ case 8:
+ return 'date'
+ case 9:
+ return 'sync time'
+ case 10:
+ return 'sensor reading notification'
+ case 11:
+ return 'bus monitoring'
+ case 16:
+ return 'driving range limits for safety drive commands'
+ case 17:
+ return 'driving range limits for safety drive commands'
+ case 19:
+ return 'driving range limits for safety drive commands'
+ case 20:
+ return 'driving range limits for safety drive commands'
+ case 22:
+ return 'driving range limits for automatic drive commands'
+ case 23:
+ return 'driving range limits for automatic drive commands'
+ case 24:
+ return 'driving range limits for automatic drive commands'
+ default:
+ return `unknown value for function: ${command}`
+ }
+}
+
+function griesserPrio(prio: number, command: number) {
+ const prioCommand = ((command & 224) / 32) >> 0
+ if (((prio & 252) / 4) >> 0 === 0) {
+ switch (prioCommand) {
+ case 0:
+ return 'border command'
+ case 1:
+ return 'automatic command'
+ case 3:
+ return 'priority command'
+ case 4:
+ return 'warning command'
+ case 5:
+ return 'security command'
+ case 6:
+ return 'danger command'
+ default:
+ return `unknown priority${prioCommand}`
+ }
+ } else {
+ return '-'
+ }
+}
+
+// Send to BUS
+const config: DatapointConfig = {
+ id: 'DPT60001',
+ formatAPDU(value) {
+ if (!value) {
+ Log.get().error('DPT60001: cannot write null value')
+ } else {
+ if (
+ typeof value === 'object' &&
+ Object.prototype.hasOwnProperty.call(value, 'command') &&
+ Object.prototype.hasOwnProperty.call(value, 'data') &&
+ Object.prototype.hasOwnProperty.call(value, 'sectors') &&
+ value.data[0] === 'localoperation'
+ ) {
+ const sectorCode = griesserSectorToSectorCode(value.sectors)
+ const commandCode = griesserCommandToCommandCode(value.command)
+ const p1 = griesserCommandToCommandCodeP1(value.data[1])
+ const bufferTotal = Buffer.alloc(6)
+ bufferTotal[0] = parseInt(toRadix(sectorCode, 2).slice(-8), 2)
+ bufferTotal[1] = parseInt(
+ toRadix(commandCode, 2).slice(-6) +
+ toRadix(sectorCode, 2).slice(-10, -8),
+ 2,
+ )
+ bufferTotal[2] = parseInt(toRadix(p1, 2).slice(-8), 2)
+ return bufferTotal
+ }
+ Log.get().error(
+ 'DPT60001: Must supply an value {command:"operation code", data:["localoperation", "long up"], sectors:[159]}',
+ )
+ }
+ },
+
+ // RX from BUS
+ fromBuffer(buf) {
+ if (buf.length !== 6) {
+ Log.get().warn(
+ 'DPTGriesser.fromBuffer: buf should be 6 bytes long (got %d bytes)',
+ buf.length,
+ )
+ return null
+ }
+ const hexToDecimal = (hex) => parseInt(hex, 16)
+ const bufTotale = buf.toString('hex')
+ const Byte0 = hexToDecimal(bufTotale.slice(0, 2))
+ const Byte1 = hexToDecimal(bufTotale.slice(2, 4))
+ const Byte2 = hexToDecimal(bufTotale.slice(4, 6))
+ const Byte3 = hexToDecimal(bufTotale.slice(6, 8))
+ const Byte4 = hexToDecimal(bufTotale.slice(8, 10))
+ const Byte5 = hexToDecimal(bufTotale.slice(10, 12))
+ const sectorCode = griesserSectorCode(Byte0, Byte1)
+ const commandCode = griesserCommandCode(Byte1)
+
+ return {
+ Byte0,
+ Byte1,
+ Byte2,
+ Byte3,
+ Byte4,
+ Byte5,
+ sectorCode,
+ commandCode,
+ sectors: griesserSectors(sectorCode),
+ prio: griesserPrio(Byte1, Byte2),
+ command: griesserCommand(commandCode),
+ data: griesserParameter(commandCode, Byte2, Byte3, Byte4, Byte5),
+ }
+ },
+
+ // DPT Griesser Object basetype info
+ basetype: {
+ bitlength: 4 * 8 + 2 * 6 + 1 * 10,
+ valuetype: 'composite',
+ desc: 'Commands for solar shading actors',
+ },
+ subtypes: {
+ '001': {
+ desc: 'DPT_Griesser_Object',
+ name: 'Griesser Object',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt7.js b/src/dptlib/dpt7.js
deleted file mode 100644
index 7c864e3..0000000
--- a/src/dptlib/dpt7.js
+++ /dev/null
@@ -1,101 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-//
-// DPT7: 16-bit unsigned integer value
-//
-exports.basetype = {
- "bitlength" : 16,
- "signedness": "unsigned",
- "valuetype" : "basic",
- "desc" : "16-bit unsigned value"
-}
-
-// DPT subtypes info
-exports.subtypes = {
- // 7.001 pulses
- "001" : { "use" : "G",
- "name" : "DPT_Value_2_Ucount",
- "desc" : "pulses",
- "unit" : "pulses"
- },
-
- // 7.002 time(ms)
- "002" : { "use" : "G",
- "name" : "DPT_TimePeriodMsec",
- "desc" : "time (ms)",
- "unit" : "milliseconds"
- },
-
- // 7.003 time (10ms)
- "003" : { "use" : "G",
- "name" : "DPT_TimePeriod10Msec",
- "desc" : "time (10ms)",
- "unit" : "centiseconds"
- },
-
- // 7.004 time (100ms)
- "004" : { "use" : "G",
- "name" : "DPT_TimePeriod100Msec",
- "desc" : "time (100ms)",
- "unit" : "deciseconds"
- },
-
- // 7.005 time (sec)
- "005" : { "use" : "G",
- "name" : "DPT_TimePeriodSec",
- "desc" : "time (s)",
- "unit" : "seconds"
- },
-
- // 7.006 time (min)
- "006" : { "use" : "G",
- "name" : "DPT_TimePeriodMin",
- "desc" : "time (min)",
- "unit" : "minutes"
- },
-
- // 7.007 time (hour)
- "007" : { "use" : "G",
- "name" : "DPT_TimePeriodHrs",
- "desc" : "time (hrs)",
- "unit" : "hours"
- },
-
- // 7.010 DPT_PropDataType
- // not to be used in runtime communications!
- "010" : { "use" : "FB",
- "name" : "DPT_PropDataType",
- "desc" : "Identifier Interface Object Property data type "
- },
-
- // 7.011
- "011" : { "use" : "FB SAB",
- "name" : "DPT_Length_mm",
- "desc" : "Length in mm",
- "unit" : "mm"
- },
-
- // 7.012
- "012" : { "use" : "FB",
- "name" : "DPT_UEICurrentmA",
- "desc" : "bus power supply current (mA)",
- "unit" : "mA"
- },
-
- // 7.013
- "013" : { "use" : "FB",
- "name" : "DPT_Brightness",
- "desc" : "interior brightness",
- "unit" : "lux"
- },
-
- // 7.600
- "600" : { "use" : "FB",
- "name" : "DPT_Absolute_Colour_Temperature",
- "desc" : "Absolute colour temperature",
- "unit" : "K"
- }
-}
diff --git a/src/dptlib/dpt7.ts b/src/dptlib/dpt7.ts
new file mode 100644
index 0000000..7822469
--- /dev/null
+++ b/src/dptlib/dpt7.ts
@@ -0,0 +1,120 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+
+//
+// DPT7: 16-bit unsigned integer value
+//
+const config: DatapointConfig = {
+ id: 'DPT7',
+ basetype: {
+ bitlength: 16,
+ signedness: 'unsigned',
+ valuetype: 'basic',
+ desc: '16-bit unsigned value',
+ },
+
+ // DPT subtypes info
+ subtypes: {
+ // 7.001 pulses
+ '001': {
+ use: 'G',
+ name: 'DPT_Value_2_Ucount',
+ desc: 'pulses',
+ unit: 'pulses',
+ },
+
+ // 7.002 time(ms)
+ '002': {
+ use: 'G',
+ name: 'DPT_TimePeriodMsec',
+ desc: 'time (ms)',
+ unit: 'milliseconds',
+ },
+
+ // 7.003 time (10ms)
+ '003': {
+ use: 'G',
+ name: 'DPT_TimePeriod10Msec',
+ desc: 'time (10ms)',
+ unit: 'centiseconds',
+ },
+
+ // 7.004 time (100ms)
+ '004': {
+ use: 'G',
+ name: 'DPT_TimePeriod100Msec',
+ desc: 'time (100ms)',
+ unit: 'deciseconds',
+ },
+
+ // 7.005 time (sec)
+ '005': {
+ use: 'G',
+ name: 'DPT_TimePeriodSec',
+ desc: 'time (s)',
+ unit: 'seconds',
+ },
+
+ // 7.006 time (min)
+ '006': {
+ use: 'G',
+ name: 'DPT_TimePeriodMin',
+ desc: 'time (min)',
+ unit: 'minutes',
+ },
+
+ // 7.007 time (hour)
+ '007': {
+ use: 'G',
+ name: 'DPT_TimePeriodHrs',
+ desc: 'time (hrs)',
+ unit: 'hours',
+ },
+
+ // 7.010 DPT_PropDataType
+ // not to be used in runtime communications!
+ '010': {
+ use: 'FB',
+ name: 'DPT_PropDataType',
+ desc: 'Identifier Interface Object Property data type ',
+ },
+
+ // 7.011
+ '011': {
+ use: 'FB SAB',
+ name: 'DPT_Length_mm',
+ desc: 'Length in mm',
+ unit: 'mm',
+ },
+
+ // 7.012
+ '012': {
+ use: 'FB',
+ name: 'DPT_UEICurrentmA',
+ desc: 'bus power supply current (mA)',
+ unit: 'mA',
+ },
+
+ // 7.013
+ '013': {
+ use: 'FB',
+ name: 'DPT_Brightness',
+ desc: 'interior brightness',
+ unit: 'lux',
+ },
+
+ // 7.600
+ '600': {
+ use: 'FB',
+ name: 'DPT_Absolute_Colour_Temperature',
+ desc: 'Absolute colour temperature',
+ unit: 'K',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt8.js b/src/dptlib/dpt8.js
deleted file mode 100644
index a6a80ab..0000000
--- a/src/dptlib/dpt8.js
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-//
-// DPT8.*: 2-byte signed value
-//
-
-// DPT8 basetype info
-exports.basetype = {
- "bitlength" : 16,
- "signedness": "signed",
- "valuetype" : "basic",
- "range" : [-32768, 32767],
- "desc" : "16-bit signed value"
-}
-
-// DPT8 subtypes info
-exports.subtypes = {
- // 8.001 pulses difference
- "001" : {
- "name" : "DPT_Value_2_Count",
- "desc" : "pulses",
- "unit" : "pulses"
- },
-
- // 8.002 time lag (ms)
- "002" : {
- "name" : "DPT_DeltaTimeMsec",
- "desc" : "time lag(ms)",
- "unit" : "milliseconds"
- },
-
- // 8.003 time lag (10ms)
- "003" : {
- "name" : "DPT_DeltaTime10Msec",
- "desc" : "time lag(10ms)",
- "unit" : "centiseconds"
- },
-
- // 8.004 time lag (100ms)
- "004" : {
- "name" : "DPT_DeltaTime100Msec",
- "desc" : "time lag(100ms)",
- "unit" : "deciseconds"
- },
-
- // 8.005 time lag (sec)
- "005" : {
- "name" : "DPT_DeltaTimeSec",
- "desc" : "time lag(s)",
- "unit" : "seconds"
- },
-
- // 8.006 time lag (min)
- "006" : {
- "name" : "DPT_DeltaTimeMin",
- "desc" : "time lag(min)",
- "unit" : "minutes"
- },
-
- // 8.007 time lag (hour)
- "007" : {
- "name" : "DPT_DeltaTimeHrs",
- "desc" : "time lag(hrs)",
- "unit" : "hours"
- },
-
- // 8.010 percentage difference (%)
- "010" : {
- "name" : "DPT_Percent_V16",
- "desc" : "percentage difference",
- "unit" : "%"
- },
-
- // 8.011 rotation angle (deg)
- "011" : {
- "name" : "DPT_RotationAngle",
- "desc" : "angle(degrees)",
- "unit" : "°"
- },
-}
diff --git a/src/dptlib/dpt8.ts b/src/dptlib/dpt8.ts
new file mode 100644
index 0000000..1122612
--- /dev/null
+++ b/src/dptlib/dpt8.ts
@@ -0,0 +1,90 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import type { DatapointConfig } from '.'
+
+//
+// DPT8.*: 2-byte signed value
+//
+
+const config: DatapointConfig = {
+ id: 'DPT8',
+ // DPT8 basetype info
+ basetype: {
+ bitlength: 16,
+ signedness: 'signed',
+ valuetype: 'basic',
+ range: [-32768, 32767],
+ desc: '16-bit signed value',
+ },
+
+ // DPT8 subtypes info
+ subtypes: {
+ // 8.001 pulses difference
+ '001': {
+ name: 'DPT_Value_2_Count',
+ desc: 'pulses',
+ unit: 'pulses',
+ },
+
+ // 8.002 time lag (ms)
+ '002': {
+ name: 'DPT_DeltaTimeMsec',
+ desc: 'time lag(ms)',
+ unit: 'milliseconds',
+ },
+
+ // 8.003 time lag (10ms)
+ '003': {
+ name: 'DPT_DeltaTime10Msec',
+ desc: 'time lag(10ms)',
+ unit: 'centiseconds',
+ },
+
+ // 8.004 time lag (100ms)
+ '004': {
+ name: 'DPT_DeltaTime100Msec',
+ desc: 'time lag(100ms)',
+ unit: 'deciseconds',
+ },
+
+ // 8.005 time lag (sec)
+ '005': {
+ name: 'DPT_DeltaTimeSec',
+ desc: 'time lag(s)',
+ unit: 'seconds',
+ },
+
+ // 8.006 time lag (min)
+ '006': {
+ name: 'DPT_DeltaTimeMin',
+ desc: 'time lag(min)',
+ unit: 'minutes',
+ },
+
+ // 8.007 time lag (hour)
+ '007': {
+ name: 'DPT_DeltaTimeHrs',
+ desc: 'time lag(hrs)',
+ unit: 'hours',
+ },
+
+ // 8.010 percentage difference (%)
+ '010': {
+ name: 'DPT_Percent_V16',
+ desc: 'percentage difference',
+ unit: '%',
+ },
+
+ // 8.011 rotation angle (deg)
+ '011': {
+ name: 'DPT_RotationAngle',
+ desc: 'angle(degrees)',
+ unit: '°',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt9.js b/src/dptlib/dpt9.js
deleted file mode 100644
index d130341..0000000
--- a/src/dptlib/dpt9.js
+++ /dev/null
@@ -1,229 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-const log = require('log-driver').logger;
-
-//
-// DPT9.*: 2-byte floating point value
-//
-
-const util = require('util');
-// kudos to http://croquetweak.blogspot.gr/2014/08/deconstructing-floats-frexp-and-ldexp.html
-const ldexp = (mantissa, exponent) =>
- exponent > 1023 // avoid multiplying by infinity
- ? mantissa * Math.pow(2, 1023) * Math.pow(2, exponent - 1023)
- : exponent < -1074 // avoid multiplying by zero
- ? mantissa * Math.pow(2, -1074) * Math.pow(2, exponent + 1074)
- : mantissa * Math.pow(2, exponent);
-
-const frexp = (value) => {
- if (value === 0) return [0, 0];
- const data = new DataView(new ArrayBuffer(8));
- data.setFloat64(0, value);
- let bits = (data.getUint32(0) >>> 20) & 0x7ff;
- if (bits === 0) {
- data.setFloat64(0, value * Math.pow(2, 64));
- bits = ((data.getUint32(0) >>> 20) & 0x7ff) - 64;
- }
- const exponent = bits - 1022;
- const mantissa = ldexp(value, -exponent);
- return [mantissa, exponent];
-};
-
-exports.formatAPDU = (value) => {
- if (!isFinite(value))
- return log.warn('DPT9: cannot write non-numeric or undefined value');
-
- const arr = frexp(value);
- const [mantissa, exponent] = arr;
- // find the minimum exponent that will upsize the normalized mantissa (0,5 to 1 range)
- // in order to fit in 11 bits ([-2048, 2047])
- let max_mantissa = 0;
- for (e = exponent; e >= -15; e--) {
- max_mantissa = ldexp(100 * mantissa, e);
- if (max_mantissa > -2048 && max_mantissa < 2047) break;
- }
- const sign = mantissa < 0 ? 1 : 0;
- const mant = mantissa < 0 ? ~(max_mantissa ^ 2047) : max_mantissa;
- const exp = exponent - e;
- // yucks
- return Buffer.from([(sign << 7) + (exp << 3) + (mant >> 8), mant % 256]);
-};
-
-exports.fromBuffer = (buf) => {
- if (buf.length != 2)
- return log.warn(
- 'DPT9.fromBuffer: buf should be 2 bytes long (got %d bytes)',
- buf.length
- );
-
- const sign = buf[0] >> 7;
- const exponent = (buf[0] & 0b01111000) >> 3;
- let mantissa = 256 * (buf[0] & 0b00000111) + buf[1];
- if (sign) mantissa = ~(mantissa ^ 2047);
- return parseFloat(ldexp(0.01 * mantissa, exponent).toPrecision(15));
-};
-
-// DPT9 basetype info
-exports.basetype = {
- bitlength: 16,
- valuetype: 'basic',
- desc: '16-bit floating point value',
-};
-
-// DPT9 subtypes
-exports.subtypes = {
- // 9.001 temperature (oC)
- '001': {
- name: 'DPT_Value_Temp',
- desc: 'temperature',
- unit: '°C',
- range: [-273, 670760],
- },
-
- // 9.002 temperature difference (oC)
- '002': {
- name: 'DPT_Value_Tempd',
- desc: 'temperature difference',
- unit: '°C',
- range: [-670760, 670760],
- },
-
- // 9.003 kelvin/hour (K/h)
- '003': {
- name: 'DPT_Value_Tempa',
- desc: 'kelvin/hour',
- unit: '°K/h',
- range: [-670760, 670760],
- },
-
- // 9.004 lux (Lux)
- '004': {
- name: 'DPT_Value_Lux',
- desc: 'lux',
- unit: 'lux',
- range: [0, 670760],
- },
-
- // 9.005 speed (m/s)
- '005': {
- name: 'DPT_Value_Wsp',
- desc: 'wind speed',
- unit: 'm/s',
- range: [0, 670760],
- },
-
- // 9.006 pressure (Pa)
- '006': {
- name: 'DPT_Value_Pres',
- desc: 'pressure',
- unit: 'Pa',
- range: [0, 670760],
- },
-
- // 9.007 humidity (%)
- '007': {
- name: 'DPT_Value_Humidity',
- desc: 'humidity',
- unit: '%',
- range: [0, 670760],
- },
-
- // 9.008 parts/million (ppm)
- '008': {
- name: 'DPT_Value_AirQuality',
- desc: 'air quality',
- unit: 'ppm',
- range: [0, 670760],
- },
-
- // 9.010 time (s)
- '010': {
- name: 'DPT_Value_Time1',
- desc: 'time(sec)',
- unit: 's',
- range: [-670760, 670760],
- },
-
- // 9.011 time (ms)
- '011': {
- name: 'DPT_Value_Time2',
- desc: 'time(msec)',
- unit: 'ms',
- range: [-670760, 670760],
- },
-
- // 9.020 voltage (mV)
- '020': {
- name: 'DPT_Value_Volt',
- desc: 'voltage',
- unit: 'mV',
- range: [-670760, 670760],
- },
-
- // 9.021 current (mA)
- '021': {
- name: 'DPT_Value_Curr',
- desc: 'current',
- unit: 'mA',
- range: [-670760, 670760],
- },
-
- // 9.022 power density (W/m2)
- '022': {
- name: 'DPT_PowerDensity',
- desc: 'power density',
- unit: 'W/m²',
- range: [-670760, 670760],
- },
-
- // 9.023 kelvin/percent (K/%)
- '023': {
- name: 'DPT_KelvinPerPercent',
- desc: 'Kelvin / %',
- unit: 'K/%',
- range: [-670760, 670760],
- },
-
- // 9.024 power (kW)
- '024': {
- name: 'DPT_Power',
- desc: 'power (kW)',
- unit: 'kW',
- range: [-670760, 670760],
- },
-
- // 9.025 volume flow (l/h)
- '025': {
- name: 'DPT_Value_Volume_Flow',
- desc: 'volume flow',
- unit: 'l/h',
- range: [-670760, 670760],
- },
-
- // 9.026 rain amount (l/m2)
- '026': {
- name: 'DPT_Rain_Amount',
- desc: 'rain amount',
- unit: 'l/m²',
- range: [-670760, 670760],
- },
-
- // 9.027 temperature (Fahrenheit)
- '027': {
- name: 'DPT_Value_Temp_F',
- desc: 'temperature (F)',
- unit: '°F',
- range: -[459.6, 670760],
- },
-
- // 9.028 wind speed (km/h)
- '028': {
- name: 'DPT_Value_Wsp_kmh',
- desc: 'wind speed (km/h)',
- unit: 'km/h',
- range: [0, 670760],
- },
-};
diff --git a/src/dptlib/dpt9.ts b/src/dptlib/dpt9.ts
new file mode 100644
index 0000000..46b08eb
--- /dev/null
+++ b/src/dptlib/dpt9.ts
@@ -0,0 +1,216 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { frexp, ldexp } from '../utils'
+
+//
+// DPT9.*: 2-byte floating point value
+//
+
+const config: DatapointConfig = {
+ id: 'DPT9',
+ formatAPDU: (value) => {
+ if (!isFinite(value))
+ return Log.get().warn(
+ 'DPT9: cannot write non-numeric or undefined value',
+ )
+
+ const arr = frexp(value)
+ const [mantissa, exponent] = arr
+ // find the minimum exponent that will upsize the normalized mantissa (0,5 to 1 range)
+ // in order to fit in 11 bits ([-2048, 2047])
+ let max_mantissa = 0
+ let e: number
+ for (e = exponent; e >= -15; e--) {
+ max_mantissa = ldexp(100 * mantissa, e)
+ if (max_mantissa > -2048 && max_mantissa < 2047) break
+ }
+ const sign = mantissa < 0 ? 1 : 0
+ const mant = mantissa < 0 ? ~(max_mantissa ^ 2047) : max_mantissa
+ const exp = exponent - e
+ // yucks
+ return Buffer.from([(sign << 7) + (exp << 3) + (mant >> 8), mant % 256])
+ },
+
+ fromBuffer: (buf) => {
+ if (buf.length !== 2)
+ return Log.get().warn(
+ 'DPT9.fromBuffer: buf should be 2 bytes long (got %d bytes)',
+ buf.length,
+ )
+
+ const sign = buf[0] >> 7
+ const exponent = (buf[0] & 0b01111000) >> 3
+ let mantissa = 256 * (buf[0] & 0b00000111) + buf[1]
+ if (sign) mantissa = ~(mantissa ^ 2047)
+ return parseFloat(ldexp(0.01 * mantissa, exponent).toPrecision(15))
+ },
+
+ // DPT9 basetype info
+ basetype: {
+ bitlength: 16,
+ valuetype: 'basic',
+ desc: '16-bit floating point value',
+ },
+
+ // DPT9 subtypes
+ subtypes: {
+ // 9.001 temperature (oC)
+ '001': {
+ name: 'DPT_Value_Temp',
+ desc: 'temperature',
+ unit: '°C',
+ range: [-273, 670760],
+ },
+
+ // 9.002 temperature difference (oC)
+ '002': {
+ name: 'DPT_Value_Tempd',
+ desc: 'temperature difference',
+ unit: '°C',
+ range: [-670760, 670760],
+ },
+
+ // 9.003 kelvin/hour (K/h)
+ '003': {
+ name: 'DPT_Value_Tempa',
+ desc: 'kelvin/hour',
+ unit: '°K/h',
+ range: [-670760, 670760],
+ },
+
+ // 9.004 lux (Lux)
+ '004': {
+ name: 'DPT_Value_Lux',
+ desc: 'lux',
+ unit: 'lux',
+ range: [0, 670760],
+ },
+
+ // 9.005 speed (m/s)
+ '005': {
+ name: 'DPT_Value_Wsp',
+ desc: 'wind speed',
+ unit: 'm/s',
+ range: [0, 670760],
+ },
+
+ // 9.006 pressure (Pa)
+ '006': {
+ name: 'DPT_Value_Pres',
+ desc: 'pressure',
+ unit: 'Pa',
+ range: [0, 670760],
+ },
+
+ // 9.007 humidity (%)
+ '007': {
+ name: 'DPT_Value_Humidity',
+ desc: 'humidity',
+ unit: '%',
+ range: [0, 670760],
+ },
+
+ // 9.008 parts/million (ppm)
+ '008': {
+ name: 'DPT_Value_AirQuality',
+ desc: 'air quality',
+ unit: 'ppm',
+ range: [0, 670760],
+ },
+
+ // 9.010 time (s)
+ '010': {
+ name: 'DPT_Value_Time1',
+ desc: 'time(sec)',
+ unit: 's',
+ range: [-670760, 670760],
+ },
+
+ // 9.011 time (ms)
+ '011': {
+ name: 'DPT_Value_Time2',
+ desc: 'time(msec)',
+ unit: 'ms',
+ range: [-670760, 670760],
+ },
+
+ // 9.020 voltage (mV)
+ '020': {
+ name: 'DPT_Value_Volt',
+ desc: 'voltage',
+ unit: 'mV',
+ range: [-670760, 670760],
+ },
+
+ // 9.021 current (mA)
+ '021': {
+ name: 'DPT_Value_Curr',
+ desc: 'current',
+ unit: 'mA',
+ range: [-670760, 670760],
+ },
+
+ // 9.022 power density (W/m2)
+ '022': {
+ name: 'DPT_PowerDensity',
+ desc: 'power density',
+ unit: 'W/m²',
+ range: [-670760, 670760],
+ },
+
+ // 9.023 kelvin/percent (K/%)
+ '023': {
+ name: 'DPT_KelvinPerPercent',
+ desc: 'Kelvin / %',
+ unit: 'K/%',
+ range: [-670760, 670760],
+ },
+
+ // 9.024 power (kW)
+ '024': {
+ name: 'DPT_Power',
+ desc: 'power (kW)',
+ unit: 'kW',
+ range: [-670760, 670760],
+ },
+
+ // 9.025 volume flow (l/h)
+ '025': {
+ name: 'DPT_Value_Volume_Flow',
+ desc: 'volume flow',
+ unit: 'l/h',
+ range: [-670760, 670760],
+ },
+
+ // 9.026 rain amount (l/m2)
+ '026': {
+ name: 'DPT_Rain_Amount',
+ desc: 'rain amount',
+ unit: 'l/m²',
+ range: [-670760, 670760],
+ },
+
+ // 9.027 temperature (Fahrenheit)
+ '027': {
+ name: 'DPT_Value_Temp_F',
+ desc: 'temperature (F)',
+ unit: '°F',
+ range: [-459.6, 670760],
+ },
+
+ // 9.028 wind speed (km/h)
+ '028': {
+ name: 'DPT_Value_Wsp_kmh',
+ desc: 'wind speed (km/h)',
+ unit: 'km/h',
+ range: [0, 670760],
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/dpt999.ts b/src/dptlib/dpt999.ts
new file mode 100644
index 0000000..1093eb6
--- /dev/null
+++ b/src/dptlib/dpt999.ts
@@ -0,0 +1,60 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import Log from '../KnxLog'
+import type { DatapointConfig } from '.'
+import { hexToDec } from '../utils'
+
+//
+// DPT999: 10 Bytes (RFID keypad style)
+//
+const config: DatapointConfig = {
+ id: 'DPT999',
+ formatAPDU(value) {
+ if (typeof value !== 'string' || value.length < 10)
+ Log.get().warn(
+ "Must supply an HEX string value of 10 bytes. Please don't add '$' nor '0x' Example 12340000000000000000",
+ )
+ else {
+ value = value
+ .toUpperCase()
+ .replace(/\$/g, '')
+ .replace(/0X/g, '')
+ .replace(/ /g, '') // Remove the $ and 0x
+ const apdu_data = Buffer.alloc(10)
+ let i = 0
+ const iSlice = 2
+ for (let index = 0; index < value.length; index += iSlice) {
+ const sHex = value.substring(index, iSlice + index)
+ const int = hexToDec(sHex)
+ apdu_data[i] = int
+ i++
+ }
+ return apdu_data
+ }
+ },
+ fromBuffer(buf) {
+ return buf.toString('hex')
+ },
+
+ // basetype info
+ basetype: {
+ bitlength: 80,
+ valuetype: 'basic',
+ desc: '10-bytes',
+ },
+
+ // DPT999 subtypes
+ subtypes: {
+ // 10 Bytes string hex value
+ '001': {
+ use: 'G',
+ desc: '10Bytes HEX',
+ name: '10 Bytes',
+ },
+ },
+}
+
+export default config
diff --git a/src/dptlib/index.js b/src/dptlib/index.js
deleted file mode 100644
index 2181a5a..0000000
--- a/src/dptlib/index.js
+++ /dev/null
@@ -1,203 +0,0 @@
-/**
- * knx.js - a KNX protocol stack in pure Javascript
- * (C) 2016-2018 Elias Karakoulakis
- */
-
-/*
-Datatypes
-=========
-KNX/EIB Function Information length EIS DPT Value
-Switch 1 Bit EIS 1 DPT 1 0,1
-Dimming (Position, Control, Value) 1 Bit, 4 Bit, 8 Bit EIS 2 DPT 3 [0,0]...[1,7]
-Time 3 Byte EIS 3 DPT 10
-Date 3 Byte EIS 4 DPT 11
-Floating point 2 Byte EIS 5 DPT 9 -671088,64 - 670760,96
-8-bit unsigned value 1 Byte EIS 6 DPT 5 0...255
-8-bit unsigned value 1 Byte DPT 5.001 DPT 5.001 0...100
-Blinds / Roller shutter 1 Bit EIS 7 DPT 1 0,1
-Priority 2 Bit EIS 8 DPT 2 [0,0]...[1,1]
-IEEE Floating point 4 Byte EIS 9 DPT 14 4-Octet Float Value IEEE 754
-16-bit unsigned value 2 Byte EIS 10 DPT 7 0...65535
-16-bit signed value 2 Byte DPT 8 DPT 8 -32768...32767
-32-bit unsigned value 4 Byte EIS 11 DPT 12 0...4294967295
-32-bit signed value 4 Byte DPT 13 DPT 13 -2147483648...2147483647
-Access control 1 Byte EIS 12 DPT 15
-ASCII character 1 Byte EIS 13 DPT 4
-8859_1 character 1 Byte DPT 4.002 DPT 4.002
-8-bit signed value 1 Byte EIS 14 DPT 6 -128...127
-14 character ASCII 14 Byte EIS 15 DPT 16
-14 character 8859_1 14 Byte DPT 16.001 DPT 16.001
-Scene 1 Byte DPT 17 DPT 17 0...63
-HVAC 1 Byte DPT 20 DPT 20 0..255
-Unlimited string 8859_1 . DPT 24 DPT 24
-List 3-byte value 3 Byte DPT 232 DPT 232 RGB[0,0,0]...[255,255,255]
-*/
-
-const fs = require('fs');
-const path = require('path');
-const util = require('util');
-const log = require('log-driver').logger;
-
-const dpts = {};
-for (const entry of fs.readdirSync(__dirname)) {
- const matches = entry.match(/(dpt.*)\.js/);
- if (!matches) continue;
- const dptid = matches[1].toUpperCase(); // DPT1..DPTxxx
- const mod = require(__dirname + path.sep + entry);
- if (
- !mod.hasOwnProperty('basetype') ||
- !mod.basetype.hasOwnProperty('bitlength')
- )
- throw 'incomplete ' + dptid + ', missing basetype and/or bitlength!';
- mod.id = dptid;
- dpts[dptid] = mod;
- //log.trace('DPT library: loaded %s (%s)', dptid, dpts[dptid].basetype.desc);
-}
-
-// a generic DPT resolution function
-// DPTs might come in as 9/"9"/"9.001"/"DPT9.001"
-dpts.resolve = (dptid) => {
- const m = dptid
- .toString()
- .toUpperCase()
- .match(/^(?:DPT)?(\d+)(\.(\d+))?$/);
- if (m === null) throw 'Invalid DPT format: ' + dptid;
-
- const dpt = dpts[util.format('DPT%s', m[1])];
- if (!dpt) throw 'Unsupported DPT: ' + dptid;
-
- const cloned_dpt = cloneDpt(dpt);
- if (m[3]) {
- cloned_dpt.subtypeid = m[3];
- cloned_dpt.subtype = cloned_dpt.subtypes[m[3]];
- }
-
- return cloned_dpt;
-};
-
-/* POPULATE an APDU object from a given Javascript value for the given DPT
- * - either by a custom DPT formatAPDU function
- * - or by this generic version, which:
- * -- 1) checks if the value adheres to the range set from the DPT's bitlength
- *
- */
-dpts.populateAPDU = (value, apdu, dptid) => {
- const dpt = dpts.resolve(dptid || 'DPT1');
- const nbytes = Math.ceil(dpt.basetype.bitlength / 8);
- apdu.data = Buffer.alloc(nbytes);
- apdu.bitlength = (dpt.basetype && dpt.basetype.bitlength) || 1;
- let tgtvalue = value;
- // get the raw APDU data for the given JS value
- if (typeof dpt.formatAPDU == 'function') {
- // nothing to do here, DPT-specific formatAPDU implementation will handle everything
- // log.trace('>>> custom formatAPDU(%s): %j', dptid, value);
- apdu.data = dpt.formatAPDU(value);
- // log.trace('<<< custom formatAPDU(%s): %j', dptid, apdu.data);
- return apdu;
- }
-
- if (!isFinite(value))
- throw util.format('Invalid value, expected a %s', dpt.desc);
- // check if value is in range, be it explicitly defined or implied from bitlength
- const [r_min, r_max] = dpt.basetype.hasOwnProperty('range')
- ? dpt.basetype.range
- : [0, Math.pow(2, dpt.basetype.bitlength) - 1]; // TODO: Maybe bitshift instead of pow?
- // is there a scalar range? eg. DPT5.003 angle degrees (0=0, ff=360)
- if (
- dpt.hasOwnProperty('subtype') &&
- dpt.subtype.hasOwnProperty('scalar_range')
- ) {
- const [s_min, s_max] = dpt.subtype.scalar_range;
- if (value < s_min || value > s_max) {
- log.trace(
- 'Value %j(%s) out of scalar range(%j) for %s',
- value,
- typeof value,
- scalar,
- dpt.id
- );
- } else {
- // convert value from its scalar representation
- // e.g. in DPT5.001, 50(%) => 0x7F , 100(%) => 0xFF
- const a = (s_max - s_min) / (r_max - r_min);
- const b = s_min - r_min;
- tgtvalue = Math.round((value - b) / a);
- }
- }
- // just a plain numeric value, only check if within bounds
- else if (value < r_min || value > r_max) {
- log.trace(
- 'Value %j(%s) out of bounds(%j) for %s.%s',
- value,
- typeof value,
- range,
- dpt.id,
- dpt.subtypeid
- );
- }
-
- // generic APDU is assumed to convey an unsigned integer of arbitrary bitlength
- if (
- dpt.basetype.hasOwnProperty('signedness') &&
- dpt.basetype.signedness == 'signed'
- ) {
- apdu.data.writeIntBE(tgtvalue, 0, nbytes);
- } else {
- apdu.data.writeUIntBE(tgtvalue, 0, nbytes);
- }
-
- // log.trace('generic populateAPDU tgtvalue=%j(%s) nbytes=%d => apdu=%j', tgtvalue, typeof tgtvalue, nbytes, apdu);
-};
-
-/* get the correct Javascript value from a APDU buffer for the given DPT
- * - either by a custom DPT formatAPDU function
- * - or by this generic version, which:
- * -- 1) checks if the value adheres to the range set from the DPT's bitlength
- */
-dpts.fromBuffer = (buf, dpt) => {
- // sanity check
- if (!dpt) throw util.format('DPT %s not found', dpt);
- // get the raw APDU data for the given JS value
- if (typeof dpt.fromBuffer == 'function') {
- // nothing to do here, DPT-specific fromBuffer implementation will handle everything
- return dpt.fromBuffer(buf);
- }
- // log.trace('%s buflength == %d => %j', typeof buf, buf.length, JSON.stringify(buf) );
- if (buf.length > 6) {
- throw 'cannot handle unsigned integers more then 6 bytes in length';
- }
- let value = 0;
- if (
- dpt.basetype.hasOwnProperty('signedness') &&
- dpt.basetype.signedness == 'signed'
- )
- value = buf.readIntBE(0, buf.length);
- else value = buf.readUIntBE(0, buf.length);
-
- // log.trace(' ../knx/src/index.js : DPT : ' + JSON.stringify(dpt)); // for exploring dpt and implementing description
- if (
- dpt.hasOwnProperty('subtype') &&
- dpt.subtype.hasOwnProperty('scalar_range')
- ) {
- const [r_min, r_max] = dpt.basetype.hasOwnProperty('range')
- ? dpt.basetype.range
- : [0, Math.pow(2, dpt.basetype.bitlength) - 1];
- const [s_min, s_max] = dpt.subtype.scalar_range;
- // convert value from its scalar representation
- // e.g. in DPT5.001, 50(%) => 0x7F , 100(%) => 0xFF
- const a = (s_max - s_min) / (r_max - r_min);
- const b = s_min - r_min;
- value = Math.round(a * value + b);
- //log.trace('fromBuffer scalar a=%j b=%j %j', a,b, value);
- }
-
- // log.trace('generic fromBuffer buf=%j, value=%j', buf, value);
- return value;
-};
-
-const cloneDpt = (d) => {
- const { fromBuffer, formatAPDU } = d;
- return { ...JSON.parse(JSON.stringify(d)), fromBuffer, formatAPDU };
-};
-
-module.exports = dpts;
diff --git a/src/dptlib/index.ts b/src/dptlib/index.ts
new file mode 100644
index 0000000..bf915f4
--- /dev/null
+++ b/src/dptlib/index.ts
@@ -0,0 +1,262 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+/*
+Datatypes
+=========
+KNX/EIB Function Information length EIS DPT Value
+Switch 1 Bit EIS 1 DPT 1 0,1
+Dimming (Position, Control, Value) 1 Bit, 4 Bit, 8 Bit EIS 2 DPT 3 [0,0]...[1,7]
+Time 3 Byte EIS 3 DPT 10
+Date 3 Byte EIS 4 DPT 11
+Floating point 2 Byte EIS 5 DPT 9 -671088,64 - 670760,96
+8-bit unsigned value 1 Byte EIS 6 DPT 5 0...255
+8-bit unsigned value 1 Byte DPT 5.001 DPT 5.001 0...100
+Blinds / Roller shutter 1 Bit EIS 7 DPT 1 0,1
+Priority 2 Bit EIS 8 DPT 2 [0,0]...[1,1]
+IEEE Floating point 4 Byte EIS 9 DPT 14 4-Octet Float Value IEEE 754
+16-bit unsigned value 2 Byte EIS 10 DPT 7 0...65535
+16-bit signed value 2 Byte DPT 8 DPT 8 -32768...32767
+32-bit unsigned value 4 Byte EIS 11 DPT 12 0...4294967295
+32-bit signed value 4 Byte DPT 13 DPT 13 -2147483648...2147483647
+Access control 1 Byte EIS 12 DPT 15
+ASCII character 1 Byte EIS 13 DPT 4
+8859_1 character 1 Byte DPT 4.002 DPT 4.002
+8-bit signed value 1 Byte EIS 14 DPT 6 -128...127
+14 character ASCII 14 Byte EIS 15 DPT 16
+14 character 8859_1 14 Byte DPT 16.001 DPT 16.001
+Scene 1 Byte DPT 17 DPT 17 0...63
+HVAC 1 Byte DPT 20 DPT 20 0..255
+Unlimited string 8859_1 . DPT 24 DPT 24
+List 3-byte value 3 Byte DPT 232 DPT 232 RGB[0,0,0]...[255,255,255]
+*/
+import * as util from 'util'
+import KnxLog from '../KnxLog'
+
+import DPT1 from './dpt1'
+import DPT2 from './dpt2'
+import DPT3 from './dpt3'
+import DPT4 from './dpt4'
+import DPT5 from './dpt5'
+import DPT6 from './dpt6'
+import DPT7 from './dpt7'
+import DPT8 from './dpt8'
+import DPT9 from './dpt9'
+import DPT10 from './dpt10'
+import DPT11 from './dpt11'
+import DPT12 from './dpt12'
+import DPT13 from './dpt13'
+import DPT14 from './dpt14'
+import DPT15 from './dpt15'
+import DPT16 from './dpt16'
+import DPT17 from './dpt17'
+import DPT18 from './dpt18'
+import DPT19 from './dpt19'
+import DPT20 from './dpt20'
+import DPT21 from './dpt21'
+import DPT22 from './dpt22'
+import DPT28 from './dpt28'
+import DPT29 from './dpt29'
+import DPT213 from './dpt213'
+import DPT232 from './dpt232'
+import DPT235 from './dpt235'
+import DPT237 from './dpt237'
+import DPT238 from './dpt238'
+import DPT242 from './dpt242'
+import DPT249 from './dpt249'
+import DPT251 from './dpt251'
+import DPT275 from './dpt275'
+import DPT999 from './dpt999'
+import DPT6001 from './dpt60001'
+
+import type { Datagram } from '../KnxClient'
+import { hasProp } from '../utils'
+
+interface DatapointSubtype {
+ scalar_range?: [number, number]
+ name: string
+ use?: string
+ desc: string
+ force_encoding?: string
+ unit?: string
+ enc?: Record
+ range?: [number, number] | [undefined, undefined]
+}
+
+export interface DatapointConfig {
+ id: string
+ subtypeid?: string
+ basetype: {
+ bitlength: number
+ signedness?: string
+ range?: [number, number]
+ valuetype: string
+ desc?: string
+ }
+ subtype?: DatapointSubtype
+ subtypes?: Record
+ formatAPDU?: (value: any) => Buffer | void
+ fromBuffer?: (buf: Buffer) => any
+}
+
+const dpts: Record = {
+ [DPT1.id]: DPT1,
+ [DPT2.id]: DPT2,
+ [DPT3.id]: DPT3,
+ [DPT4.id]: DPT4,
+ [DPT5.id]: DPT5,
+ [DPT6.id]: DPT6,
+ [DPT7.id]: DPT7,
+ [DPT8.id]: DPT8,
+ [DPT9.id]: DPT9,
+ [DPT10.id]: DPT10,
+ [DPT11.id]: DPT11,
+ [DPT12.id]: DPT12,
+ [DPT13.id]: DPT13,
+ [DPT14.id]: DPT14,
+ [DPT15.id]: DPT15,
+ [DPT16.id]: DPT16,
+ [DPT17.id]: DPT17,
+ [DPT18.id]: DPT18,
+ [DPT19.id]: DPT19,
+ [DPT20.id]: DPT20,
+ [DPT21.id]: DPT21,
+ [DPT22.id]: DPT22,
+ [DPT28.id]: DPT28,
+ [DPT29.id]: DPT29,
+ [DPT213.id]: DPT213,
+ [DPT232.id]: DPT232,
+ [DPT235.id]: DPT235,
+ [DPT237.id]: DPT237,
+ [DPT238.id]: DPT238,
+ [DPT242.id]: DPT242,
+ [DPT249.id]: DPT249,
+ [DPT251.id]: DPT251,
+ [DPT275.id]: DPT275,
+ [DPT999.id]: DPT999,
+ [DPT6001.id]: DPT6001,
+}
+
+export function resolve(dptid: string | number): DatapointConfig {
+ const m = dptid
+ .toString()
+ .toUpperCase()
+ .match(/^(?:DPT)?(\d+)(\.(\d+))?$/)
+ if (m === null) throw Error(`Invalid DPT format: ${dptid}`)
+
+ const dptkey = util.format('DPT%s', m[1])
+ const dpt = dpts[dptkey]
+ if (!dpt) throw Error(`Unsupported DPT: ${dptid}`)
+
+ const cloned_dpt = cloneDpt(dpt)
+ if (m[3]) {
+ cloned_dpt.subtypeid = m[3]
+ cloned_dpt.subtype = cloned_dpt.subtypes[m[3]]
+ }
+
+ return cloned_dpt
+}
+
+export function populateAPDU(
+ value: any,
+ apdu: Datagram['cemi']['apdu'],
+ dptid?: number | string,
+) {
+ const dpt = resolve(dptid || 'DPT1')
+ const nbytes = Math.ceil(dpt.basetype.bitlength / 8)
+ apdu.data = Buffer.alloc(nbytes)
+ apdu.bitlength = (dpt.basetype && dpt.basetype.bitlength) || 1
+ let tgtvalue = value
+
+ if (typeof dpt.formatAPDU === 'function') {
+ apdu.data = dpt.formatAPDU(value)
+ return apdu
+ }
+
+ if (!isFinite(value))
+ throw Error(
+ util.format('Invalid value, expected a %s', dpt.basetype.desc),
+ )
+
+ const [r_min, r_max] = hasProp(dpt.basetype, 'range')
+ ? dpt.basetype.range
+ : [0, 2 ** dpt.basetype.bitlength - 1]
+
+ if (hasProp(dpt, 'subtype') && hasProp(dpt.subtype, 'scalar_range')) {
+ const [s_min, s_max] = dpt.subtype.scalar_range
+ if (value < s_min || value > s_max) {
+ KnxLog.get().trace(
+ 'Value %j(%s) out of scalar range(%j) for %s',
+ value,
+ typeof value,
+ dpt.subtype.scalar_range,
+ dpt.id,
+ )
+ } else {
+ const a = (s_max - s_min) / (r_max - r_min)
+ const b = s_min - r_min
+ tgtvalue = Math.round((value - b) / a)
+ }
+ } else if (value < r_min || value > r_max) {
+ KnxLog.get().trace(
+ 'Value %j(%s) out of bounds(%j) for %s.%s',
+ value,
+ typeof value,
+ dpt.subtype.scalar_range,
+ dpt.id,
+ dpt.subtypeid,
+ )
+ }
+
+ if (
+ hasProp(dpt.basetype, 'signedness') &&
+ dpt.basetype.signedness === 'signed'
+ ) {
+ apdu.data.writeIntBE(tgtvalue, 0, nbytes)
+ } else {
+ apdu.data.writeUIntBE(tgtvalue, 0, nbytes)
+ }
+}
+
+export function fromBuffer(buf: Buffer, dpt: DatapointConfig) {
+ if (!dpt) throw Error(util.format('DPT %s not found', dpt))
+
+ if (typeof dpt.fromBuffer === 'function') {
+ return dpt.fromBuffer(buf)
+ }
+
+ if (buf.length > 6) {
+ throw Error(
+ 'cannot handle unsigned integers more then 6 bytes in length',
+ )
+ }
+
+ let value = 0
+ if (
+ hasProp(dpt.basetype, 'signedness') &&
+ dpt.basetype.signedness === 'signed'
+ )
+ value = buf.readIntBE(0, buf.length)
+ else value = buf.readUIntBE(0, buf.length)
+
+ if (hasProp(dpt, 'subtype') && hasProp(dpt.subtype, 'scalar_range')) {
+ const [r_min, r_max] = hasProp(dpt.subtype, 'range')
+ ? dpt.basetype.range
+ : [0, 2 ** dpt.basetype.bitlength - 1]
+ const [s_min, s_max] = dpt.subtype.scalar_range
+ const a = (s_max - s_min) / (r_max - r_min)
+ const b = s_min - r_min
+ value = Math.round(a * value + b)
+ }
+
+ return value
+}
+
+const cloneDpt = (d: DatapointConfig) => {
+ const { fromBuffer: fb, formatAPDU: fa } = d
+ return { ...JSON.parse(JSON.stringify(d)), fromBuffer: fb, formatAPDU: fa }
+}
+
+export default dpts
diff --git a/src/index.ts b/src/index.ts
new file mode 100644
index 0000000..496ea18
--- /dev/null
+++ b/src/index.ts
@@ -0,0 +1,12 @@
+/**
+ * knx.ts - a KNX protocol stack in pure Typescript
+ * (C) 2016-2017 Elias Karakoulakis
+ */
+
+import { KnxClient } from './KnxClient'
+import Datapoint from './Datapoint'
+import Devices from './devices'
+import Log from './KnxLog'
+import dpts from './dptlib'
+
+export { KnxClient, Datapoint, Devices, Log, dpts }
diff --git a/src/types/binary-protocol.d.ts b/src/types/binary-protocol.d.ts
new file mode 100644
index 0000000..0468390
--- /dev/null
+++ b/src/types/binary-protocol.d.ts
@@ -0,0 +1,59 @@
+import { Duplex } from 'stream'
+
+declare module 'binary-protocol' {
+ interface ProtocolConfig {
+ read(this: Reader, propertyName: string): void
+ write(this: Writer, value: any): void
+ }
+
+ interface Reader {
+ define(name: string, config: ProtocolConfig): this
+ clone(): this
+ demand(howMany: number): this
+ tap(callback: (value: any) => void): this
+ enqueue(name: string, arg1: any, arg2: any): this
+ prepend(fn: (arg: any) => void, arg1: any): this
+ /** Allocate a new object to read the data into */
+ pushStack(item: any): this
+ /** Pop the interim value off the stack and insert the real value into `property` */
+ popStack(property: string, fn: (value: any) => void): this
+ /** Collect the final data to return */
+ collect(property: string, fn: (value: any) => void): this
+ loop(property: string, fn: (value: any) => void): this
+ end(fn: () => void): this
+ finally(fn: () => void): this
+ reset(): this
+ raw(property: string, length: number): this
+ next(chunk?: any): any
+ process(): any
+ createLooper(property: string, fn: (value: any) => void): this
+ [key: string]: (property: string) => this // method created with `define`
+ }
+
+ interface Writer {
+ buffer: Buffer
+ define(name: string, config: ProtocolConfig): this
+ clone(): this
+ allocate(howMany: number): this
+ raw(buffer: Buffer): this
+ forward(howMany: number): this
+ tap(callback: (value: any) => void): this
+ [key: string]: (data: any) => this // method created with `define`
+ }
+
+ interface Commander {
+ define(name: string, config: ProtocolConfig): this
+ clone(): this
+ createReadStream(options: any): Reader
+ createWriteStream(options: any): Writer
+ [key: string]: (data: any) => Promise // method created with `define`
+ }
+
+ export default class BinaryProtocol {
+ define(name: string, config: ProtocolConfig): this
+ createReader(buffer: Buffer, offset?: number): Reader
+ createWriter(buffer?: Buffer, offset?: number): Writer
+ createCommander(duplex: Duplex): Commander
+ [key: string]: any // method created with `define`
+ }
+}
diff --git a/src/types/log-driver.d.ts b/src/types/log-driver.d.ts
new file mode 100644
index 0000000..57b2336
--- /dev/null
+++ b/src/types/log-driver.d.ts
@@ -0,0 +1,21 @@
+declare module 'log-driver' {
+ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error'
+
+ export interface LogDriverOptions {
+ level: LogLevel
+ format: (level: LogLevel, msg: string, ...args: any[]) => string
+ }
+
+ export interface Logger {
+ trace: (...args: any[]) => void
+ debug: (...args: any[]) => void
+ info: (...args: any[]) => void
+ warn: (...args: any[]) => void
+ error: (...args: any[]) => void
+ format: LogDriverOptions['format']
+ }
+
+ export default function factory(options: LogDriverOptions): Logger
+
+ export const logger: Logger
+}
diff --git a/src/types/machina.d.ts b/src/types/machina.d.ts
new file mode 100644
index 0000000..3172018
--- /dev/null
+++ b/src/types/machina.d.ts
@@ -0,0 +1,63 @@
+import EventEmitter from 'events'
+
+declare module 'machina' {
+ export interface States {
+ /** It's the "catch-all" handler which, if provided, will match any input in that state that's not explicitly matched by name */
+ '*'?: () => void
+
+ _onEnter?: () => void
+
+ timeout?: string
+
+ _reset?: string
+
+ _onExit?: () => void
+
+ [key: string]: {
+ [key: string]: () => void
+ }
+ }
+
+ export interface Options {
+ initialState: string
+ states: Record
+ namespace?: string
+ initialize?: () => void
+ }
+
+ export class BehavioralFsm extends EventEmitter {
+ initialState: string
+
+ namespace: string
+
+ states: any // Record;
+
+ state: string
+
+ static extend(
+ protoProps: Partial,
+ staticProps?: any,
+ ): typeof BehavioralFsm
+ compositeState(client: BehavioralFsm): any
+ clearQueue(client: BehavioralFsm, name?: string): void
+ handle(client: BehavioralFsm, ...args: any[]): any
+ transition(
+ client: BehavioralFsm,
+ newState: string,
+ ...args: any[]
+ ): void
+ deferUntilTransition(client: BehavioralFsm, state: string): void
+ initialize(...args: any): void
+ processQueue(client: BehavioralFsm): void
+ }
+
+ export class Fsm extends BehavioralFsm {
+ static extend(protoProps: Partial, staticProps?: any): typeof Fsm
+ compositeState(): any
+ clearQueue(name?: string): void
+ handle(...args: any[]): any
+ transition(newState: string, ...args: any[]): void
+ deferUntilTransition(state: string): void
+ processQueue(): void
+ }
+}
diff --git a/src/utils.ts b/src/utils.ts
new file mode 100644
index 0000000..b66e422
--- /dev/null
+++ b/src/utils.ts
@@ -0,0 +1,42 @@
+// eslint-disable-next-line import/prefer-default-export
+export function hasProp(obj: any, prop: string): boolean {
+ return Object.prototype.hasOwnProperty.call(obj, prop)
+}
+
+export function hex2bin(hex: string) {
+ return parseInt(hex, 16).toString(2).padStart(8, '0')
+}
+
+export function hexToDec(hex: string) {
+ let result = 0
+ let digitValue: number
+ hex = hex.toLowerCase()
+ for (let i = 0; i < hex.length; i++) {
+ digitValue = '0123456789abcdefgh'.indexOf(hex[i])
+ result = result * 16 + digitValue
+ }
+ return result
+}
+
+export function ldexp(mantissa: number, exponent: number) {
+ // eslint-disable-next-line no-nested-ternary
+ return exponent > 1023 // avoid multiplying by infinity
+ ? mantissa * 2 ** 1023 * 2 ** (exponent - 1023)
+ : exponent < -1074 // avoid multiplying by zero
+ ? mantissa * 2 ** -1074 * 2 ** (exponent + 1074)
+ : mantissa * 2 ** exponent
+}
+
+export function frexp(value: number) {
+ if (value === 0) return [value, 0]
+ const data = new DataView(new ArrayBuffer(8))
+ data.setFloat64(0, value)
+ let bits = (data.getUint32(0) >>> 20) & 0x7ff
+ if (bits === 0) {
+ data.setFloat64(0, value * 2 ** 64)
+ bits = ((data.getUint32(0) >>> 20) & 0x7ff) - 64
+ }
+ const exponent = bits - 1022
+ const mantissa = ldexp(value, -exponent)
+ return [mantissa, exponent]
+}
diff --git a/test/connection/test-connect-routing.js b/test/connection/test-connect-routing.js
deleted file mode 100644
index fbc7182..0000000
--- a/test/connection/test-connect-routing.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-Error.stackTraceLimit = Infinity;
-
-const knx = require('../..');
-const address = require('../../src/Address.js');
-const assert = require('assert');
-const test = require('tape');
-
-//
-test('KNX connect routing', function(t) {
- var connection = knx.Connection({
- loglevel: 'trace',
- handlers: {
- connected: function() {
- console.log('----------');
- console.log('Connected!');
- console.log('----------');
- t.pass('connected in routing mode');
- t.end();
- process.exit(0);
- },
- error: function() {
- t.fail('error connecting');
- t.end();
- process.exit(1);
- }
- }
- });
-});
-
-setTimeout(function() {
- console.log('Exiting with timeout...');
- process.exit(2);
-}, 1000);
diff --git a/test/connection/test-connect-routing.ts b/test/connection/test-connect-routing.ts
new file mode 100644
index 0000000..4282480
--- /dev/null
+++ b/test/connection/test-connect-routing.ts
@@ -0,0 +1,36 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { KnxClient } from '../../src'
+import test from 'tape'
+
+Error.stackTraceLimit = Infinity
+
+//
+test('KNX connect routing', function (t) {
+ const connection = new KnxClient({
+ loglevel: 'trace',
+ handlers: {
+ connected() {
+ console.log('----------')
+ console.log('Connected!')
+ console.log('----------')
+ t.pass('connected in routing mode')
+ t.end()
+ process.exit(0)
+ },
+ error() {
+ t.fail('error connecting')
+ t.end()
+ process.exit(1)
+ },
+ },
+ })
+})
+
+setTimeout(function () {
+ console.log('Exiting with timeout...')
+ process.exit(2)
+}, 1000)
diff --git a/test/dptlib/commontest.js b/test/dptlib/commontest.js
deleted file mode 100644
index c900a4e..0000000
--- a/test/dptlib/commontest.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const test = require('tape');
-const DPTLib = require('../../src/dptlib');
-const assert = require('assert');
-
-/* common DPT unit test. Tries to
-- 1. marshal a JS value into an apdu.data (Buffer) and compare it output to the expected value
-- 2. unmarshal the produced APDU from step 1 and compare it to the initial JS value
-- 3. unmarshal the expected APDU from the test definition and compare it to the initial JS value
-*/
-
-// marshalling test (JS value to APDU)
-function marshalTest(t, dptid, jsval, apdu) {
- var marshalled = {};
- DPTLib.populateAPDU(jsval, marshalled, dptid);
- // console.log('%j --> %j', apdu.constructor.name, marshalled.data)
- t.deepEqual(marshalled.data, apdu,
- `${dptid}.populateAPDU(${jsval}:${typeof jsval}) should be marshalled as \"0x${apdu.toString('hex')}\", got: \"0x${marshalled.data.toString('hex')}\"`
- );
- return marshalled.data;
-};
-
-function unmarshalTest(t, dptid, jsval, data) {
- var dpt = DPTLib.resolve(dptid);
- var unmarshalled = DPTLib.fromBuffer(data, dpt);
- //console.log('%s: %j --> %j', dpt.id, rhs, converted);
- var msg = `${dptid}.fromBuffer(${JSON.stringify(data)}) should unmarshall to ${JSON.stringify(jsval)}, got: ${JSON.stringify(unmarshalled)}`
- switch (typeof jsval) {
- case 'object':
- t.deepEqual(unmarshalled, jsval, msg);
- break;
- case 'number':
- t.equal(unmarshalled, jsval, msg);
- break;
- default:
- t.ok(unmarshalled == jsval, msg);
- }
-};
-
-module.exports = {
- do: function(dptid, tests) {
- var dpt = DPTLib.resolve(dptid);
- var desc = (dpt.hasOwnProperty('subtype') && dpt.subtype.desc) || dpt.basetype.desc;
- test(`${dptid}: ${desc}`, function(t) {
- Object.keys(tests).forEach(function(key) {
- var apdu = new Buffer(tests[key].apdu_data);
- var jsval = tests[key].jsval;
- //console.log(dptid + ': apdu=%j jsval=%j', apdu, jsval);
- // 1. marshalling test (JS value to APDU)
- var marshalled_data = marshalTest(t, dptid, jsval, apdu);
- // 2. unmarshal from APDU produced by step 1
- unmarshalTest(t, dptid, jsval, marshalled_data);
- // 3. unmarshal from test APDU
- unmarshalTest(t, dptid, jsval, apdu);
- });
- t.end();
- });
- }
-}
diff --git a/test/dptlib/commontest.ts b/test/dptlib/commontest.ts
new file mode 100644
index 0000000..b6b9b6f
--- /dev/null
+++ b/test/dptlib/commontest.ts
@@ -0,0 +1,75 @@
+/* eslint-disable no-prototype-builtins */
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import test, { Test } from 'tape'
+import DPTLib, { fromBuffer, populateAPDU, resolve } from '../../src/dptlib'
+import { Datagram } from '../../src/KnxClient'
+
+/* common DPT unit test. Tries to
+- 1. marshal a JS value into an apdu.data (Buffer) and compare it output to the expected value
+- 2. unmarshal the produced APDU from step 1 and compare it to the initial JS value
+- 3. unmarshal the expected APDU from the test definition and compare it to the initial JS value
+*/
+
+// marshalling test (JS value to APDU)
+function marshalTest(t: Test, dptid: string | number, jsval: any, apdu: any) {
+ const marshalled = {} as Datagram['cemi']['apdu']
+ populateAPDU(jsval, marshalled, dptid)
+ // console.log('%j --> %j', apdu.constructor.name, marshalled.data)
+ t.deepEqual(
+ marshalled.data,
+ apdu,
+ `${dptid}.populateAPDU(${jsval}:${typeof jsval}) should be marshalled as "0x${apdu.toString(
+ 'hex',
+ )}", got: "0x${marshalled.data.toString('hex')}"`,
+ )
+ return marshalled.data
+}
+
+function unmarshalTest(t, dptid, jsval, data) {
+ const dpt = resolve(dptid)
+ const unmarshalled = fromBuffer(data, dpt)
+ // console.log('%s: %j --> %j', dpt.id, rhs, converted);
+ const msg = `${dptid}.fromBuffer(${JSON.stringify(
+ data,
+ )}) should unmarshall to ${JSON.stringify(jsval)}, got: ${JSON.stringify(
+ unmarshalled,
+ )}`
+ switch (typeof jsval) {
+ case 'object':
+ t.deepEqual(unmarshalled, jsval, msg)
+ break
+ case 'number':
+ t.equal(unmarshalled, jsval, msg)
+ break
+ default:
+ t.ok(unmarshalled === jsval, msg)
+ }
+}
+
+// eslint-disable-next-line import/prefer-default-export
+export function run(
+ dptid: number | string,
+ tests: { apdu_data: number[]; jsval: any }[],
+) {
+ const dpt = resolve(dptid)
+ const desc =
+ (dpt.hasOwnProperty('subtype') && dpt.subtype.desc) || dpt.basetype.desc
+ test(`${dptid}: ${desc}`, function (t) {
+ Object.keys(tests).forEach(function (key) {
+ const apdu = Buffer.from(tests[key].apdu_data)
+ const jsval = tests[key].jsval
+ // console.log(dptid + ': apdu=%j jsval=%j', apdu, jsval);
+ // 1. marshalling test (JS value to APDU)
+ const marshalled_data = marshalTest(t, dptid, jsval, apdu)
+ // 2. unmarshal from APDU produced by step 1
+ unmarshalTest(t, dptid, jsval, marshalled_data)
+ // 3. unmarshal from test APDU
+ unmarshalTest(t, dptid, jsval, apdu)
+ })
+ t.end()
+ })
+}
diff --git a/test/dptlib/test-dpt.js b/test/dptlib/test-dpt.js
deleted file mode 100644
index 2d1249c..0000000
--- a/test/dptlib/test-dpt.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const test = require('tape');
-const DPTLib = require('../../src/dptlib');
-const assert = require('assert');
-
-test('resolve', function(t) {
- t.throws(() => {
- DPTLib.resolve('invalid input');
- }, /Invalid DPT format: .*/, 'Invalid format of a DPT');
-
- t.throws(() => {
- DPTLib.resolve({dpt: 9});
- }, /Invalid DPT format: .*/, 'Invalid format of a DPT');
-
- t.throws(() => {
- DPTLib.resolve([9,9]);
- }, /Invalid DPT format: .*/, 'Invalid format of a DPT');
-
- t.throws(() => {
- DPTLib.resolve('29.010');
- }, /Unsupported DPT: .*/, 'Unsupported/unknown DPT');
-
- t.throws(() => {
- DPTLib.resolve(29);
- }, /Unsupported DPT: .*/, 'Unsupported/unknown Int DPT');
-
- t.throws(() => {
- DPTLib.resolve([29]);
- }, /Unsupported DPT: .*/, 'Unsupported/unknown Int DPT');
-
- var d0 = DPTLib.resolve(1)
- t.equal(d0.id, 'DPT1')
- t.equal(d0.subtypeid, undefined)
-
- var d1 = DPTLib.resolve('DPT9')
- t.equal(d1.id, 'DPT9')
- t.equal(d1.subtypeid, undefined)
-
- var d2 = DPTLib.resolve('DPT1.002')
- t.equal(d2.id, 'DPT1')
- t.equal(d2.subtypeid, '002')
-
- var d3 = DPTLib.resolve('DPT1.001')
- t.equal(d3.id, 'DPT1')
- t.equal(d3.subtypeid, '001')
-
- // Check that dpts are not destroyed by subsequent calls to resolve
- t.equal(d2.id, 'DPT1')
- t.equal(d2.subtypeid, '002')
-
- var d4 = DPTLib.resolve('1.002')
- t.equal(d4.id, 'DPT1')
- t.equal(d4.subtypeid, '002')
-
- t.end()
-})
diff --git a/test/dptlib/test-dpt.ts b/test/dptlib/test-dpt.ts
new file mode 100644
index 0000000..84e1d47
--- /dev/null
+++ b/test/dptlib/test-dpt.ts
@@ -0,0 +1,83 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import test from 'tape'
+import { resolve } from '../../src/dptlib'
+
+test('resolve', function (t) {
+ t.throws(
+ () => {
+ resolve('invalid input')
+ },
+ /Invalid DPT format: .*/,
+ 'Invalid format of a DPT',
+ )
+
+ t.throws(
+ () => {
+ resolve({ dpt: 9 } as any)
+ },
+ /Invalid DPT format: .*/,
+ 'Invalid format of a DPT',
+ )
+
+ t.throws(
+ () => {
+ resolve([9, 9] as any)
+ },
+ /Invalid DPT format: .*/,
+ 'Invalid format of a DPT',
+ )
+
+ t.throws(
+ () => {
+ resolve('1111111.010')
+ },
+ /Unsupported DPT: .*/,
+ 'Unsupported/unknown DPT',
+ )
+
+ t.throws(
+ () => {
+ resolve(1111111)
+ },
+ /Unsupported DPT: .*/,
+ 'Unsupported/unknown Int DPT',
+ )
+
+ t.throws(
+ () => {
+ resolve([1111111] as any)
+ },
+ /Unsupported DPT: .*/,
+ 'Unsupported/unknown Int DPT',
+ )
+
+ const d0 = resolve(1)
+ t.equal(d0.id, 'DPT1')
+ t.equal(d0.subtypeid, undefined)
+
+ const d1 = resolve('DPT9')
+ t.equal(d1.id, 'DPT9')
+ t.equal(d1.subtypeid, undefined)
+
+ const d2 = resolve('DPT1.002')
+ t.equal(d2.id, 'DPT1')
+ t.equal(d2.subtypeid, '002')
+
+ const d3 = resolve('DPT1.001')
+ t.equal(d3.id, 'DPT1')
+ t.equal(d3.subtypeid, '001')
+
+ // Check that dpts are not destroyed by subsequent calls to resolve
+ t.equal(d2.id, 'DPT1')
+ t.equal(d2.subtypeid, '002')
+
+ const d4 = resolve('1.002')
+ t.equal(d4.id, 'DPT1')
+ t.equal(d4.subtypeid, '002')
+
+ t.end()
+})
diff --git a/test/dptlib/test-dpt1.js b/test/dptlib/test-dpt1.js
deleted file mode 100644
index 78dcbdf..0000000
--- a/test/dptlib/test-dpt1.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-require('./commontest').do('DPT1', [
- { apdu_data: [0x00], jsval: false},
- { apdu_data: [0x01], jsval: true}
-]);
diff --git a/test/dptlib/test-dpt1.ts b/test/dptlib/test-dpt1.ts
new file mode 100644
index 0000000..e141c85
--- /dev/null
+++ b/test/dptlib/test-dpt1.ts
@@ -0,0 +1,10 @@
+import { run } from './commontest'
+
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+run('DPT1', [
+ { apdu_data: [0x00], jsval: false },
+ { apdu_data: [0x01], jsval: true },
+])
diff --git a/test/dptlib/test-dpt10.js b/test/dptlib/test-dpt10.js
deleted file mode 100644
index 3276150..0000000
--- a/test/dptlib/test-dpt10.js
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const test = require('tape');
-const DPTLib = require('../../src/dptlib');
-const assert = require('assert');
-
-function timecompare(date1, sign, date2) {
- var dow1 = date1.getDay();
- var hour1 = date1.getHours();
- var min1 = date1.getMinutes();
- var sec1 = date1.getSeconds();
- var dow2 = date2.getDay();
- var hour2 = date2.getHours();
- var min2 = date2.getMinutes();
- var sec2 = date2.getSeconds();
- if (sign === '===') {
- if (dow1 == dow2 && hour1 === hour2 && min1 === min2 && sec1 === sec2) return true;
- else return false;
- } else if (sign === '>') {
- if (dow1 > dow2) return true;
- else if (dow1 == dow2 && hour1 > hour2) return true;
- else if (dow1 == dow2 && hour1 === hour2 && min1 > min2) return true;
- else if (dow1 == dow2 && hour1 === hour2 && min1 === min2 && sec1 > sec2) return true;
- else return false;
- }
-}
-
-test('DPT10 time conversion', function(t) {
-
- var tests = [
- ['DPT10', [(1<<5)+23, 15, 30], new Date('July 1, 2019 23:15:30')], // Monday
- ['DPT10', [(3<<5)+14, 55, 11], new Date('July 10, 2019 14:55:11')],// Wednesday
- ['DPT10', [(7<<5)+23, 15, 30], new Date('July 7, 2019 23:15:30')] // Sunday
- ];
- for (var i = 0; i < tests.length; i++) {
- var dpt = DPTLib.resolve(tests[i][0]);
- var buf = new Buffer(tests[i][1]);
- var val = tests[i][2];
-
- // unmarshalling test (raw data to value)
- var converted = DPTLib.fromBuffer(buf, dpt);
- t.ok(timecompare(converted, '===', val) ,
- `${tests[i][0]} fromBuffer value ${buf.toString('hex')} => expected ${val}, got ${converted}`);
-
- // marshalling test (value to raw data)
- var apdu = {};
- DPTLib.populateAPDU(val, apdu, 'dpt10');
- t.ok(Buffer.compare(buf, apdu.data) == 0,
- `${tests[i][0]} formatAPDU value ${val} => expected ${buf.toString('hex')}, got ${apdu.data.toString('hex')}`);
- }
- t.end()
-})
diff --git a/test/dptlib/test-dpt10.ts b/test/dptlib/test-dpt10.ts
new file mode 100644
index 0000000..24e408c
--- /dev/null
+++ b/test/dptlib/test-dpt10.ts
@@ -0,0 +1,61 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import test from 'tape'
+import { fromBuffer, populateAPDU, resolve } from '../../src/dptlib'
+import { Datagram } from '../../src/KnxClient'
+
+function timecompare(date1, sign, date2) {
+ const dow1 = date1.getDay()
+ const hour1 = date1.getHours()
+ const min1 = date1.getMinutes()
+ const sec1 = date1.getSeconds()
+ const dow2 = date2.getDay()
+ const hour2 = date2.getHours()
+ const min2 = date2.getMinutes()
+ const sec2 = date2.getSeconds()
+ if (sign === '===') {
+ if (dow1 === dow2 && hour1 === hour2 && min1 === min2 && sec1 === sec2)
+ return true
+ return false
+ }
+ if (sign === '>') {
+ if (dow1 > dow2) return true
+ if (dow1 === dow2 && hour1 > hour2) return true
+ if (dow1 === dow2 && hour1 === hour2 && min1 > min2) return true
+ if (dow1 === dow2 && hour1 === hour2 && min1 === min2 && sec1 > sec2)
+ return true
+ return false
+ }
+}
+
+test('DPT10 time conversion', function (t) {
+ const tests = [
+ ['DPT10', [(1 << 5) + 23, 15, 30], new Date('July 1, 2019 23:15:30')], // Monday
+ ['DPT10', [(3 << 5) + 14, 55, 11], new Date('July 10, 2019 14:55:11')], // Wednesday
+ ['DPT10', [(7 << 5) + 23, 15, 30], new Date('July 7, 2019 23:15:30')], // Sunday
+ ]
+ for (let i = 0; i < tests.length; i++) {
+ const dpt = resolve(tests[i][0] as string)
+ const buf = Buffer.from(tests[i][1] as number[])
+ const val = tests[i][2]
+
+ // unmarshalling test (raw data to value)
+ const converted = fromBuffer(buf, dpt)
+ t.ok(
+ timecompare(converted, '===', val),
+ `${tests[i][0]} fromBuffer value ${buf.toString('hex')} => expected ${val}, got ${converted}`,
+ )
+
+ // marshalling test (value to raw data)
+ const apdu = {} as Datagram['cemi']['apdu']
+ populateAPDU(val, apdu, 'dpt10')
+ t.ok(
+ Buffer.compare(buf, apdu.data) === 0,
+ `${tests[i][0]} formatAPDU value ${val} => expected ${buf.toString('hex')}, got ${apdu.data.toString('hex')}`,
+ )
+ }
+ t.end()
+})
diff --git a/test/dptlib/test-dpt11.js b/test/dptlib/test-dpt11.js
deleted file mode 100644
index 1b0feca..0000000
--- a/test/dptlib/test-dpt11.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const test = require('tape');
-const DPTLib = require('../../src/dptlib');
-const assert = require('assert');
-
-function dateequals(d1, d2) {
- var d = d1.getDate();
- var m = d1.getMonth();
- var y = d1.getFullYear();
- return (d == d2.getDate() && m == d2.getMonth() && y == d2.getFullYear());
-}
-
-test('DPT11 date conversion', function(t) {
- var tests = [
- ['DPT11', [25, 12, 95], new Date('1995-12-25')],
- ['DPT11', [0x19, 0x0C, 0x0F], new Date('2015-12-25')],
- ['DPT11', [0x16, 0x0B, 0x10], new Date('2016-11-22')],
- ['DPT11', [0x1B, 0x01, 0x13], new Date('2019-01-27')],
- ['DPT11', [0x03, 0x02, 0x13], new Date('2019-02-03')]
- ]
- for (var i = 0; i < tests.length; i++) {
- var dpt = DPTLib.resolve(tests[i][0]);
- var buf = new Buffer(tests[i][1]);
- var val = tests[i][2];
-
- // unmarshalling test (raw data to value)
- var converted = DPTLib.fromBuffer(buf, dpt);
- t.ok(dateequals(val, converted),
- `${tests[i][0]} fromBuffer value ${val} => ${JSON.stringify(converted)}`
- );
-
- // marshalling test (value to raw data)
- var apdu = {};
- DPTLib.populateAPDU(val, apdu, 'dpt11');
- t.ok(Buffer.compare(buf, apdu.data) == 0,
- `${tests[i][0]} formatAPDU value ${val} => ${JSON.stringify(converted)}`
- );
- }
- t.end()
-})
diff --git a/test/dptlib/test-dpt11.ts b/test/dptlib/test-dpt11.ts
new file mode 100644
index 0000000..60246d0
--- /dev/null
+++ b/test/dptlib/test-dpt11.ts
@@ -0,0 +1,46 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import test from 'tape'
+import { fromBuffer, populateAPDU, resolve } from '../../src/dptlib'
+import { Datagram } from '../../src/KnxClient'
+
+function dateequals(d1: Date, d2: Date) {
+ const d = d1.getDate()
+ const m = d1.getMonth()
+ const y = d1.getFullYear()
+ return d === d2.getDate() && m === d2.getMonth() && y === d2.getFullYear()
+}
+
+test('DPT11 date conversion', function (t) {
+ const tests = [
+ ['DPT11', [25, 12, 95], new Date('1995-12-25')],
+ ['DPT11', [0x19, 0x0c, 0x0f], new Date('2015-12-25')],
+ ['DPT11', [0x16, 0x0b, 0x10], new Date('2016-11-22')],
+ ['DPT11', [0x1b, 0x01, 0x13], new Date('2019-01-27')],
+ ['DPT11', [0x03, 0x02, 0x13], new Date('2019-02-03')],
+ ]
+ for (let i = 0; i < tests.length; i++) {
+ const dpt = resolve(tests[i][0] as string)
+ const buf = Buffer.from(tests[i][1] as number[])
+ const val = tests[i][2] as Date
+
+ // unmarshalling test (raw data to value)
+ const converted = fromBuffer(buf, dpt)
+ t.ok(
+ dateequals(val, converted),
+ `${tests[i][0]} fromBuffer value ${val} => ${JSON.stringify(converted)}`,
+ )
+
+ // marshalling test (value to raw data)
+ const apdu = {} as Datagram['cemi']['apdu']
+ populateAPDU(val, apdu, 'dpt11')
+ t.ok(
+ Buffer.compare(buf, apdu.data) === 0,
+ `${tests[i][0]} formatAPDU value ${val} => ${JSON.stringify(converted)}`,
+ )
+ }
+ t.end()
+})
diff --git a/test/dptlib/test-dpt12.js b/test/dptlib/test-dpt12.js
deleted file mode 100644
index e4795d8..0000000
--- a/test/dptlib/test-dpt12.js
+++ /dev/null
@@ -1,14 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-require('./commontest').do('DPT12', [
- { apdu_data: [0x00, 0x00, 0x00, 0x11], jsval: 17},
- { apdu_data: [0x00, 0x00, 0x01, 0x00], jsval: 256},
- { apdu_data: [0x00, 0x00, 0x10, 0x01], jsval: 4097},
- { apdu_data: [0x00, 0x00, 0xff, 0xff], jsval: 65535},
- { apdu_data: [0x00, 0x01, 0x00, 0x00], jsval: 65536},
- { apdu_data: [0x07, 0x5b, 0xcd, 0x15], jsval: 123456789},
- { apdu_data: [0x49, 0x96, 0x02, 0xd2], jsval: 1234567890},
-]);
diff --git a/test/dptlib/test-dpt12.ts b/test/dptlib/test-dpt12.ts
new file mode 100644
index 0000000..d1d8c29
--- /dev/null
+++ b/test/dptlib/test-dpt12.ts
@@ -0,0 +1,16 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { run } from './commontest'
+
+run('DPT12', [
+ { apdu_data: [0x00, 0x00, 0x00, 0x11], jsval: 17 },
+ { apdu_data: [0x00, 0x00, 0x01, 0x00], jsval: 256 },
+ { apdu_data: [0x00, 0x00, 0x10, 0x01], jsval: 4097 },
+ { apdu_data: [0x00, 0x00, 0xff, 0xff], jsval: 65535 },
+ { apdu_data: [0x00, 0x01, 0x00, 0x00], jsval: 65536 },
+ { apdu_data: [0x07, 0x5b, 0xcd, 0x15], jsval: 123456789 },
+ { apdu_data: [0x49, 0x96, 0x02, 0xd2], jsval: 1234567890 },
+])
diff --git a/test/dptlib/test-dpt13.js b/test/dptlib/test-dpt13.js
deleted file mode 100644
index 0008afe..0000000
--- a/test/dptlib/test-dpt13.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-require('./commontest').do('DPT13', [
- { apdu_data: [0x00, 0x00, 0x00, 0x11], jsval: 17},
- { apdu_data: [0x00, 0x00, 0x01, 0x00], jsval: 256},
- { apdu_data: [0x00, 0x00, 0x10, 0x00], jsval: 4096},
- { apdu_data: [0x00, 0x01, 0x00, 0x00], jsval: 65536},
- { apdu_data: [0x7f, 0xff, 0xff, 0xff], jsval: 2147483647},
- { apdu_data: [0x80, 0x00, 0x00, 0x00], jsval: -2147483648},
- { apdu_data: [0x80, 0x00, 0x00, 0x01], jsval: -2147483647},
- { apdu_data: [0xff, 0xff, 0xff, 0xff], jsval: -1},
-]);
diff --git a/test/dptlib/test-dpt13.ts b/test/dptlib/test-dpt13.ts
new file mode 100644
index 0000000..7a002ad
--- /dev/null
+++ b/test/dptlib/test-dpt13.ts
@@ -0,0 +1,17 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { run } from './commontest'
+
+run('DPT13', [
+ { apdu_data: [0x00, 0x00, 0x00, 0x11], jsval: 17 },
+ { apdu_data: [0x00, 0x00, 0x01, 0x00], jsval: 256 },
+ { apdu_data: [0x00, 0x00, 0x10, 0x00], jsval: 4096 },
+ { apdu_data: [0x00, 0x01, 0x00, 0x00], jsval: 65536 },
+ { apdu_data: [0x7f, 0xff, 0xff, 0xff], jsval: 2147483647 },
+ { apdu_data: [0x80, 0x00, 0x00, 0x00], jsval: -2147483648 },
+ { apdu_data: [0x80, 0x00, 0x00, 0x01], jsval: -2147483647 },
+ { apdu_data: [0xff, 0xff, 0xff, 0xff], jsval: -1 },
+])
diff --git a/test/dptlib/test-dpt19.js b/test/dptlib/test-dpt19.js
deleted file mode 100644
index a7808ae..0000000
--- a/test/dptlib/test-dpt19.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const test = require('tape');
-const DPTLib = require('../../src/dptlib');
-
-test('DPT19 datetime conversion', function(t) {
-
- var tests = ['1995-12-17T03:24:00', '1996-07-17T03:24:00'];
-
- Object.keys(tests).forEach(function(key) {
- var date = new Date(tests[key]);
- date.setMilliseconds(0);
-
- var day = (date.getDay() === 0) ? 7 : date.getDay();
- var buffer = new Buffer([
- date.getFullYear() - 1900, // year with offset 1900
- date.getMonth() + 1, // month from 1 - 12
- date.getDate(), // day of month from 1 - 31
- (day << 5) + date.getHours(), // 3 bits: day of week (1-7), 5 bits: hour
- date.getMinutes(),
- date.getSeconds(),
- 0,
- 0
- ]);
-
- var name = 'DPT19';
- var dpt = DPTLib.resolve(name);
-
- // unmarshalling test (raw data to value)
- var converted = DPTLib.fromBuffer(buffer, dpt);
- t.equal(date.getTime(), converted.getTime(),
- `${name} fromBuffer value ${JSON.stringify(buffer)} => ${converted}`
- );
-
- // marshalling test (value to raw data)
- var apdu = {};
- DPTLib.populateAPDU(date, apdu, name);
- t.ok(Buffer.compare(buffer, apdu.data) === 0,
- `${name} formatAPDU value ${date} => ${JSON.stringify(apdu)}`
- );
- });
-
- t.end();
-});
diff --git a/test/dptlib/test-dpt19.ts b/test/dptlib/test-dpt19.ts
new file mode 100644
index 0000000..ae621ab
--- /dev/null
+++ b/test/dptlib/test-dpt19.ts
@@ -0,0 +1,50 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import test from 'tape'
+import { fromBuffer, populateAPDU, resolve } from '../../src/dptlib'
+import { Datagram } from '../../src/KnxClient'
+
+test('DPT19 datetime conversion', function (t) {
+ const tests = ['1995-12-17T03:24:00', '1996-07-17T03:24:00']
+
+ Object.keys(tests).forEach(function (key) {
+ const date = new Date(tests[key])
+ date.setMilliseconds(0)
+
+ const day = date.getDay() === 0 ? 7 : date.getDay()
+ const buffer = Buffer.from([
+ date.getFullYear() - 1900, // year with offset 1900
+ date.getMonth() + 1, // month from 1 - 12
+ date.getDate(), // day of month from 1 - 31
+ (day << 5) + date.getHours(), // 3 bits: day of week (1-7), 5 bits: hour
+ date.getMinutes(),
+ date.getSeconds(),
+ 0,
+ 0,
+ ])
+
+ const name = 'DPT19'
+ const dpt = resolve(name)
+
+ // unmarshalling test (raw data to value)
+ const converted = fromBuffer(buffer, dpt)
+ t.equal(
+ date.getTime(),
+ converted.getTime(),
+ `${name} fromBuffer value ${JSON.stringify(buffer)} => ${converted}`,
+ )
+
+ // marshalling test (value to raw data)
+ const apdu = {} as Datagram['cemi']['apdu']
+ populateAPDU(date, apdu, name)
+ t.ok(
+ Buffer.compare(buffer, apdu.data) === 0,
+ `${name} formatAPDU value ${date} => ${JSON.stringify(apdu)}`,
+ )
+ })
+
+ t.end()
+})
diff --git a/test/dptlib/test-dpt2.js b/test/dptlib/test-dpt2.js
deleted file mode 100644
index 4d9255a..0000000
--- a/test/dptlib/test-dpt2.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-require('./commontest').do('DPT2', [
- { apdu_data: [0x00], jsval: {priority: 0 , data: 0}},
- { apdu_data: [0x01], jsval: {priority: 0 , data: 1}},
- { apdu_data: [0x02], jsval: {priority: 1 , data: 0}},
- { apdu_data: [0x03], jsval: {priority: 1 , data: 1}}
-]);
diff --git a/test/dptlib/test-dpt2.ts b/test/dptlib/test-dpt2.ts
new file mode 100644
index 0000000..06c7c01
--- /dev/null
+++ b/test/dptlib/test-dpt2.ts
@@ -0,0 +1,13 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { run } from './commontest'
+
+run('DPT2', [
+ { apdu_data: [0x00], jsval: { priority: 0, data: 0 } },
+ { apdu_data: [0x01], jsval: { priority: 0, data: 1 } },
+ { apdu_data: [0x02], jsval: { priority: 1, data: 0 } },
+ { apdu_data: [0x03], jsval: { priority: 1, data: 1 } },
+])
diff --git a/test/dptlib/test-dpt21.js b/test/dptlib/test-dpt21.js
deleted file mode 100644
index a89b6af..0000000
--- a/test/dptlib/test-dpt21.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-const commontest = require('./commontest')
-
-commontest.do('DPT21', [
- { apdu_data: [0x00], jsval: {outofservice: 0, fault: 0, overridden: 0, inalarm: 0, alarmunack: 0}},
- { apdu_data: [0x01], jsval: {outofservice: 1, fault: 0, overridden: 0, inalarm: 0, alarmunack: 0}},
- { apdu_data: [0x03], jsval: {outofservice: 1, fault: 1, overridden: 0, inalarm: 0, alarmunack: 0}},
- { apdu_data: [0x05], jsval: {outofservice: 1, fault: 0, overridden: 1, inalarm: 0, alarmunack: 0}},
- { apdu_data: [0x08], jsval: {outofservice: 0, fault: 0, overridden: 0, inalarm: 1, alarmunack: 0}}
-]);
-
-commontest.do('DPT21.001', [
- { apdu_data: [0x01], jsval: {outofservice: 1, fault: 0, overridden: 0, inalarm: 0, alarmunack: 0}},
- { apdu_data: [0x05], jsval: {outofservice: 1, fault: 0, overridden: 1, inalarm: 0, alarmunack: 0}},
- { apdu_data: [0x06], jsval: {outofservice: 0, fault: 1, overridden: 1, inalarm: 0, alarmunack: 0}},
- { apdu_data: [0x08], jsval: {outofservice: 0, fault: 0, overridden: 0, inalarm: 1, alarmunack: 0}}
-]);
diff --git a/test/dptlib/test-dpt21.ts b/test/dptlib/test-dpt21.ts
new file mode 100644
index 0000000..2eb32b2
--- /dev/null
+++ b/test/dptlib/test-dpt21.ts
@@ -0,0 +1,101 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+import { run } from './commontest'
+
+run('DPT21', [
+ {
+ apdu_data: [0x00],
+ jsval: {
+ outofservice: 0,
+ fault: 0,
+ overridden: 0,
+ inalarm: 0,
+ alarmunack: 0,
+ },
+ },
+ {
+ apdu_data: [0x01],
+ jsval: {
+ outofservice: 1,
+ fault: 0,
+ overridden: 0,
+ inalarm: 0,
+ alarmunack: 0,
+ },
+ },
+ {
+ apdu_data: [0x03],
+ jsval: {
+ outofservice: 1,
+ fault: 1,
+ overridden: 0,
+ inalarm: 0,
+ alarmunack: 0,
+ },
+ },
+ {
+ apdu_data: [0x05],
+ jsval: {
+ outofservice: 1,
+ fault: 0,
+ overridden: 1,
+ inalarm: 0,
+ alarmunack: 0,
+ },
+ },
+ {
+ apdu_data: [0x08],
+ jsval: {
+ outofservice: 0,
+ fault: 0,
+ overridden: 0,
+ inalarm: 1,
+ alarmunack: 0,
+ },
+ },
+])
+
+run('DPT21.001', [
+ {
+ apdu_data: [0x01],
+ jsval: {
+ outofservice: 1,
+ fault: 0,
+ overridden: 0,
+ inalarm: 0,
+ alarmunack: 0,
+ },
+ },
+ {
+ apdu_data: [0x05],
+ jsval: {
+ outofservice: 1,
+ fault: 0,
+ overridden: 1,
+ inalarm: 0,
+ alarmunack: 0,
+ },
+ },
+ {
+ apdu_data: [0x06],
+ jsval: {
+ outofservice: 0,
+ fault: 1,
+ overridden: 1,
+ inalarm: 0,
+ alarmunack: 0,
+ },
+ },
+ {
+ apdu_data: [0x08],
+ jsval: {
+ outofservice: 0,
+ fault: 0,
+ overridden: 0,
+ inalarm: 1,
+ alarmunack: 0,
+ },
+ },
+])
diff --git a/test/dptlib/test-dpt237.js b/test/dptlib/test-dpt237.js
deleted file mode 100644
index 4833e9c..0000000
--- a/test/dptlib/test-dpt237.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-const commontest = require('./commontest')
-
-commontest.do('DPT237', [
- { apdu_data: [0x00, 0x00], jsval: {address: 0, addresstype: 0,
- readresponse: 0, lampfailure: 0, ballastfailure: 0, convertorerror: 0}},
- { apdu_data: [0x00, 0x21], jsval: {address: 1, addresstype: 1,
- readresponse: 0, lampfailure: 0, ballastfailure: 0, convertorerror: 0}},
- { apdu_data: [0x00, 0x63], jsval: {address: 3, addresstype: 1,
- readresponse: 1, lampfailure: 0, ballastfailure: 0, convertorerror: 0}},
- { apdu_data: [0x01, 0x05], jsval: {address: 5, addresstype: 0,
- readresponse: 0, lampfailure: 0, ballastfailure: 1, convertorerror: 0}},
- { apdu_data: [0x02, 0x08], jsval: {address: 8, addresstype: 0,
- readresponse: 0, lampfailure: 0, ballastfailure: 0, convertorerror: 1}}
-]);
-
-commontest.do('DPT237.600', [
- { apdu_data: [0x00, 0x01], jsval: {address: 1, addresstype: 0,
- readresponse: 0, lampfailure: 0, ballastfailure: 0, convertorerror: 0}},
- { apdu_data: [0x00, 0x05], jsval: {address: 5, addresstype: 0,
- readresponse: 0, lampfailure: 0, ballastfailure: 0, convertorerror: 0}},
- { apdu_data: [0x00, 0x06], jsval: {address: 6, addresstype: 0,
- readresponse: 0, lampfailure: 0, ballastfailure: 0, convertorerror: 0}},
- { apdu_data: [0x00, 0x08], jsval: {address: 8, addresstype: 0,
- readresponse: 0, lampfailure: 0, ballastfailure: 0, convertorerror: 0}}
-]);
diff --git a/test/dptlib/test-dpt237.ts b/test/dptlib/test-dpt237.ts
new file mode 100644
index 0000000..f53ce70
--- /dev/null
+++ b/test/dptlib/test-dpt237.ts
@@ -0,0 +1,110 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+import { run } from './commontest'
+
+run('DPT237', [
+ {
+ apdu_data: [0x00, 0x00],
+ jsval: {
+ address: 0,
+ addresstype: 0,
+ readresponse: 0,
+ lampfailure: 0,
+ ballastfailure: 0,
+ convertorerror: 0,
+ },
+ },
+ {
+ apdu_data: [0x00, 0x21],
+ jsval: {
+ address: 1,
+ addresstype: 1,
+ readresponse: 0,
+ lampfailure: 0,
+ ballastfailure: 0,
+ convertorerror: 0,
+ },
+ },
+ {
+ apdu_data: [0x00, 0x63],
+ jsval: {
+ address: 3,
+ addresstype: 1,
+ readresponse: 1,
+ lampfailure: 0,
+ ballastfailure: 0,
+ convertorerror: 0,
+ },
+ },
+ {
+ apdu_data: [0x01, 0x05],
+ jsval: {
+ address: 5,
+ addresstype: 0,
+ readresponse: 0,
+ lampfailure: 0,
+ ballastfailure: 1,
+ convertorerror: 0,
+ },
+ },
+ {
+ apdu_data: [0x02, 0x08],
+ jsval: {
+ address: 8,
+ addresstype: 0,
+ readresponse: 0,
+ lampfailure: 0,
+ ballastfailure: 0,
+ convertorerror: 1,
+ },
+ },
+])
+
+run('DPT237.600', [
+ {
+ apdu_data: [0x00, 0x01],
+ jsval: {
+ address: 1,
+ addresstype: 0,
+ readresponse: 0,
+ lampfailure: 0,
+ ballastfailure: 0,
+ convertorerror: 0,
+ },
+ },
+ {
+ apdu_data: [0x00, 0x05],
+ jsval: {
+ address: 5,
+ addresstype: 0,
+ readresponse: 0,
+ lampfailure: 0,
+ ballastfailure: 0,
+ convertorerror: 0,
+ },
+ },
+ {
+ apdu_data: [0x00, 0x06],
+ jsval: {
+ address: 6,
+ addresstype: 0,
+ readresponse: 0,
+ lampfailure: 0,
+ ballastfailure: 0,
+ convertorerror: 0,
+ },
+ },
+ {
+ apdu_data: [0x00, 0x08],
+ jsval: {
+ address: 8,
+ addresstype: 0,
+ readresponse: 0,
+ lampfailure: 0,
+ ballastfailure: 0,
+ convertorerror: 0,
+ },
+ },
+])
diff --git a/test/dptlib/test-dpt3.js b/test/dptlib/test-dpt3.js
deleted file mode 100644
index 7b46aee..0000000
--- a/test/dptlib/test-dpt3.js
+++ /dev/null
@@ -1,17 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-const commontest = require('./commontest')
-
-commontest.do('DPT3', [
- { apdu_data: [0x00], jsval: {decr_incr: 0, data: 0}},
- { apdu_data: [0x06], jsval: {decr_incr: 0, data: 6}}
-]);
-
-commontest.do('DPT3.007', [
- { apdu_data: [0x01], jsval: {decr_incr: 0, data: 1}},
- { apdu_data: [0x05], jsval: {decr_incr: 0, data: 5}},
- { apdu_data: [0x08], jsval: {decr_incr: 1, data: 0}},
- { apdu_data: [0x0f], jsval: {decr_incr: 1, data: 7}}
-]);
diff --git a/test/dptlib/test-dpt3.ts b/test/dptlib/test-dpt3.ts
new file mode 100644
index 0000000..1df5dc0
--- /dev/null
+++ b/test/dptlib/test-dpt3.ts
@@ -0,0 +1,18 @@
+import { run } from './commontest'
+
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+run('DPT3', [
+ { apdu_data: [0x00], jsval: { decr_incr: 0, data: 0 } },
+ { apdu_data: [0x06], jsval: { decr_incr: 0, data: 6 } },
+])
+
+run('DPT3.007', [
+ { apdu_data: [0x01], jsval: { decr_incr: 0, data: 1 } },
+ { apdu_data: [0x05], jsval: { decr_incr: 0, data: 5 } },
+ { apdu_data: [0x08], jsval: { decr_incr: 1, data: 0 } },
+ { apdu_data: [0x0f], jsval: { decr_incr: 1, data: 7 } },
+])
diff --git a/test/dptlib/test-dpt4.js b/test/dptlib/test-dpt4.js
deleted file mode 100644
index 275e156..0000000
--- a/test/dptlib/test-dpt4.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-require('./commontest').do('DPT4', [
- { apdu_data: [0x40], jsval: "@"},
- { apdu_data: [0x76], jsval: "v"}
-]);
diff --git a/test/dptlib/test-dpt4.ts b/test/dptlib/test-dpt4.ts
new file mode 100644
index 0000000..030592c
--- /dev/null
+++ b/test/dptlib/test-dpt4.ts
@@ -0,0 +1,11 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { run } from './commontest'
+
+run('DPT4', [
+ { apdu_data: [0x40], jsval: '@' },
+ { apdu_data: [0x76], jsval: 'v' },
+])
diff --git a/test/dptlib/test-dpt5.js b/test/dptlib/test-dpt5.js
deleted file mode 100644
index b8c7646..0000000
--- a/test/dptlib/test-dpt5.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-const commontest = require('./commontest');
-// DPT5 without subtype: no scaling
-commontest.do('DPT5', [
- { apdu_data: [0x00], jsval: 0},
- { apdu_data: [0x40], jsval: 64},
- { apdu_data: [0x41], jsval: 65},
- { apdu_data: [0x80], jsval: 128},
- { apdu_data: [0xff], jsval: 255}
-]);
-// 5.001 percentage (0=0..ff=100%)
-commontest.do('DPT5.001', [
- { apdu_data: [0x00], jsval: 0 },
- { apdu_data: [0x80], jsval: 50},
- { apdu_data: [0xff], jsval: 100}
-]);
-// 5.003 angle (degrees 0=0, ff=360)
-commontest.do('DPT5.003', [
- { apdu_data: [0x00], jsval: 0 },
- { apdu_data: [0x80], jsval: 181 },
- { apdu_data: [0xff], jsval: 360 }
-]);
diff --git a/test/dptlib/test-dpt5.ts b/test/dptlib/test-dpt5.ts
new file mode 100644
index 0000000..29b9b7d
--- /dev/null
+++ b/test/dptlib/test-dpt5.ts
@@ -0,0 +1,27 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { run } from './commontest'
+
+// DPT5 without subtype: no scaling
+run('DPT5', [
+ { apdu_data: [0x00], jsval: 0 },
+ { apdu_data: [0x40], jsval: 64 },
+ { apdu_data: [0x41], jsval: 65 },
+ { apdu_data: [0x80], jsval: 128 },
+ { apdu_data: [0xff], jsval: 255 },
+])
+// 5.001 percentage (0=0..ff=100%)
+run('DPT5.001', [
+ { apdu_data: [0x00], jsval: 0 },
+ { apdu_data: [0x80], jsval: 50 },
+ { apdu_data: [0xff], jsval: 100 },
+])
+// 5.003 angle (degrees 0=0, ff=360)
+run('DPT5.003', [
+ { apdu_data: [0x00], jsval: 0 },
+ { apdu_data: [0x80], jsval: 181 },
+ { apdu_data: [0xff], jsval: 360 },
+])
diff --git a/test/dptlib/test-dpt6.js b/test/dptlib/test-dpt6.js
deleted file mode 100644
index 52ccdaf..0000000
--- a/test/dptlib/test-dpt6.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-require('./commontest').do('DPT6', [
- { apdu_data: [0x00], jsval: 0},
- { apdu_data: [0x7f], jsval: 127},
- { apdu_data: [0x80], jsval: -128},
- { apdu_data: [0xff], jsval: -1}
-]);
diff --git a/test/dptlib/test-dpt6.ts b/test/dptlib/test-dpt6.ts
new file mode 100644
index 0000000..3ce424f
--- /dev/null
+++ b/test/dptlib/test-dpt6.ts
@@ -0,0 +1,13 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { run } from './commontest'
+
+run('DPT6', [
+ { apdu_data: [0x00], jsval: 0 },
+ { apdu_data: [0x7f], jsval: 127 },
+ { apdu_data: [0x80], jsval: -128 },
+ { apdu_data: [0xff], jsval: -1 },
+])
diff --git a/test/dptlib/test-dpt7.js b/test/dptlib/test-dpt7.js
deleted file mode 100644
index c71d9ef..0000000
--- a/test/dptlib/test-dpt7.js
+++ /dev/null
@@ -1,11 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-require('./commontest').do('DPT7', [
- { apdu_data: [0x00, 0x11], jsval: 17},
- { apdu_data: [0x01, 0x00], jsval: 256},
- { apdu_data: [0x10, 0x01], jsval: 4097},
- { apdu_data: [0xff, 0xff], jsval: 65535},
-]);
diff --git a/test/dptlib/test-dpt7.ts b/test/dptlib/test-dpt7.ts
new file mode 100644
index 0000000..ec57773
--- /dev/null
+++ b/test/dptlib/test-dpt7.ts
@@ -0,0 +1,13 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { run } from './commontest'
+
+run('DPT7', [
+ { apdu_data: [0x00, 0x11], jsval: 17 },
+ { apdu_data: [0x01, 0x00], jsval: 256 },
+ { apdu_data: [0x10, 0x01], jsval: 4097 },
+ { apdu_data: [0xff, 0xff], jsval: 65535 },
+])
diff --git a/test/dptlib/test-dpt8.js b/test/dptlib/test-dpt8.js
deleted file mode 100644
index 4037692..0000000
--- a/test/dptlib/test-dpt8.js
+++ /dev/null
@@ -1,12 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-require('./commontest').do('DPT8', [
- { apdu_data: [0x00, 0x11], jsval: 17},
- { apdu_data: [0x01, 0x00], jsval: 256},
- { apdu_data: [0x7f, 0xff], jsval: 32767},
- { apdu_data: [0x80, 0x00], jsval: -32768},
- { apdu_data: [0xff, 0xff], jsval: -1}
-]);
diff --git a/test/dptlib/test-dpt8.ts b/test/dptlib/test-dpt8.ts
new file mode 100644
index 0000000..1a21fa4
--- /dev/null
+++ b/test/dptlib/test-dpt8.ts
@@ -0,0 +1,14 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { run } from './commontest'
+
+run('DPT8', [
+ { apdu_data: [0x00, 0x11], jsval: 17 },
+ { apdu_data: [0x01, 0x00], jsval: 256 },
+ { apdu_data: [0x7f, 0xff], jsval: 32767 },
+ { apdu_data: [0x80, 0x00], jsval: -32768 },
+ { apdu_data: [0xff, 0xff], jsval: -1 },
+])
diff --git a/test/dptlib/test-dpt9.js b/test/dptlib/test-dpt9.js
deleted file mode 100644
index 9f0adb8..0000000
--- a/test/dptlib/test-dpt9.js
+++ /dev/null
@@ -1,16 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-require('./commontest').do('DPT9', [
- { apdu_data: [0x00, 0x02], jsval: 0.02},
- { apdu_data: [0x87, 0xfe], jsval: -0.02},
- { apdu_data: [0x02, 0xf8], jsval: 7.6},
- { apdu_data: [0x0c, 0x24], jsval: 21.2},
- { apdu_data: [0x0c, 0x7e], jsval: 23},
- { apdu_data: [0x5c, 0xc4], jsval: 24985.6},
- { apdu_data: [0xdb, 0x3c], jsval: -24985.6},
- { apdu_data: [0x7f, 0xfe], jsval: 670433.28},
- { apdu_data: [0xf8, 0x02], jsval: -670433.28},
-]);
diff --git a/test/dptlib/test-dpt9.ts b/test/dptlib/test-dpt9.ts
new file mode 100644
index 0000000..d1e569d
--- /dev/null
+++ b/test/dptlib/test-dpt9.ts
@@ -0,0 +1,18 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { run } from './commontest'
+
+run('DPT9', [
+ { apdu_data: [0x00, 0x02], jsval: 0.02 },
+ { apdu_data: [0x87, 0xfe], jsval: -0.02 },
+ { apdu_data: [0x02, 0xf8], jsval: 7.6 },
+ { apdu_data: [0x0c, 0x24], jsval: 21.2 },
+ { apdu_data: [0x0c, 0x7e], jsval: 23 },
+ { apdu_data: [0x5c, 0xc4], jsval: 24985.6 },
+ { apdu_data: [0xdb, 0x3c], jsval: -24985.6 },
+ { apdu_data: [0x7f, 0xfe], jsval: 670433.28 },
+ { apdu_data: [0xf8, 0x02], jsval: -670433.28 },
+])
diff --git a/test/knxproto/test-address.js b/test/knxproto/test-address.js
deleted file mode 100644
index 902ee7c..0000000
--- a/test/knxproto/test-address.js
+++ /dev/null
@@ -1,67 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const address = require('../../src/Address.js');
-const assert = require('assert');
-const test = require('tape');
-
-//
-test('KNX physical address test', function(t) {
- var tests = {
- "0.0.0": new Buffer([0, 0]),
- "0.0.10": new Buffer([0, 10]),
- "0.0.255": new Buffer([0, 255]),
- "0.1.0": new Buffer([1, 0]),
- "1.0.0": new Buffer([16, 0]),
- "15.14.0": new Buffer([254, 0]),
- "15.15.0": new Buffer([255, 0]),
- };
- Object.keys(tests).forEach((key, idx) => {
- var buf = tests[key];
- var encoded = address.parse(key, address.TYPE.PHYSICAL);
- t.ok(Buffer.compare(encoded, buf) == 0, `Marshaled KNX physical address ${key}: encoded=${encoded.toString()} buf=${buf.toString()}`);
- var decoded = address.toString(encoded, address.TYPE.PHYSICAL);
- t.ok(decoded == key, `${key}: unmarshaled KNX physical address`);
- });
- // test invalid physical addresses
- var invalid = ["0.0.", "0.0.256", "123122312312312", "16.0.0", "15.17.13"];
- for (var i in invalid) {
- var key = invalid[i];
- t.throws(() => {
- address.parse(key);
- }, null, `invalid KNX physical address ${key}`);
- }
- t.end();
-});
-
-//
-test('KNX group address test', function(t) {
- var tests = {
- "0/0/0": new Buffer([0, 0]),
- "0/0/10": new Buffer([0, 10]),
- "0/0/255": new Buffer([0, 255]),
- "0/1/0": new Buffer([1, 0]),
- "1/0/0": new Buffer([8, 0]),
- "1/7/0": new Buffer([15, 0]),
- "31/6/0": new Buffer([254, 0]),
- "31/7/0": new Buffer([255, 0]),
- };
- Object.keys(tests).forEach((key, idx) => {
- var buf = tests[key];
- var encoded = address.parse(key, address.TYPE.GROUP);
- t.ok(Buffer.compare(encoded, buf) == 0, `Marshaled KNX group address ${key}: encoded=${encoded.toString('hex')} buf=${buf.toString('hex')}`);
- var decoded = address.toString(encoded, address.TYPE.GROUP);
- t.ok(decoded == key, `${key}: unmarshaled KNX group address`);
- });
-
- var invalid = ["0/0/", "0/0/256", "123122312312312", "16/0/0", "15/17/13"];
- for (var i in invalid) {
- var key = invalid[i];
- t.throws(() => {
- address.parse(key);
- }, null, `invalid KNX group address ${key}`);
- }
- t.end();
-});
diff --git a/test/knxproto/test-address.ts b/test/knxproto/test-address.ts
new file mode 100644
index 0000000..df28e3e
--- /dev/null
+++ b/test/knxproto/test-address.ts
@@ -0,0 +1,80 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import * as address from '../../src/Address'
+import test from 'tape'
+
+//
+test('KNX physical address test', function (t) {
+ const tests = {
+ '0.0.0': Buffer.from([0, 0]),
+ '0.0.10': Buffer.from([0, 10]),
+ '0.0.255': Buffer.from([0, 255]),
+ '0.1.0': Buffer.from([1, 0]),
+ '1.0.0': Buffer.from([16, 0]),
+ '15.14.0': Buffer.from([254, 0]),
+ '15.15.0': Buffer.from([255, 0]),
+ }
+ Object.keys(tests).forEach((key, idx) => {
+ const buf = tests[key]
+ const encoded = address.parse(key, address.TYPE.PHYSICAL)
+ t.ok(
+ Buffer.compare(encoded, buf) === 0,
+ `Marshaled KNX physical address ${key}: encoded=${encoded.toString()} buf=${buf.toString()}`,
+ )
+ const decoded = address.toString(encoded, address.TYPE.PHYSICAL)
+ t.ok(decoded === key, `${key}: unmarshaled KNX physical address`)
+ })
+ // test invalid physical addresses
+ const invalid = ['0.0.', '0.0.256', '123122312312312', '16.0.0', '15.17.13']
+ for (const i in invalid) {
+ const key = invalid[i]
+ t.throws(
+ () => {
+ address.parse(key)
+ },
+ null,
+ `invalid KNX physical address ${key}`,
+ )
+ }
+ t.end()
+})
+
+//
+test('KNX group address test', function (t) {
+ const tests = {
+ '0/0/0': Buffer.from([0, 0]),
+ '0/0/10': Buffer.from([0, 10]),
+ '0/0/255': Buffer.from([0, 255]),
+ '0/1/0': Buffer.from([1, 0]),
+ '1/0/0': Buffer.from([8, 0]),
+ '1/7/0': Buffer.from([15, 0]),
+ '31/6/0': Buffer.from([254, 0]),
+ '31/7/0': Buffer.from([255, 0]),
+ }
+ Object.keys(tests).forEach((key, idx) => {
+ const buf = tests[key]
+ const encoded = address.parse(key, address.TYPE.GROUP)
+ t.ok(
+ Buffer.compare(encoded, buf) === 0,
+ `Marshaled KNX group address ${key}: encoded=${encoded.toString('hex')} buf=${buf.toString('hex')}`,
+ )
+ const decoded = address.toString(encoded, address.TYPE.GROUP)
+ t.ok(decoded === key, `${key}: unmarshaled KNX group address`)
+ })
+
+ const invalid = ['0/0/', '0/0/256', '123122312312312', '16/0/0', '15/17/13']
+ for (const i in invalid) {
+ const key = invalid[i]
+ t.throws(
+ () => {
+ address.parse(key)
+ },
+ null,
+ `invalid KNX group address ${key}`,
+ )
+ }
+ t.end()
+})
diff --git a/test/knxproto/test-proto.js b/test/knxproto/test-proto.js
deleted file mode 100644
index 9c8f5db..0000000
--- a/test/knxproto/test-proto.js
+++ /dev/null
@@ -1,259 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const knxnetprotocol = require('../../src/KnxProtocol.js');
-const assert = require('assert');
-const test = require('tape');
-knxnetprotocol.debug = true;
-
-//
-test('KNX protocol unmarshaller', function(t) {
- var tests = {
- "ETS5 programming request": new Buffer([
- 6, 16, 4, 32, 0, 20, 4, 34,
- 1, 0, 197, 0, 17, 252, 17, 253,
- 17, 1, 0, 238
- ])
- };
- Object.keys(tests).forEach((key, idx) => {
- var buf = tests[key];
- // unmarshal from a buffer...
- var reader = knxnetprotocol.createReader(buf);
- var writer = knxnetprotocol.createWriter();
- reader.KNXNetHeader('tmp');
- var decoded = reader.next()['tmp'];
- console.log("\n=== %s: %j ===> %j", key, buf, decoded);
- t.ok(decoded != undefined, `${key}: unmarshaled KNX datagram`);
- });
- t.end();
-});
-
-test('KNX protocol marshal+unmarshal', function(t) {
- var tests = {
- CONNECT_REQUEST: new Buffer(
- "06100205001a0801c0a80ab3d96d0801c0a80ab3d83604040200", 'hex'),
- CONNECT_RESPONSE: new Buffer(
- "061002060014030008010a0c17350e5704040000", 'hex'),
- "CONNECT_RESPONSE, failure E_NO_MORE_CONNECTIONS: 0x24": new Buffer(
- "0610020600080024", 'hex'),
- "tunneling request (GroupValue_Read) apdu=1byte": new Buffer(
- "061004200015040200002e00bce000000832010000", 'hex'),
- "tunneling request (GroupValue_Write) apdu=1byte": new Buffer(
- "061004200015040200002e00bce000000832010081", 'hex'),
- "tunneling request (GroupValue_Write) apdu=2byte": new Buffer(
- "061004200016040201002900bce00000083b0200804a", 'hex'),
- "routing indication": new Buffer(
- "0610053000112900bce0ff0f0908010000", 'hex'),
- DISCONNECT_REQUEST: new Buffer([
- 6, 16, 2, 9, 0, 16, 142, 142,
- 8, 1, 192, 168, 2, 222, 14, 87
- ]),
- };
- Object.keys(tests).forEach((key, idx) => {
- var buf = tests[key];
- // unmarshal from a buffer...
- var reader = knxnetprotocol.createReader(buf);
- var writer = knxnetprotocol.createWriter();
- reader.KNXNetHeader('tmp');
- var decoded = reader.next()['tmp'];
- console.log("\n=== %s: %j ===> %j", key, buf, decoded);
- t.ok(decoded != undefined, `${key}: unmarshaled KNX datagram`);
- // then marshal the datagram again into a buffer...
- writer.KNXNetHeader(decoded);
- if (Buffer.compare(buf, writer.buffer) != 0) {
- console.log("\n\n========\n FAIL: %s\n========\nbuffer is different:\n", key);
- console.log(' 0 1 2 3 4 5|6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9')
- console.log('expected : %s', buf.toString('hex'));
- console.log('got instead: %s', writer.buffer.toString('hex'));
- }
- t.ok(Buffer.compare(buf, writer.buffer) == 0);
- });
- t.end();
-});
-
-test('KNX protocol marshaller', function(t) {
- var tests = {
- "compose tunneling request (write) apdu=1byte - turn ON a light": {
- hexbuf: "061004200015040200002e00bce000000832010081",
- dgram: {
- header_length: 6,
- protocol_version: 16,
- service_type: 1056,
- total_length: 21,
- tunnstate: {
- header_length: 4,
- channel_id: 2,
- seqnum: 0,
- rsvd: 0
- },
- cemi: {
- msgcode: 46,
- addinfo_length: 0,
- ctrl: {
- frameType: 1,
- reserved: 0,
- repeat: 1,
- broadcast: 1,
- priority: 3,
- acknowledge: 0,
- confirm: 0,
- destAddrType: 1,
- hopCount: 6,
- extendedFrame: 0
- },
- src_addr: '0.0.0',
- dest_addr: '1/0/50',
- apdu: {
- tpci: 0,
- apci: 'GroupValue_Write',
- data: 1
- }
- }
- }
- },
-
- "compose tunneling request (write) apdu=1byte - turn OFF a light": {
- hexbuf: "061004200015040200002e00bce000000832010080",
- dgram: {
- header_length: 6,
- protocol_version: 16,
- service_type: 1056,
- total_length: 21,
- tunnstate: {
- header_length: 4,
- channel_id: 2,
- seqnum: 0,
- rsvd: 0
- },
- cemi: {
- msgcode: 46,
- addinfo_length: 0,
- ctrl: {
- frameType: 1,
- reserved: 0,
- repeat: 1,
- broadcast: 1,
- priority: 3,
- acknowledge: 0,
- confirm: 0,
- destAddrType: 1,
- hopCount: 6,
- extendedFrame: 0
- },
- src_addr: '0.0.0',
- dest_addr: '1/0/50',
- apdu: {
- tpci: 0,
- apci: 'GroupValue_Write',
- data: [0]
- }
- }
- }
- },
-
- "compose tunneling request (write) apdu=2byte - DIMMING a light to 10%": {
- hexbuf: "061004200016040200002e00bce0000008320200800a",
- dgram: {
- header_length: 6,
- protocol_version: 16,
- service_type: 1056,
- total_length: 21,
- tunnstate: {
- header_length: 4,
- channel_id: 2,
- seqnum: 0,
- rsvd: 0
- },
- cemi: {
- msgcode: 46,
- addinfo_length: 0,
- ctrl: {
- frameType: 1,
- reserved: 0,
- repeat: 1,
- broadcast: 1,
- priority: 3,
- acknowledge: 0,
- confirm: 0,
- destAddrType: 1,
- hopCount: 6,
- extendedFrame: 0
- },
- src_addr: '0.0.0',
- dest_addr: '1/0/50',
- apdu: {
- bitlength: 8,
- tpci: 0,
- apci: 'GroupValue_Write',
- data: [10]
- }
- }
- }
- },
-
- "temperature response, apdu=2-byte": {
- hexbuf: "061004200017040200002e00BCD0110B000F0300400730",
- dgram: {
- header_length: 6,
- protocol_version: 16,
- service_type: 1056,
- total_length: 22,
- tunnstate: {
- header_length: 4,
- channel_id: 2,
- seqnum: 0,
- rsvd: 0
- },
- cemi: {
- msgcode: 46,
- addinfo_length: 0,
- ctrl: {
- frameType: 1,
- reserved: 0,
- repeat: 1,
- broadcast: 1,
- priority: 3,
- acknowledge: 0,
- confirm: 0,
- destAddrType: 1,
- hopCount: 5,
- extendedFrame: 0
- },
- src_addr: '1.1.11',
- dest_addr: '0/0/15',
- apdu: {
- bitlength: 16,
- tpci: 0,
- apci: 'GroupValue_Response',
- data: [0x07, 0x30]
- }
- }
- }
- },
- }
-
-
- Object.keys(tests).forEach((key, idx) => {
- var testcase = tests[key];
- var buf = typeof testcase.hexbuf == 'string' ?
- new Buffer(testcase.hexbuf.replace(/\s/g, ''), 'hex') : hexbuf;
- console.log("\n=== %s", key);
- // marshal the test datagram
- var writer = knxnetprotocol.createWriter();
- writer.KNXNetHeader(testcase.dgram);
- if (Buffer.compare(buf, writer.buffer) != 0) {
- // if this fails, unmarshal the buffer again to a datagram
- var reader = knxnetprotocol.createReader(writer.buffer);
- reader.KNXNetHeader('tmp');
- var decoded = reader.next()['tmp'];
- console.log("\n\n========\n FAIL: %s\n========\nbuffer is different:\n", key);
- console.log(' 0 1 2 3 4 5|6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9')
- console.log('expected : %s', buf.toString('hex'));
- console.log('got instead: %s', writer.buffer.toString('hex'));
- }
- t.ok(Buffer.compare(buf, writer.buffer) == 0);
- });
- t.end();
-});
diff --git a/test/knxproto/test-proto.ts b/test/knxproto/test-proto.ts
new file mode 100644
index 0000000..5aaf888
--- /dev/null
+++ b/test/knxproto/test-proto.ts
@@ -0,0 +1,277 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import knxProto from '../../src/KnxProtocol'
+import test from 'tape'
+
+knxProto.debug = true
+
+//
+test('KNX protocol unmarshaller', function (t) {
+ const tests = {
+ 'ETS5 programming request': Buffer.from([
+ 6, 16, 4, 32, 0, 20, 4, 34, 1, 0, 197, 0, 17, 252, 17, 253, 17, 1,
+ 0, 238,
+ ]),
+ }
+ Object.keys(tests).forEach((key, idx) => {
+ const buf = tests[key]
+ // unmarshal from a buffer...
+ const decoded = knxProto.parseDatagram(buf)
+ console.log('\n=== %s: %j ===> %j', key, buf, decoded)
+ t.ok(decoded !== undefined, `${key}: unmarshaled KNX datagram`)
+ })
+ t.end()
+})
+
+test('KNX protocol marshal+unmarshal', function (t) {
+ const tests = {
+ CONNECT_REQUEST: Buffer.from(
+ '06100205001a0801c0a80ab3d96d0801c0a80ab3d83604040200',
+ 'hex',
+ ),
+ CONNECT_RESPONSE: Buffer.from(
+ '061002060014030008010a0c17350e5704040000',
+ 'hex',
+ ),
+ 'CONNECT_RESPONSE, failure E_NO_MORE_CONNECTIONS: 0x24': Buffer.from(
+ '0610020600080024',
+ 'hex',
+ ),
+ 'tunneling request (GroupValue_Read) apdu=1byte': Buffer.from(
+ '061004200015040200002e00bce000000832010000',
+ 'hex',
+ ),
+ 'tunneling request (GroupValue_Write) apdu=1byte': Buffer.from(
+ '061004200015040200002e00bce000000832010081',
+ 'hex',
+ ),
+ 'tunneling request (GroupValue_Write) apdu=2byte': Buffer.from(
+ '061004200016040201002900bce00000083b0200804a',
+ 'hex',
+ ),
+ 'routing indication': Buffer.from(
+ '0610053000112900bce0ff0f0908010000',
+ 'hex',
+ ),
+ DISCONNECT_REQUEST: Buffer.from([
+ 6, 16, 2, 9, 0, 16, 142, 142, 8, 1, 192, 168, 2, 222, 14, 87,
+ ]),
+ }
+ Object.keys(tests).forEach((key, idx) => {
+ const buf = tests[key]
+ // unmarshal from a buffer...
+ const writer = knxProto.createWriter()
+ const decoded = knxProto.parseDatagram(buf)
+ console.log('\n=== %s: %j ===> %j', key, buf, decoded)
+ t.ok(decoded !== undefined, `${key}: unmarshaled KNX datagram`)
+ // then marshal the datagram again into a buffer...
+ writer.KNXNetHeader(decoded)
+ if (Buffer.compare(buf, writer.buffer) !== 0) {
+ console.log(
+ '\n\n========\n FAIL: %s\n========\nbuffer is different:\n',
+ key,
+ )
+ console.log(
+ ' 0 1 2 3 4 5|6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9',
+ )
+ console.log('expected : %s', buf.toString('hex'))
+ console.log('got instead: %s', writer.buffer.toString('hex'))
+ }
+ t.ok(Buffer.compare(buf, writer.buffer) === 0)
+ })
+ t.end()
+})
+
+test('KNX protocol marshaller', function (t) {
+ const tests = {
+ 'compose tunneling request (write) apdu=1byte - turn ON a light': {
+ hexbuf: '061004200015040200002e00bce000000832010081',
+ dgram: {
+ header_length: 6,
+ protocol_version: 16,
+ service_type: 1056,
+ total_length: 21,
+ tunnstate: {
+ header_length: 4,
+ channel_id: 2,
+ seqnum: 0,
+ rsvd: 0,
+ },
+ cemi: {
+ msgcode: 46,
+ addinfo_length: 0,
+ ctrl: {
+ frameType: 1,
+ reserved: 0,
+ repeat: 1,
+ broadcast: 1,
+ priority: 3,
+ acknowledge: 0,
+ confirm: 0,
+ destAddrType: 1,
+ hopCount: 6,
+ extendedFrame: 0,
+ },
+ src_addr: '0.0.0',
+ dest_addr: '1/0/50',
+ apdu: {
+ tpci: 0,
+ apci: 'GroupValue_Write',
+ data: 1,
+ },
+ },
+ },
+ },
+
+ 'compose tunneling request (write) apdu=1byte - turn OFF a light': {
+ hexbuf: '061004200015040200002e00bce000000832010080',
+ dgram: {
+ header_length: 6,
+ protocol_version: 16,
+ service_type: 1056,
+ total_length: 21,
+ tunnstate: {
+ header_length: 4,
+ channel_id: 2,
+ seqnum: 0,
+ rsvd: 0,
+ },
+ cemi: {
+ msgcode: 46,
+ addinfo_length: 0,
+ ctrl: {
+ frameType: 1,
+ reserved: 0,
+ repeat: 1,
+ broadcast: 1,
+ priority: 3,
+ acknowledge: 0,
+ confirm: 0,
+ destAddrType: 1,
+ hopCount: 6,
+ extendedFrame: 0,
+ },
+ src_addr: '0.0.0',
+ dest_addr: '1/0/50',
+ apdu: {
+ tpci: 0,
+ apci: 'GroupValue_Write',
+ data: [0],
+ },
+ },
+ },
+ },
+
+ 'compose tunneling request (write) apdu=2byte - DIMMING a light to 10%':
+ {
+ hexbuf: '061004200016040200002e00bce0000008320200800a',
+ dgram: {
+ header_length: 6,
+ protocol_version: 16,
+ service_type: 1056,
+ total_length: 21,
+ tunnstate: {
+ header_length: 4,
+ channel_id: 2,
+ seqnum: 0,
+ rsvd: 0,
+ },
+ cemi: {
+ msgcode: 46,
+ addinfo_length: 0,
+ ctrl: {
+ frameType: 1,
+ reserved: 0,
+ repeat: 1,
+ broadcast: 1,
+ priority: 3,
+ acknowledge: 0,
+ confirm: 0,
+ destAddrType: 1,
+ hopCount: 6,
+ extendedFrame: 0,
+ },
+ src_addr: '0.0.0',
+ dest_addr: '1/0/50',
+ apdu: {
+ bitlength: 8,
+ tpci: 0,
+ apci: 'GroupValue_Write',
+ data: [10],
+ },
+ },
+ },
+ },
+
+ 'temperature response, apdu=2-byte': {
+ hexbuf: '061004200017040200002e00BCD0110B000F0300400730',
+ dgram: {
+ header_length: 6,
+ protocol_version: 16,
+ service_type: 1056,
+ total_length: 22,
+ tunnstate: {
+ header_length: 4,
+ channel_id: 2,
+ seqnum: 0,
+ rsvd: 0,
+ },
+ cemi: {
+ msgcode: 46,
+ addinfo_length: 0,
+ ctrl: {
+ frameType: 1,
+ reserved: 0,
+ repeat: 1,
+ broadcast: 1,
+ priority: 3,
+ acknowledge: 0,
+ confirm: 0,
+ destAddrType: 1,
+ hopCount: 5,
+ extendedFrame: 0,
+ },
+ src_addr: '1.1.11',
+ dest_addr: '0/0/15',
+ apdu: {
+ bitlength: 16,
+ tpci: 0,
+ apci: 'GroupValue_Response',
+ data: [0x07, 0x30],
+ },
+ },
+ },
+ },
+ }
+
+ Object.keys(tests).forEach((key, idx) => {
+ const testcase = tests[key]
+ const buf =
+ typeof testcase.hexbuf === 'string'
+ ? Buffer.from(testcase.hexbuf.replace(/\s/g, ''), 'hex')
+ : testcase.hexbuf
+ console.log('\n=== %s', key)
+ // marshal the test datagram
+ const writer = knxProto.createWriter()
+ writer.KNXNetHeader(testcase.dgram)
+ if (Buffer.compare(buf, writer.buffer) !== 0) {
+ // if this fails, unmarshal the buffer again to a datagram
+ const decoded = knxProto.parseDatagram(writer.buffer)
+ console.log(
+ '\n\n========\n FAIL: %s\n========\nbuffer is different:\n',
+ key,
+ )
+ console.log(
+ ' 0 1 2 3 4 5|6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9',
+ )
+ console.log('expected : %s', buf.toString('hex'))
+ console.log('got instead: %s', writer.buffer.toString('hex'))
+ console.log('decoded : %j', decoded)
+ }
+ t.ok(Buffer.compare(buf, writer.buffer) === 0)
+ })
+ t.end()
+})
diff --git a/test/wiredtests/test-connect-routing-hybrid.js b/test/wiredtests/test-connect-routing-hybrid.js
deleted file mode 100644
index d8eb7ab..0000000
--- a/test/wiredtests/test-connect-routing-hybrid.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-Error.stackTraceLimit = Infinity;
-
-const knx = require('../..');
-const address = require('../../src/Address.js');
-const assert = require('assert');
-const test = require('tape');
-
-const options = require('./wiredtest-options.js');
-
-/*
- ========== ==================
- this is a WIRED test and requires a real KNX IP router on the LAN
- ========== ==================
- to run all tests: $ WIREDTEST=1 npm test
- to run one test : $ WIREDTEST=1 node test/wiredtests/.js
-*/
-if (process.env.hasOwnProperty('WIREDTEST')) {
- //
- test('KNX connect routing hybrid', function(t) {
- var connection = knx.Connection({
- loglevel: 'debug',
- forceTunneling: true,
- handlers: {
- connected: function() {
- t.pass('connected in hybrid mode');
- t.end();
- process.exit(0);
- },
- error: function(connstatus) {
- t.fail('error connecting: '+connstatus);
- t.end();
- process.exit(1);
- }
- }
- });
- });
-
- setTimeout(function() {
- console.log('Exiting with timeout...');
- process.exit(2);
- }, 1000);
-}
diff --git a/test/wiredtests/test-connect-routing-hybrid.ts b/test/wiredtests/test-connect-routing-hybrid.ts
new file mode 100644
index 0000000..21587e1
--- /dev/null
+++ b/test/wiredtests/test-connect-routing-hybrid.ts
@@ -0,0 +1,38 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+import { KnxClient } from '../../src'
+import test from 'tape'
+
+Error.stackTraceLimit = Infinity
+
+/*
+ ========== ==================
+ this is a WIRED test and requires a real KNX IP router on the LAN
+ ========== ==================
+*/
+//
+test('KNX connect routing hybrid', function (t) {
+ const connection = new KnxClient({
+ loglevel: 'debug',
+ forceTunneling: true,
+ handlers: {
+ connected() {
+ t.pass('connected in hybrid mode')
+ t.end()
+ process.exit(0)
+ },
+ error(connstatus) {
+ t.fail(`error connecting: ${connstatus}`)
+ t.end()
+ process.exit(1)
+ },
+ },
+ })
+})
+
+setTimeout(function () {
+ console.log('Exiting with timeout...')
+ process.exit(2)
+}, 1000)
diff --git a/test/wiredtests/test-connect-tunnel.js b/test/wiredtests/test-connect-tunnel.js
deleted file mode 100644
index bbd1354..0000000
--- a/test/wiredtests/test-connect-tunnel.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-Error.stackTraceLimit = Infinity;
-
-const knx = require('../..');
-const address = require('../../src/Address.js');
-const assert = require('assert');
-const test = require('tape');
-
-const options = require('./wiredtest-options.js');
-
-/*
- ========== ==================
- this is a WIRED test and requires a real KNX IP router on the LAN
- ========== ==================
- to run all tests: $ WIREDTEST=1 npm test
- to run one test : $ WIREDTEST=1 node test/wiredtests/.js
-*/
-if (process.env.hasOwnProperty('WIREDTEST')) {
- //
- test('KNX connect tunneling', function(t) {
- var connection = knx.Connection({
- // set up your KNX IP router's IP address (not multicast!)
- // for getting into tunnelling mode
- ipAddr: options.ipAddr,
- physAddr: options.physAddr,
- debug: true,
- handlers: {
- connected: function() {
- console.log('----------');
- console.log('Connected!');
- console.log('----------');
- t.pass('connected in TUNNELING mode');
- this.Disconnect();
- },
- disconnected: function() {
- t.pass('disconnected in TUNNELING mode');
- t.end();
- process.exit(0);
- },
- error: function(connstatus) {
- t.fail('error connecting: '+connstatus);
- t.end();
- process.exit(1);
- }
- }
- });
- });
-
- setTimeout(function() {
- console.log('Exiting with timeout...');
- process.exit(2);
- }, 1000);
-}
diff --git a/test/wiredtests/test-connect-tunnel.ts b/test/wiredtests/test-connect-tunnel.ts
new file mode 100644
index 0000000..29ad5a6
--- /dev/null
+++ b/test/wiredtests/test-connect-tunnel.ts
@@ -0,0 +1,51 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { KnxClient } from '../../src'
+import test from 'tape'
+
+import options from './wiredtest-options'
+
+Error.stackTraceLimit = Infinity
+
+/*
+ ========== ==================
+ this is a WIRED test and requires a real KNX IP router on the LAN
+ ========== ==================
+*/
+//
+test('KNX connect tunneling', function (t) {
+ const connection = new KnxClient({
+ // set up your KNX IP router's IP address (not multicast!)
+ // for getting into tunnelling mode
+ ipAddr: options.ipAddr,
+ physAddr: options.physAddr,
+ debug: true,
+ handlers: {
+ connected() {
+ console.log('----------')
+ console.log('Connected!')
+ console.log('----------')
+ t.pass('connected in TUNNELING mode')
+ this.Disconnect()
+ },
+ disconnected() {
+ t.pass('disconnected in TUNNELING mode')
+ t.end()
+ process.exit(0)
+ },
+ error(connstatus) {
+ t.fail(`error connecting: ${connstatus}`)
+ t.end()
+ process.exit(1)
+ },
+ },
+ })
+})
+
+setTimeout(function () {
+ console.log('Exiting with timeout...')
+ process.exit(2)
+}, 1000)
diff --git a/test/wiredtests/test-control.js b/test/wiredtests/test-control.js
deleted file mode 100644
index 44ce019..0000000
--- a/test/wiredtests/test-control.js
+++ /dev/null
@@ -1,82 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-Error.stackTraceLimit = Infinity;
-
-const knx = require('../..');
-const address = require('../../src/Address.js');
-const assert = require('assert');
-const test = require('tape');
-
-const options = require('./wiredtest-options.js');
-/*
- ========== ==================
- this is a WIRED test and requires a real KNX IP router on the LAN
- ========== ==================
-
- $ WIREDTEST=1 node test/wiredtests/.js
-*/
-if (process.env.hasOwnProperty('WIREDTEST')) {
-
- test('KNX wired test - control a basic DPT1 binary switch', function(t) {
- var counter = 0;
- var connection = new knx.Connection({
- debug: true,
- physAddr: options.physAddr,
- handlers: {
- connected: function() {
- console.log('----------');
- console.log('Connected!');
- console.log('----------');
- var light = new knx.Datapoint({
- ga: options.wired_test_control_ga,
- dpt: 'DPT1.001'
- }, connection);
- light.on('change', () => {
- counter += 1;
- if (counter == 4) {
- t.pass('all 4 responses received');
- t.end();
- process.exit(0);
- }
- });
- // operation 1
- light.write(0);
- // operation 2
- setTimeout(function() {
- light.write(1);
- }, 500);
- // issue #71 - writing to an invalid address should not stall the FSM
- connection.write('10/10/5', 1);
- // operation 3 - Do the same with writeRaw
- setTimeout(function() {
- connection.writeRaw(options.wired_test_control_ga, new Buffer('00', 'hex'), 1);
- }, 1000);
- // operation 4 - Do the same with writeRaw
- setTimeout(function() {
- connection.writeRaw(options.wired_test_control_ga, new Buffer('01', 'hex'), 0);
- }, 1500);
- },
- event: function(evt, src, dest, value) {
- console.log("%s ===> %s <===, src: %j, dest: %j, value: %j",
- new Date().toISOString().replace(/T/, ' ').replace(/Z$/, ''),
- evt, src, dest, value
- );
- },
- error: function(connstatus) {
- console.log("%s **** ERROR: %j",
- new Date().toISOString().replace(/T/, ' ').replace(/Z$/, ''),
- connstatus);
- t.fail('error: '+connstatus);
- process.exit(1);
- }
- }
- });
- })
-
- setTimeout(function() {
- console.log('Exiting with timeout ...');
- process.exit(2);
- }, 2000);
-}
diff --git a/test/wiredtests/test-control.ts b/test/wiredtests/test-control.ts
new file mode 100644
index 0000000..47253cf
--- /dev/null
+++ b/test/wiredtests/test-control.ts
@@ -0,0 +1,99 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+import { KnxClient, Datapoint } from '../../src'
+import test from 'tape'
+
+import options from './wiredtest-options'
+
+Error.stackTraceLimit = Infinity
+/*
+ ========== ==================
+ this is a WIRED test and requires a real KNX IP router on the LAN
+ ========== ==================
+*/
+
+test('KNX wired test - control a basic DPT1 binary switch', function (t) {
+ let counter = 0
+ const connection = new KnxClient({
+ debug: true,
+ physAddr: options.physAddr,
+ handlers: {
+ connected() {
+ console.log('----------')
+ console.log('Connected!')
+ console.log('----------')
+ const light = new Datapoint(
+ {
+ ga: options.wired_test_control_ga,
+ dpt: 'DPT1.001',
+ },
+ connection,
+ )
+ light.on('change', () => {
+ counter += 1
+ if (counter === 4) {
+ t.pass('all 4 responses received')
+ t.end()
+ process.exit(0)
+ }
+ })
+ // operation 1
+ light.write(0)
+ // operation 2
+ setTimeout(function () {
+ light.write(1)
+ }, 500)
+ // issue #71 - writing to an invalid address should not stall the FSM
+ connection.write('10/10/5', 1)
+ // operation 3 - Do the same with writeRaw
+ setTimeout(function () {
+ connection.writeRaw(
+ options.wired_test_control_ga,
+ Buffer.from('00', 'hex'),
+ 1,
+ )
+ }, 1000)
+ // operation 4 - Do the same with writeRaw
+ setTimeout(function () {
+ connection.writeRaw(
+ options.wired_test_control_ga,
+ Buffer.from('01', 'hex'),
+ 0,
+ )
+ }, 1500)
+ },
+ event(evt, src, dest, value) {
+ console.log(
+ '%s ===> %s <===, src: %j, dest: %j, value: %j',
+ new Date()
+ .toISOString()
+ .replace(/T/, ' ')
+ .replace(/Z$/, ''),
+ evt,
+ src,
+ dest,
+ value,
+ )
+ },
+ error(connstatus) {
+ console.log(
+ '%s **** ERROR: %j',
+ new Date()
+ .toISOString()
+ .replace(/T/, ' ')
+ .replace(/Z$/, ''),
+ connstatus,
+ )
+ t.fail(`error: ${connstatus}`)
+ process.exit(1)
+ },
+ },
+ })
+})
+
+setTimeout(function () {
+ console.log('Exiting with timeout ...')
+ process.exit(2)
+}, 2000)
diff --git a/test/wiredtests/test-logotimer.js b/test/wiredtests/test-logotimer.js
deleted file mode 100644
index 87e9625..0000000
--- a/test/wiredtests/test-logotimer.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const knx = require('../..');
-const test = require('tape');
-const util = require('util');
-const options = require('./wiredtest-options.js');
-
-/*
- ========== ==================
- this is a WIRED test and requires a real KNX IP router on the LAN
- ========== ==================
- to run all tests: $ WIREDTEST=1 npm test
- to run one test : $ WIREDTEST=1 node test/wiredtests/.js
-*/
-if (process.env.hasOwnProperty('WIREDTEST')) {
- test('KNX wired test - control a DPT9 timer', function(t) {
- var connection = new knx.Connection( {
- //debug: true,
- handlers: {
- connected: () => {
- var timer_control = new knx.Datapoint({ga: options.dpt9_timer_control_ga, dpt: 'DPT9.001', autoread: true}, connection);
- var timer_status = new knx.Datapoint({ga: options.dpt9_timer_status_ga, dpt: 'DPT9.001', autoread: true}, connection);
- timer_control.on('change', function(oldvalue, newvalue) {
- t.pass(util.format("**** Timer control changed from: %j to: %j", oldvalue, newvalue));
- });
- timer_status.read(function(src, response) {
- t.pass(util.format("**** Timer status response: %j", response));
- t.end();
- process.exit(0);
- });
- timer_control.write(12);
- }
- }
- });
- });
-
- setTimeout(function () {
- process.exit(1);
- }, 1000);
-}
diff --git a/test/wiredtests/test-logotimer.ts b/test/wiredtests/test-logotimer.ts
new file mode 100644
index 0000000..069d73e
--- /dev/null
+++ b/test/wiredtests/test-logotimer.ts
@@ -0,0 +1,60 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+import { KnxClient, Datapoint } from '../../src'
+import test from 'tape'
+import util from 'util'
+import options from './wiredtest-options'
+
+/*
+ ========== ==================
+ this is a WIRED test and requires a real KNX IP router on the LAN
+ ========== ==================
+*/
+test('KNX wired test - control a DPT9 timer', function (t) {
+ const connection = new KnxClient({
+ // debug: true,
+ handlers: {
+ connected: () => {
+ const timer_control = new Datapoint(
+ {
+ ga: options.dpt9_timer_control_ga,
+ dpt: 'DPT9.001',
+ autoread: true,
+ },
+ connection,
+ )
+ const timer_status = new Datapoint(
+ {
+ ga: options.dpt9_timer_status_ga,
+ dpt: 'DPT9.001',
+ autoread: true,
+ },
+ connection,
+ )
+ timer_control.on('change', function (oldvalue, newvalue) {
+ t.pass(
+ util.format(
+ '**** Timer control changed from: %j to: %j',
+ oldvalue,
+ newvalue,
+ ),
+ )
+ })
+ timer_status.read(function (src, response) {
+ t.pass(
+ util.format('**** Timer status response: %j', response),
+ )
+ t.end()
+ process.exit(0)
+ })
+ timer_control.write(12)
+ },
+ },
+ })
+})
+
+setTimeout(function () {
+ process.exit(1)
+}, 1000)
diff --git a/test/wiredtests/test-read.js b/test/wiredtests/test-read.js
deleted file mode 100644
index 993fc0d..0000000
--- a/test/wiredtests/test-read.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const knx = require('../..');
-const test = require('tape');
-const util = require('util');
-const options = require('./wiredtest-options.js');
-
-/*
- ========== ==================
- this is a WIRED test and requires a real KNX IP router on the LAN
- ========== ==================
- to run all tests: $ WIREDTEST=1 npm test
- to run one test : $ WIREDTEST=1 node test/wiredtests/.js
-*/
-if (process.env.hasOwnProperty('WIREDTEST')) {
- test('KNX wired test - read a temperature', function(t) {
- var connection = new knx.Connection({
- debug: true,
- physAddr: options.physAddr,
- handlers: {
- connected: function() {
- // just define a temperature GA that should respond to a a GroupValue_Read request
- var temperature_in = new knx.Datapoint({
- ga: options.dpt9_temperature_status_ga,
- dpt: 'DPT9.001'
- }, connection);
- temperature_in.read(function(src, response) {
- console.log("KNX response from %s: %j", src, response);
- t.pass(util.format('read temperature: %s', response));
- t.end();
- process.exit(0);
- });
- },
- error: function(connstatus) {
- console.log("%s **** ERROR: %j",
- new Date().toISOString().replace(/T/, ' ').replace(/Z$/, ''),
- connstatus);
- process.exit(1);
- }
- }
- });
- });
-
- setTimeout(function() {
- console.log('Exiting ...');
- process.exit(2);
- }, 1500);
-}
diff --git a/test/wiredtests/test-read.ts b/test/wiredtests/test-read.ts
new file mode 100644
index 0000000..32e35e6
--- /dev/null
+++ b/test/wiredtests/test-read.ts
@@ -0,0 +1,55 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { KnxClient, Datapoint } from '../../src'
+import test from 'tape'
+import util from 'util'
+import options from './wiredtest-options'
+
+/*
+ ========== ==================
+ this is a WIRED test and requires a real KNX IP router on the LAN
+ ========== ==================
+*/
+test('KNX wired test - read a temperature', function (t) {
+ const connection = new KnxClient({
+ debug: true,
+ physAddr: options.physAddr,
+ handlers: {
+ connected() {
+ // just define a temperature GA that should respond to a a GroupValue_Read request
+ const temperature_in = new Datapoint(
+ {
+ ga: options.dpt9_temperature_status_ga,
+ dpt: 'DPT9.001',
+ },
+ connection,
+ )
+ temperature_in.read(function (src, response) {
+ console.log('KNX response from %s: %j', src, response)
+ t.pass(util.format('read temperature: %s', response))
+ t.end()
+ process.exit(0)
+ })
+ },
+ error(connstatus) {
+ console.log(
+ '%s **** ERROR: %j',
+ new Date()
+ .toISOString()
+ .replace(/T/, ' ')
+ .replace(/Z$/, ''),
+ connstatus,
+ )
+ process.exit(1)
+ },
+ },
+ })
+})
+
+setTimeout(function () {
+ console.log('Exiting ...')
+ process.exit(2)
+}, 1500)
diff --git a/test/wiredtests/test-readstorm.js b/test/wiredtests/test-readstorm.js
deleted file mode 100644
index 145163d..0000000
--- a/test/wiredtests/test-readstorm.js
+++ /dev/null
@@ -1,77 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-const knx = require('../..');
-const test = require('tape');
-const util = require('util');
-const options = require('./wiredtest-options.js');
-
-Error.stackTraceLimit = Infinity;
-
-/*
- ========== ==================
- this is a WIRED test and requires a real KNX IP router on the LAN
- ========== ==================
- to run all tests: $ WIREDTEST=1 npm test
- to run one test : $ WIREDTEST=1 node test/wiredtests/.js
-*/
-if (process.env.hasOwnProperty('WIREDTEST')) {
- test('KNX wired test - read multiple statuses from a consecutive GA range', function(t) {
- var readback = {};
- function setupDatapoint(groupadress, statusga) {
- var dp = new knx.Datapoint({
- ga: groupadress,
- status_ga: statusga,
- dpt: "DPT1.001",
- autoread: true
- }, connection);
- dp.on('change', (oldvalue, newvalue) => {
- console.log("**** %s current value: %j", groupadress, newvalue);
- });
- return dp;
- }
- function setupDatapoints() {
- var ctrl_ga_arr = options.readstorm_control_ga_start.split('/');
- var stat_ga_arr = options.readstorm_status_ga_start.split('/');
- for (i=0; i< options.readstorm_range; i++) {
- var ctrl_ga = [ctrl_ga_arr[0], ctrl_ga_arr[1], i+parseInt(ctrl_ga_arr[2])].join('/');
- var stat_ga = [stat_ga_arr[0], stat_ga_arr[1], i+parseInt(stat_ga_arr[2])].join('/');
- setupDatapoint(ctrl_ga, stat_ga);
- }
- }
- var connection = knx.Connection({
- loglevel: 'warn',
- //forceTunneling: true,
-// minimumDelay: 100,
- handlers: {
- connected: function() {
- setupDatapoints();
- },
- event: function (evt, src, dest, value) {
- if (evt == 'GroupValue_Response') {
- readback[dest] = [src, value];
- // have we got responses from all the read requests for all datapoints?
- if (Object.keys(readback).length == options.readstorm_range) {
- t.pass(util.format('readstorm: all %d datapoints accounted for', options.readstorm_range));
- t.end();
- process.exit(0);
- }
- }
- },
- error: function(connstatus) {
- console.log("%s **** ERROR: %j",
- new Date().toISOString().replace(/T/, ' ').replace(/Z$/, ''),
- connstatus);
- process.exit(1);
- }
- }
- });
- });
-
- setTimeout(function() {
- console.log('Exiting with timeout...');
- process.exit(2);
- }, 1500);
-}
diff --git a/test/wiredtests/test-readstorm.ts b/test/wiredtests/test-readstorm.ts
new file mode 100644
index 0000000..be6cd63
--- /dev/null
+++ b/test/wiredtests/test-readstorm.ts
@@ -0,0 +1,96 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+import { KnxClient, Datapoint } from '../../src'
+import test from 'tape'
+import util from 'util'
+import options from './wiredtest-options.js'
+
+Error.stackTraceLimit = Infinity
+
+/*
+ ========== ==================
+ this is a WIRED test and requires a real KNX IP router on the LAN
+ ========== ==================
+*/
+test('KNX wired test - read multiple statuses from a consecutive GA range', function (t) {
+ const readback = {}
+ function setupDatapoint(groupadress: string, statusga: string) {
+ const dp = new Datapoint(
+ {
+ ga: groupadress,
+ status_ga: statusga,
+ dpt: 'DPT1.001',
+ autoread: true,
+ },
+ connection,
+ )
+ dp.on('change', (oldvalue, newvalue) => {
+ console.log('**** %s current value: %j', groupadress, newvalue)
+ })
+ return dp
+ }
+ function setupDatapoints() {
+ const ctrl_ga_arr = options.readstorm_control_ga_start.split('/')
+ const stat_ga_arr = options.readstorm_status_ga_start.split('/')
+ for (let i = 0; i < options.readstorm_range; i++) {
+ const ctrl_ga = [
+ ctrl_ga_arr[0],
+ ctrl_ga_arr[1],
+ i + parseInt(ctrl_ga_arr[2]),
+ ].join('/')
+ const stat_ga = [
+ stat_ga_arr[0],
+ stat_ga_arr[1],
+ i + parseInt(stat_ga_arr[2]),
+ ].join('/')
+ setupDatapoint(ctrl_ga, stat_ga)
+ }
+ }
+ const connection = new KnxClient({
+ loglevel: 'warn',
+ // forceTunneling: true,
+ // minimumDelay: 100,
+ handlers: {
+ connected() {
+ setupDatapoints()
+ },
+ event(evt, src, dest, value) {
+ if (evt === 'GroupValue_Response') {
+ readback[dest] = [src, value]
+ // have we got responses from all the read requests for all datapoints?
+ if (
+ Object.keys(readback).length === options.readstorm_range
+ ) {
+ t.pass(
+ util.format(
+ 'readstorm: all %d datapoints accounted for',
+ options.readstorm_range,
+ ),
+ )
+ t.end()
+ process.exit(0)
+ }
+ }
+ },
+ error(connstatus) {
+ console.log(
+ '%s **** ERROR: %j',
+ new Date()
+ .toISOString()
+ .replace(/T/, ' ')
+ .replace(/Z$/, ''),
+ connstatus,
+ )
+ process.exit(1)
+ },
+ },
+ })
+})
+
+setTimeout(function () {
+ console.log('Exiting with timeout...')
+ process.exit(2)
+}, 1500)
diff --git a/test/wiredtests/wiredtest-options.js b/test/wiredtests/wiredtest-options.js
deleted file mode 100644
index 26620bc..0000000
--- a/test/wiredtests/wiredtest-options.js
+++ /dev/null
@@ -1,29 +0,0 @@
-/**
-* knx.js - a KNX protocol stack in pure Javascript
-* (C) 2016-2018 Elias Karakoulakis
-*/
-
-/*
- define the required options for running wired tests
-*/
-module.exports = {
- loglevel: 'trace',
- // your KNX IP router UNICAST ip address
- ipAddr: '192.168.8.4',
- // the physical address used by the wired tests
- physAddr: '14.14.14',
- // a DPT1 group address pair (binary status) to test write/read/respond
- dpt1_status_ga: '1/1/1',
- dpt1_control_ga: '1/1/101',
- // a DPT9 group address (temperature) that should be able to respond to a GroupValue_Read request
- dpt9_temperature_status_ga: '0/0/15',
- // a DPT 9 control and its status GA
- dpt9_timer_control_ga: '4/1/4',
- dpt9_timer_status_ga: '4/1/3',
- // a DPT1 group address that should also be able to respond to a GroupValue_Read request
- wired_test_control_ga: '5/0/0',
- // read storm test: read back statuses from an actuator with multiple relays
- readstorm_control_ga_start: '1/1/0',
- readstorm_status_ga_start: '1/1/100',
- readstorm_range: 8
-}
diff --git a/test/wiredtests/wiredtest-options.ts b/test/wiredtests/wiredtest-options.ts
new file mode 100644
index 0000000..7ab95db
--- /dev/null
+++ b/test/wiredtests/wiredtest-options.ts
@@ -0,0 +1,31 @@
+/**
+ * knx.js - a KNX protocol stack in pure Javascript
+ * (C) 2016-2018 Elias Karakoulakis
+ */
+
+/*
+ define the required options for running wired tests
+*/
+const options = {
+ loglevel: 'trace',
+ // your KNX IP router UNICAST ip address
+ ipAddr: '192.168.8.4',
+ // the physical address used by the wired tests
+ physAddr: '14.14.14',
+ // a DPT1 group address pair (binary status) to test write/read/respond
+ dpt1_status_ga: '1/1/1',
+ dpt1_control_ga: '1/1/101',
+ // a DPT9 group address (temperature) that should be able to respond to a GroupValue_Read request
+ dpt9_temperature_status_ga: '0/0/15',
+ // a DPT 9 control and its status GA
+ dpt9_timer_control_ga: '4/1/4',
+ dpt9_timer_status_ga: '4/1/3',
+ // a DPT1 group address that should also be able to respond to a GroupValue_Read request
+ wired_test_control_ga: '5/0/0',
+ // read storm test: read back statuses from an actuator with multiple relays
+ readstorm_control_ga_start: '1/1/0',
+ readstorm_status_ga_start: '1/1/100',
+ readstorm_range: 8,
+}
+
+export default options
diff --git a/tsconfig.build.json b/tsconfig.build.json
new file mode 100644
index 0000000..160c3e4
--- /dev/null
+++ b/tsconfig.build.json
@@ -0,0 +1,5 @@
+{
+ "extends": "./tsconfig.json",
+ "exclude": ["node_modules", "test", "build", "manualtest"],
+ }
+
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..0337e72
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "module": "commonjs",
+ "declaration": true,
+ "removeComments": true,
+ "emitDecoratorMetadata": true,
+ "experimentalDecorators": true,
+ "allowSyntheticDefaultImports": true,
+ "target": "es2017",
+ "sourceMap": true,
+ "outDir": "./build",
+ "baseUrl": "./",
+ "incremental": true,
+ "skipLibCheck": true,
+ "preserveSymlinks": true,
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "typeRoots": [
+ "node_modules/@types",
+ "src/types"
+ ],
+ "types": [
+ "node"
+ ],
+ },
+ "include": [
+ "src",
+ "test",
+ "manualtest"
+ ],
+ "exclude": [
+ "typescript-sample",
+ ]
+}
\ No newline at end of file
diff --git a/typescript-sample/package-lock.json b/typescript-sample/package-lock.json
new file mode 100644
index 0000000..a8352c9
--- /dev/null
+++ b/typescript-sample/package-lock.json
@@ -0,0 +1,453 @@
+{
+ "name": "typescript-sample",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "typescript-sample",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "knx": "../",
+ "tslint": "^5.1.0",
+ "typescript": "^2.2.2"
+ }
+ },
+ "..": {
+ "version": "2.5.4",
+ "license": "MIT",
+ "dependencies": {
+ "binary-parser": "^2.2.1",
+ "binary-protocol": "0.0.0",
+ "ipaddr.js": "2.1.0",
+ "log-driver": "1.2.7",
+ "machina": "^4.0.2"
+ },
+ "devDependencies": {
+ "@types/node": "^20.12.3",
+ "@types/tape": "^5.6.4",
+ "@typescript-eslint/eslint-plugin": "^7.5.0",
+ "@typescript-eslint/parser": "^7.5.0",
+ "esbuild-register": "^3.5.0",
+ "eslint": "^8.57.0",
+ "eslint-config-airbnb-base": "^15.0.0",
+ "eslint-config-airbnb-typescript": "^18.0.0",
+ "eslint-config-prettier": "^9.1.0",
+ "eslint-plugin-import": "^2.29.1",
+ "eslint-plugin-prettier": "^5.1.3",
+ "prettier": "^3.2.5",
+ "tape": "^5.7.5",
+ "typescript": "^5.4.3"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.24.2",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz",
+ "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==",
+ "dependencies": {
+ "@babel/highlight": "^7.24.2",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.24.2",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.2.tgz",
+ "integrity": "sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/builtin-modules": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz",
+ "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
+ "node_modules/commander": {
+ "version": "2.20.3",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/is-core-module": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+ "dependencies": {
+ "hasown": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/knx": {
+ "resolved": "..",
+ "link": true
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
+ "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
+ "dependencies": {
+ "minimist": "^1.2.6"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ },
+ "node_modules/tslint": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz",
+ "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "builtin-modules": "^1.1.1",
+ "chalk": "^2.3.0",
+ "commander": "^2.12.1",
+ "diff": "^4.0.1",
+ "glob": "^7.1.1",
+ "js-yaml": "^3.13.1",
+ "minimatch": "^3.0.4",
+ "mkdirp": "^0.5.1",
+ "resolve": "^1.3.2",
+ "semver": "^5.3.0",
+ "tslib": "^1.8.0",
+ "tsutils": "^2.29.0"
+ },
+ "bin": {
+ "tslint": "bin/tslint"
+ },
+ "engines": {
+ "node": ">=4.8.0"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev"
+ }
+ },
+ "node_modules/tsutils": {
+ "version": "2.29.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz",
+ "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==",
+ "dependencies": {
+ "tslib": "^1.8.1"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "2.9.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz",
+ "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ }
+ }
+}
diff --git a/typescript-sample/test-toggle-onoff.ts b/typescript-sample/test-toggle-onoff.ts
index 95ab9fa..16ef59a 100644
--- a/typescript-sample/test-toggle-onoff.ts
+++ b/typescript-sample/test-toggle-onoff.ts
@@ -3,11 +3,11 @@
* (C) 2016-2017 Elias Karakoulakis
*/
-import * as knx from 'knx'
+import { KnxClient, Datapoint } from 'knx'
var groupAddress = process.argv[2]
-var connection = new knx.Connection({
+var connection = new KnxClient({
ipAddr: process.env.KNXGW,
handlers: {
connected: onConnected
@@ -16,7 +16,7 @@ var connection = new knx.Connection({
async function onConnected() {
console.log('Connected')
- var dp = new knx.Datapoint({
+ var dp = new Datapoint({
ga: groupAddress,
dpt: 'DPT1.001'
}, connection)