Initial commit.

This commit is contained in:
2022-03-09 22:38:02 +01:00
commit a998ec05ee
63 changed files with 7890 additions and 0 deletions

232
src/DPT1.ts Normal file
View File

@@ -0,0 +1,232 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT1 implements DPT {
id = '1';
name = '1-bit value';
bufferLength = 1;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the on/off value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
const val = buffer.readUInt8(0);
if (val !== 0 && val !== 1) {
throw new InvalidValueError(`Invalid binary value ${val} for DPT1. Expected 1 or 0`);
}
return val;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (value !== 0 && value !== 1) {
throw new InvalidValueError(`Invalid value ${value} for a DPT1. Should be 0 or 1.`);
}
const buf = Buffer.alloc(this.bufferLength);
buf.writeUInt8(value, 0);
return buf;
}
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: '???' },
},
};
}

92
src/DPT10.ts Normal file
View File

@@ -0,0 +1,92 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
const timeRegexp = /(\d{1,2}):(\d{1,2}):(\d{1,2})/;
export class DPT10 implements DPT {
id = '10';
name = 'day of week + time of day';
bufferLength = 3;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): Date {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
const [dnh, minutes, seconds] = buffer;
const dow = (dnh & 0b11100000) >> 5;
const hours = dnh & 0b00011111;
if (hours < 0 || hours > 23)
throw new InvalidValueError(`Hours out of range: Expected [0-23], got ${hours}`)
if (minutes < 0 || minutes > 59)
throw new InvalidValueError(`Minutes out of range: Expected [0-59], got ${minutes}`)
if (seconds < 0 || seconds > 59)
throw new InvalidValueError(`Seconds out of range: Expected [0-59], got ${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);
// reset the milliseconds
d.setMilliseconds(0)
return d;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: Date | number | string): Buffer {
if (value === undefined || value === null)
throw new InvalidValueError(`Must supply a Date, number or String for DPT10 time.`);
let dow, hour, minute, second;
// day of week. NOTE: JS Sunday = 0
switch (typeof value) {
case 'string':
// try to parse
let match = timeRegexp.exec(value);
if (match) {
dow = ((new Date().getDay() - 7) % 7) + 7;
hour = parseInt(match[1]);
minute = parseInt(match[2]);
second = parseInt(match[3]);
} else {
throw new InvalidValueError(`DPT10: invalid time format (${value})`);
}
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]);
}
subtypes: {
// 10.001 time of day
'001': {
name: 'DPT_TimeOfDay',
desc: 'time of day',
},
};
}

77
src/DPT11.ts Normal file
View File

@@ -0,0 +1,77 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT11 implements DPT {
id = '11';
name = '3-byte date value';
bufferLength = 3;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): Date {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
const day = buffer[0] & 31; //0b00011111);
const month = buffer[1] & 15; //0b00001111);
let year = buffer[2] & 127; //0b01111111);
year = year + (year > 89 ? 1900 : 2000);
if (
day < 1 ||
day > 31 ||
month < 1 ||
month > 12 ||
year < 1990 ||
year > 2089
) {
throw new InvalidValueError(`${buffer} => ${day}/${month}/${year} is not valid date according to DPT11, setting to 1990/01/01`);
}
return new Date(year, month - 1, day);
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: Date | string): Buffer {
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()))
throw new InvalidValueError('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),
]);
}
subtypes: {
// 11.001 date
'001': {
name: 'DPT_Date',
desc: 'Date',
},
};
}

48
src/DPT12.ts Normal file
View File

@@ -0,0 +1,48 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
import { buffer } from 'stream/consumers';
export class DPT12 implements DPT {
id = '12';
name = '4-byte unsigned value';
bufferLength = 4
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
return buffer.readUInt32BE(0);
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (!value)
throw new InvalidValueError('Cannot write null value');
let result = Buffer.alloc(this.bufferLength)
result.writeUInt32BE(value)
return result;
}
subtypes: {
// 12.001 counter pulses
"001": {
"name": "DPT_Value_4_Ucount",
"desc": "counter pulses"
}
};
}

102
src/DPT13.ts Normal file
View File

@@ -0,0 +1,102 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT13 implements DPT {
id = '13';
name = '4-byte signed value';
bufferLength = 4;
range = [-Math.pow(2, 31), Math.pow(2, 31) - 1]
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
// In 4 bytes, there's no way to exceed the range
return buffer.readInt32BE(0)
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (value === undefined || value === null || Number.isFinite(value))
throw new InvalidValueError(`Value is not viable`)
if (Number.isInteger(value))
throw new InvalidValueError(`Value ${value} is not an integer`)
if (value < this.range[0] || value > this.range[1])
throw new InvalidValueError(`Value ${value} is out of range`)
let result = Buffer.alloc(this.bufferLength)
result.writeInt32BE(value)
return result;
}
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"
},
};
}

169
src/DPT14.ts Normal file
View File

@@ -0,0 +1,169 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT14 implements DPT {
id = '14';
name = '32-bit floating point value';
bufferLength = 4;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
return buffer.readFloatBE(0);
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (!value || typeof value != 'number')
throw new InvalidValueError(`Invalid value [${value}]`)
const apdu_data = Buffer.alloc(this.bufferLength);
apdu_data.writeFloatBE(value, 0);
return apdu_data;
}
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',
},
};
}

100
src/DPT15.ts Normal file
View File

@@ -0,0 +1,100 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
/**
* U 4 bit
* V 4 bit
* W 4 bit
* X 4 bit
* Y 4 bit
* Z 4 bit
* E 1 bit
* P 1 bit
* D 1 bit
* C 1 bit
* N 4 bit
*
* U,V,W,X,Y,Z = [0 … 9]; E,P,D,C = {0,1}; N = [0 … 15]
*
* Source: https://www.promotic.eu/en/pmdoc/Subsystems/Comm/PmDrivers/KNXDTypes.htm
*/
export interface DPT15Result {
U: number
V: number
W: number
X: number
Y: number
Z: number
E: number
P: number
D: number
C: number
N: number
}
export class DPT15 implements DPT {
id = '';
name = '4-byte access control data';
bufferLength = 4;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): DPT15Result {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
const result: DPT15Result = {
U: (buffer[0] & 0xf0) >> 4,
V: (buffer[0] & 0x0f),
W: (buffer[1] & 0xf0) >> 4,
X: (buffer[1] & 0x0f),
Y: (buffer[2] & 0xf0) >> 4,
Z: (buffer[2] & 0x0f),
E: (buffer[3] >> 7) & 0x01,
P: (buffer[3] >> 6) & 0x01,
D: (buffer[3] >> 5) & 0x01,
C: (buffer[3] >> 4) & 0x01,
N: (buffer[3] & 0x0f),
}
if (Math.max(result.U, result.V, result.W, result.X, result.Y, result.Z) > 9)
throw new InvalidValueError(`Value must be < 9 (U: ${result.U}, V: ${result.V}, W: ${result.W}, X: ${result.X}, y: ${result.Y}, Z: ${result.Z})`)
return result;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: DPT15Result): Buffer {
if (value === undefined || value === null)
throw new InvalidValueError(`Invalid value [${value}]`)
if (Math.max(value.U, value.V, value.W, value.X, value.Y, value.Z) > 9)
throw new InvalidValueError(`Value must be < 9 (U: ${value.U}, V: ${value.V}, W: ${value.W}, X: ${value.X}, y: ${value.Y}, Z: ${value.Z})`)
const apdu_data = Buffer.alloc(this.bufferLength);
apdu_data.writeUInt8((value.U << 4) + value.V, 0);
apdu_data.writeUInt8((value.W << 4) + value.X, 1);
apdu_data.writeUInt8((value.Y << 4) + value.Z, 2);
apdu_data.writeUInt8((value.E << 7) + (value.P << 6) + (value.D << 5) + (value.C << 4) + value.N, 3);
return apdu_data;
}
subtypes: {
"000": {
name: "DPT_Access_Data",
desc: "default field"
}
};
}

59
src/DPT16.ts Normal file
View File

@@ -0,0 +1,59 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT16 implements DPT {
id = '16';
name = '14-character string';
bufferLength = 14;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): string {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
return buffer.toString('ascii');
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: string): Buffer {
if (typeof value !== 'string')
throw new InvalidValueError('Must supply a string value')
const buf = Buffer.alloc(14);
buf.write(value, 'ascii');
return buf;
}
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',
},
};
}

52
src/DPT17.ts Normal file
View File

@@ -0,0 +1,52 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT17 implements DPT {
id = '17';
name = 'scene number';
bufferLength = 1;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
// Scene number between 0 and 63
return buffer.readUInt8(0) & 0b00111111;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (value === undefined || value === null)
throw new InvalidValueError(`Invalid value [${value}]`)
if (value > 63)
throw new InvalidValueError(`Expected scene number [0, 63]. Got [${value}]`)
let buf = Buffer.alloc(this.bufferLength)
buf.writeUInt8(value, 0)
return buf;
}
subtypes: {
// 17.001 Scene number
"001": {
use: "G",
name: "DPT_SceneNumber", desc: "Scene Number",
},
};
}

62
src/DPT18.ts Normal file
View File

@@ -0,0 +1,62 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export interface DPT18Result {
activateLearn: number
// pad: number // reserved
sceneNumber: number
}
export class DPT18 implements DPT {
id = '18';
name = '8-bit Scene Activate/Learn + number';
bufferLength = 1;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): DPT18Result {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
return {
activateLearn: (buffer[0] & 0b10000000) >> 7,
sceneNumber: buffer[0] & 0b00111111
};
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: DPT18Result): Buffer {
if (value === undefined || value === null)
throw new InvalidValueError(`Invalid value [${value}]`)
if (value.sceneNumber > 63)
throw new InvalidValueError(`Expected scene number [0..63]. Got [${value.sceneNumber}]`)
if (value.activateLearn > 1)
throw new InvalidValueError(`Expected scene number [0, 1]. Got [${value.activateLearn}]`)
let buf = Buffer.alloc(this.bufferLength)
buf.writeUInt8((value.activateLearn << 7) + value.sceneNumber)
return buf;
}
subtypes: {
// 9.001 temperature (oC)
"001": {
name: "DPT_SceneControl", desc: "scene control"
}
};
}

226
src/DPT19.ts Normal file
View File

@@ -0,0 +1,226 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
/**
* <strong>19.001</strong> Date & Time
*
* <pre>
* +-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+
* Field Names | (Year) | 0 0 0 0 (Month) |
* Encoding | U U U U U U U U | r r r r U U U U |
* +-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+
* | 0 0 0 (Day Of Month) | (DayOfWeek) (Hour) |
* | r r r U U U U U | U U U U U U U U |
* +-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+
* | 0 0 (Minutes) | 0 0 (Seconds) |
* | r r U U U U U U | r r U U U U U U |
* +-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+
* | F WD NWD NY ND NDoW NT SST| CLQ SRC 0 0 0 0 0 0 |
* | B B B B B B B B | B B r r r r r r |
* +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+
* Format: 8 octets (U<sub>8</sub> [r<sub>4</sub>U<sub>4</sub>] [r<sub>3</sub>U<sub>5</sub>] [r<sub>3</sub>U<sub>5</sub>] [r<sub>2</sub>U<sub>6</sub>] [r<sub>2</sub>U<sub>6</sub>] B<sub>16</sub>)
* Encoding:
* Year = [0 .. 255]
* 0 = year 1900
* 255 = year 2155
* Month = [1 .. 12]
* DayOfMonth = [1 .. 31]
* DayOfWeek = [0 .. 7]
* 1 = Monday, 2 = Tuesday, 3 = Wednesday, 4 = Thursday, 5 = Friday, 6 = Saturday, 7 = Sunday, 0 = any day
* Hour = [0 .. 24]
* Minutes = [0 .. 59]
* Seconds = [0 .. 59]
* (F) Fault = {0, 1}
* 0 = Normal (no fault)
* 1 = Fault
* (WD) WorkingDay = {0, 1}
* 0 = No Working Day
* 1 = Working Day
* (NWD) NoWorkingDay = {0, 1}
* 0 = WorkingDay field valid
* 1 = WorkingDay field not valid
* (NY) NoYear = {0, 1}
* 0 = Year field valid
* 1 = Year field not valid
* (ND) NoDate = {0, 1}
* 0 = Month and DayOfMonth fields valid
* 1 = Month and DayOfMonth fields not valid
* (NDoW) NoDayOfWeek = {0, 1}
* 0 = DayOfWeek field valid
* 1 = DayOfWeek field not valid
* (NT) NoTime = {0, 1}
* 0 = Hour, Minutes and Seconds valid
* 1 = Hour, Minutes and Seconds not valid
* (SST) Standard Summer Time = {0, 1}
* 0 = UTC+x (standard time)
* 1 = UTC+x +1h (summer daylight saving time)
* (CLQ) QualityOfClock = {0, 1}
* 0 = Clock without external synchronization signal
* 1 = Clock with external synchronization signal (DCF 77, VideoText, ...)
* (SRC) SynchronisationSourceReliability = {0, 1}
* 0 = Unreliable Synchronisation (mains, local quartz)
* 1 = Reliable Synchronisation (radio, internet)
* </pre>
* <p>
* The encoding of the hour is within the range [0 .. 24] instead of [0 .. 23]. When the hour is set to "24", the
* values of octet 3 (Minutes) and 2 (Seconds) have to be set to zero.
* <p>
* "Fault" is set if one ore more supported fields of the Date & Time information are corrupted. "Fault" is set e.g.
* power-down if battery backup was not sufficient, after 1st start up of device (clock un-configured) or radio-clock
* (DCF 77) had no reception for a very long time. "Fault" is usually cleared automatically by the device if the
* local clock is set or clock data is refreshed
* <p>
* The receiver (e.g. a room unit, MMI) will interpret Date&Time with "Fault" as corrupted and will either ignore
* the message or show --:--:-- or blinking 00:00:00 (as known from Video recorders after power-up).
*/
export class DPT19Result {
dateTime: Date = new Date()
dayOfWeek: number = undefined
f: boolean = false
wd: boolean = false
nwd: boolean = false
ny: boolean = false
nd: boolean = false
ndow: boolean = false
nt: boolean = false
sst: boolean = false
internalClock: boolean = true // Assume external clock
reliability: boolean = false;
}
export class DPT19 implements DPT {
id = '19';
name = '8-byte Date+Time';
bufferLength = 8;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): DPT19Result {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
const byte8 = buffer.readUInt8(0)
const byte7 = buffer.readUInt8(1)
const byte6 = buffer.readUInt8(2)
const byte5 = buffer.readUInt8(3)
const byte4 = buffer.readUInt8(4)
const byte3 = buffer.readUInt8(5)
const byte2 = buffer.readUInt8(6)
const byte1 = buffer.readUInt8(7)
let year = byte8
let month = byte7 & 0xF
const dayOfMonth = byte6 & 0x1F
let dayOfWeek = byte5 >> 5
const hourOfDay = byte5 & 0x1F
const minutes = byte4 & 0x3F
const seconds = byte3 & 0x3F
const f = !!(byte2 & 0x80)
const wd = !!(byte2 & 0x40)
const nwd = !!(byte2 & 0x20)
const ny = !!(byte2 & 0x10)
const nd = !!(byte2 & 0x8)
const ndow = !!(byte2 & 0x4)
const nt = !!(byte2 & 0x2)
const suti = !!(byte2 & 0x1)
const clq = !!(byte1 & 0x80)
const reliability = !!(byte1 & 0x40)
year += 1900
// Convert month from knx to JavaScript (12 => 11, 11 => 10, ...)
month -= 1
// Convert day of week from knx to JavaScript (7 => 0, 0 => undefined)
if (dayOfWeek === 7) {
dayOfWeek = 0
} else if (dayOfWeek === 0) {
dayOfWeek = undefined
}
// Check minutes and seconds if hours equals 24
if (hourOfDay === 24 && (minutes !== 0 || seconds !== 0)) {
throw new RangeError('Invalid time (hour of day is 24, but minutes or seconds are not 0)')
}
return {
dateTime: new Date(year, month, dayOfMonth, hourOfDay, minutes, seconds),
dayOfWeek: dayOfWeek,
f: f,
wd: wd,
nwd: nwd,
ny: ny,
nd: nd,
ndow: ndow,
nt: nt,
sst: suti,
internalClock: clq,
reliability: reliability
}
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: DPT19Result | Date): Buffer {
if (value === undefined || value === null)
throw new InvalidValueError(`Invalid value [${value}]`)
let temp: DPT19Result;
if (value.constructor.name === 'Date') {
temp = new DPT19Result()
temp.dateTime = value as Date
temp.dayOfWeek = (value as Date).getDay()
} else {
temp = value as DPT19Result
}
let dayOfWeek = temp.dayOfWeek
const f = !!temp.f ? 1 : 0
const wd = !!temp.wd ? 1 : 0
const nwd = !!temp.nwd ? 1 : 0
const ny = !!temp.ny ? 1 : 0
const nd = !!temp.nd ? 1 : 0
const ndow = !!temp.ndow ? 1 : 0
const nt = !!temp.nt ? 1 : 0
const sst = !!temp.sst ? 1 : 0
const clq = !!temp.internalClock ? 1 : 0
const reliability = !!temp.reliability ? 1 : 0
// Convert day of week from JavaScript to knx (0 => 7, undefined => 0)
if (typeof dayOfWeek === 'undefined') {
dayOfWeek = 0
} else if (dayOfWeek === 0) {
dayOfWeek = 7
}
const buffer = Buffer.alloc(8, 0)
buffer.writeUInt8(temp.dateTime.getFullYear() - 1900, 0)
buffer.writeUInt8(temp.dateTime.getMonth() + 1, 1)
buffer.writeUInt8(temp.dateTime.getDate(), 2)
buffer.writeUInt8((dayOfWeek << 5) | temp.dateTime.getHours(), 3)
buffer.writeUInt8(temp.dateTime.getMinutes(), 4)
buffer.writeUInt8(temp.dateTime.getSeconds(), 5)
buffer.writeUInt8((f << 7) | (wd << 6) | (nwd << 5) | (ny << 4) | (nd << 3) | (ndow << 2) | (nt << 1) | sst, 6)
buffer.writeUInt8(clq << 7 | reliability << 6, 7)
return buffer
}
subtypes: {
// 19.001
'001': {
name: 'DPT_DateTime',
desc: 'datetime',
},
};
}

140
src/DPT2.ts Normal file
View File

@@ -0,0 +1,140 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export interface DPT2Result {
priority: boolean;
data: boolean;
}
export class DPT2 implements DPT {
id = '2';
name = '1-bit value with priority';
bufferLength = 1;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): DPT2Result {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
const value = buffer.readUInt8(0)
return {
priority: ((value & 0b00000010) >> 1) === 1,
data: (value & 0b00000001) === 1,
};
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: DPT2Result): Buffer {
if (!value) throw new InvalidValueError('DPT2: cannot write null value');
return Buffer.from([((value.priority ? 1 : 0) << 1) + ((value.data ? 1 : 0) & 0b00000001)]);
}
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' },
},
};
}

94
src/DPT20.ts Normal file
View File

@@ -0,0 +1,94 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
/**
* Original at https://github.com/pitschr/knx-core/blob/main/src/main/java/li/pitschmann/knx/core/datapoint/DPT20.java
*
* Data Point Type 20 for '8-Bit Enumeration' (1 Octet)
*
* <pre>
* +-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+
* Field Names | (Field 1) |
* Encoding | N N N N N N N N |
* +---+---+---+---+---+---+---+---+
* Format: 8 bit (N<sub>8</sub>)
* </pre>
*/
export class DPT20 implements DPT {
id = '20';
name = '8-Bit Enumeration';
bufferLength = 1;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
return buffer.readUInt8(0);
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (value === undefined || value === null)
throw new InvalidValueError(`Invalid value [${value}]`)
return Buffer.from([value & 0xff])
}
subtypes: {
// 20.001 SCLO Mode
1: {
name: 'SCLO_Mode',
desc: '',
unit: '',
scalar_range: {
0: 'Autonomous',
1: 'Slave',
2: 'Master'
},
range: undefined,
},
// 20.001 Building Mode
2: {
name: 'Building_Mode',
desc: '',
unit: '',
scalar_range: {
0: 'Building in use',
1: 'Building not used',
2: 'Building protection'
},
range: undefined,
},
// 20.102 HVAC mode
102: {
name: 'HVAC_Mode',
desc: '',
unit: '',
scalar_range: {
0: 'Auto',
1: 'Comfort',
2: 'Standby',
3: 'Economy',
4: 'Building protection'
},
range: undefined,
},
};
}

101
src/DPT21.ts Normal file
View File

@@ -0,0 +1,101 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
/**
* Original at https://github.com/pitschr/knx-core/blob/main/src/main/java/li/pitschmann/knx/core/datapoint/DPT21.java
* * Data Point Type 21 for 8-Bits flagged messages
*
* <pre>
* +-7-+-6-+-5-+-4-+-3-+-2-+-1-+-0-+
* Field Names | b b b b b b b b |
* Encoding | B B B B B B B B |
* +---+---+---+---+---+---+---+---+
* Format: 8 bits (B<sub>8</sub>)
* </pre>
*/
export class DPT21 implements DPT {
id = '';
name = '';
bufferLength = 0;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): boolean[] {
if (buffer.length !== 2)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected 2.`);
const result = [
!!(buffer[0] & 0x80),
!!(buffer[0] & 0x40),
!!(buffer[0] & 0x20),
!!(buffer[0] & 0x10),
!!(buffer[0] & 0x08),
!!(buffer[0] & 0x04),
!!(buffer[0] & 0x02),
!!(buffer[0] & 0x01),
]
return result;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: boolean[]): Buffer {
if (value === undefined || value === null)
throw new InvalidValueError(`Invalid value [${value}]`)
if (value.length != 8)
throw new InvalidValueError(`Value length should be 8. Got ${value.length}.`)
const b7 = value[0] ? 1 : 0
const b6 = value[0] ? 1 : 0
const b5 = value[0] ? 1 : 0
const b4 = value[0] ? 1 : 0
const b3 = value[0] ? 1 : 0
const b2 = value[0] ? 1 : 0
const b1 = value[0] ? 1 : 0
const b0 = value[0] ? 1 : 0
const buf = Buffer.from([
b7 << 7 |
b6 << 6 |
b5 << 5 |
b4 << 4 |
b3 << 3 |
b2 << 2 |
b1 << 1 |
b0
])
return buf;
}
subtypes: {
// 21.001 status - 5 bits
"001": {
"name": "DPT_StatusGen",
"desc": "General Status",
"unit": "",
"scalar_range": undefined,
"range": undefined
},
// 21.002 control - 3 bits
"002": {
"name": "DPT_Device_Control",
"desc": "Device Control",
"unit": "",
"scalar_range": undefined,
"range": undefined
}
};
}

66
src/DPT232.ts Normal file
View File

@@ -0,0 +1,66 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export interface DPT232Result {
red: number,
green: number,
blue: number
}
export class DPT232 implements DPT {
id = '232';
name = 'RGB array';
bufferLength = 3
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the on/off value
*/
decoder(buffer: Buffer): DPT232Result {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
const result: DPT232Result = {
red: buffer.readUInt8(0),
green: buffer.readUInt8(1),
blue: buffer.readUInt8(2),
}
return result;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: DPT232Result): Buffer {
if (value === undefined || value === null)
throw new InvalidValueError('Cannot write null value');
if (value.red < 0 || value.red > 255)
throw new InvalidValueError(`Red component out of range ${value.red}`);
if (value.blue < 0 || value.blue > 255)
throw new InvalidValueError(`Blue component out of range ${value.blue}`);
if (value.green < 0 || value.green > 255)
throw new InvalidValueError(`Green component out of range ${value.green}`);
let buffer = Buffer.alloc(this.bufferLength)
buffer.writeUInt8(value.red, 0)
buffer.writeUInt8(value.green, 1)
buffer.writeUInt8(value.blue, 2)
return buffer
}
subtypes: {
'600': {
name: 'RGB',
desc: 'RGB color triplet',
unit: '',
},
};
}

39
src/DPT237.ts Normal file
View File

@@ -0,0 +1,39 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT237 implements DPT {
id = '';
name = '';
bufferLength = 0;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT8. Expected ${this.bufferLength}.`);
return value;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
return buf;
}
subtypes: {
};
}

74
src/DPT238.ts Normal file
View File

@@ -0,0 +1,74 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT238 implements DPT {
id = '238';
name = '8-bit unsigned value';
bufferLength = 1
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
return buffer.readUInt8(0)
}
encoder(value: number): Buffer {
if (!value)
throw new InvalidValueError('DPT5: cannot write null value');
let buf = Buffer.alloc(this.bufferLength);
buf.writeUInt8(value, 0)
return buf;
}
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',
},
}
}

64
src/DPT3.ts Normal file
View File

@@ -0,0 +1,64 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export interface DPT3Result {
decr_incr: number;
data: number;
}
export class DPT3 implements DPT {
id = '3';
name = '4-bit relative dimming control';
bufferLength = 1;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): DPT3Result {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
const value = buffer.readUInt8(0)
return {
decr_incr: (value & 0b00001000) >> 3,
data: value & 0b00000111,
};
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: DPT3Result): Buffer {
if (!value)
throw new InvalidValueError('DPT3: cannot write null value');
if (value.decr_incr > 1)
throw new InvalidValueError(`DPT3: Invalid increment/decrement value: ${value.decr_incr}. Expected 1 or 0.`);
return Buffer.from([(value.decr_incr << 3) + (value.data & 0b00000111)]);
}
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',
},
};
}

57
src/DPT4.ts Normal file
View File

@@ -0,0 +1,57 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT4 implements DPT {
id = '4';
name = '8-bit character';
bufferLength = 1;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): string {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
return String.fromCharCode(buffer[0]);
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: string): Buffer {
if (!value)
throw new InvalidValueError('DPT4: cannot write null value');
const apdu_data = value.charCodeAt(0);
if (apdu_data > 255)
throw new InvalidValueError('DPT4: must supply an ASCII character');
return Buffer.from([apdu_data]);
}
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',
},
};
}

73
src/DPT5.ts Normal file
View File

@@ -0,0 +1,73 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT5 implements DPT {
id = '5';
name = '8-bit unsigned value';
bufferLength = 1;
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
return buffer.readUInt8(0)
}
encoder(value: number): Buffer {
if (!value)
throw new InvalidValueError('DPT5: cannot write null value');
let buf = Buffer.alloc(1);
buf.writeUInt8(value, 0)
return buf;
}
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",
},
}
}

58
src/DPT6.ts Normal file
View File

@@ -0,0 +1,58 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT6 implements DPT {
id = '6';
name = "8-bit signed value";
bufferLength = 1;
range = [-128, 127];
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
const value = buffer.readInt8(0)
return value;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (!value)
throw new InvalidValueError('DPT6: cannot write null value');
if (value < this.range[0] || value > this.range[1])
throw new InvalidValueError(`DPT6: Value ${value} out of range [${this.range[0]}, ${this.range[1]}].`);
let buf = Buffer.alloc(this.bufferLength);
buf.writeInt8(value, 0)
return buf;
}
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"
},
};
}

134
src/DPT7.ts Normal file
View File

@@ -0,0 +1,134 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT7 implements DPT {
id = '7';
name = '16-bit unsigned value';
bufferLength = 2
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
const value = buffer.readUInt16BE(0)
return value;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (!value)
throw new InvalidValueError('DPT7: cannot write null value');
if (value < 0)
throw new InvalidValueError(`DPT7: Cannot write negative value ${value}`);
let buf = Buffer.alloc(this.bufferLength);
buf.writeUInt16BE(value, 0)
return buf;
}
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"
}
};
}

111
src/DPT8.ts Normal file
View File

@@ -0,0 +1,111 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
export class DPT8 implements DPT {
id = '8';
name = '16-bit signed value';
bufferLength = 2;
"range" = [-32768, 32767];
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the DPT value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
const value = buffer.readInt16BE(0)
return value;
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (!value)
throw new InvalidValueError('DPT8: cannot write null value');
if (value < this.range[0] || value > this.range[1])
throw new InvalidValueError(`DPT6: Value ${value} out of range [${this.range[0]}, ${this.range[1]}].`);
let buf = Buffer.alloc(this.bufferLength);
buf.writeInt16BE(value, 0)
return buf;
}
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": "°"
},
};
}

232
src/DPT9.ts Normal file
View File

@@ -0,0 +1,232 @@
'use strict';
import { BufferLengthError } from './errors/BufferLengthError';
import { InvalidValueError } from './errors/InvalidValueError';
import { DPT } from './definitions';
function ldexp(mantissa: number, exponent: number): number {
return 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);
}
function frexp(value: number): number[] {
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];
};
export class DPT9 implements DPT {
id = '9';
name = '16-bit floating point value';
bufferLength = 2;
/**
* Decode a buffer
*
* @param buffer the buffer
* @returns the on/off value
*/
decoder(buffer: Buffer): number {
if (buffer.length !== this.bufferLength)
throw new BufferLengthError(`Invalid buffer length ${buffer.length}/${buffer} for DPT1. Expected ${this.bufferLength}.`);
const sign = buffer[0] >> 7;
const exponent = (buffer[0] & 0b01111000) >> 3;
let mantissa = 256 * (buffer[0] & 0b00000111) + buffer[1];
if (sign) mantissa = ~(mantissa ^ 2047);
return parseFloat(ldexp(0.01 * mantissa, exponent).toPrecision(15));
};
/**
* Encode a buffer
*
* @param value the value to be converted to buffer
* @returns the buffer
*/
encoder(value: number): Buffer {
if (value === undefined || value === null)
throw new InvalidValueError('DPT9: cannot write null value');
if (!isFinite(value))
throw new InvalidValueError('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]);
}
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],
},
};
}

113
src/DataPointType.ts Normal file
View File

@@ -0,0 +1,113 @@
'use strict';
import { Encoder, Decoder, DPT } from './definitions';
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 { DPT232 } from './DPT232';
import { DPT237 } from './DPT237';
import { DPT238 } from './DPT238';
export class DataPointType {
get type(): string {
return this._type;
}
get subtype(): string {
return this._subtype;
}
/**
* @property {DPTYPE} DPT1
* @property {DPTYPE} DPT2
* @property {DPTYPE} DPT3
* @property {DPTYPE} DPT4
* @property {DPTYPE} DPT5
* @property {DPTYPE} DPT6
* @property {DPTYPE} DPT7
* @property {DPTYPE} DPT8
* @property {DPTYPE} DPT9
* @property {DPTYPE} DPT10
* @property {DPTYPE} DPT11
* @property {DPTYPE} DPT12
* @property {DPTYPE} DPT13
* @property {DPTYPE} DPT14
* @property {DPTYPE} DPT15
* @property {DPTYPE} DPT16
* @property {DPTYPE} DPT17
* @property {DPTYPE} DPT18
* @property {DPTYPE} DPT19
* @property {DPTYPE} DPT20
*/
static get TYPES(): { [index: string]: DPT | null } {
return {
DPT1: new DPT1(),
DPT2: new DPT2(),
DPT3: new DPT3(),
DPT4: new DPT4(),
DPT5: new DPT5(),
DPT6: new DPT6(),
DPT7: new DPT7(),
DPT8: new DPT8(),
DPT9: new DPT9(),
DPT10: new DPT10(),
DPT11: new DPT11(),
DPT12: new DPT12(),
DPT13: new DPT13(),
DPT14: new DPT14(),
DPT15: new DPT15(),
DPT16: new DPT16(),
DPT17: new DPT17(),
DPT18: new DPT18(),
DPT19: new DPT19(),
DPT20: new DPT20(),
DPT21: new DPT21(),
//DPT22: new DPT22(),
DPT232: new DPT232(),
DPT237: new DPT237(),
DPT238: new DPT238()
};
}
constructor(private _type: string, private _subtype: string, private _encoder: Encoder, private _decoder: Decoder) {
}
static validType(text: string): boolean {
const m = text.toUpperCase().match(/(?:DPT)?(\d+)(\.(\d+))?/);
return m != null;
}
toString(): string {
return `${this.type}.${this.subtype}`;
}
decode(buffer: Buffer): string | number {
return this._decoder(buffer);
}
encode(value: string | number): Buffer {
return this._encoder(value);
}
}

35
src/definitions.ts Normal file
View File

@@ -0,0 +1,35 @@
/**
* Definitions for the KNX library
*/
export type Encoder = (value: any) => Buffer;
export type Decoder = (buffer: Buffer) => any;
/**
* @property {string} use
* @property {string} name
* @property {string} desc
* @property {any} enc
*/
export interface DPTSubType {
use?: string;
name: string;
desc: string;
enc?: any;
unit?: string;
range?: number[];
}
/**
* DPT generic interface
*/
export interface DPT {
id: string;
name: string;
bufferLength: number;
subtypes: { [index: string]: DPTSubType };
decoder: Decoder;
encoder: Encoder;
}

View File

@@ -0,0 +1,2 @@
export class BufferLengthError extends Error {
}

View File

@@ -0,0 +1,2 @@
export class InvalidValueError extends Error {
}