Skip to content

Commit

Permalink
feat: add some missing dpts
Browse files Browse the repository at this point in the history
  • Loading branch information
robertsLando committed Apr 5, 2024
1 parent 5466dba commit de9a06a
Show file tree
Hide file tree
Showing 22 changed files with 1,442 additions and 72 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "knx",
"description": "KNXnet/IP protocol implementation for Node(>=6.x)",
"description": "KNXnet/IP protocol implementation",
"version": "2.5.4",
"engines": {
"node": ">=16"
Expand Down
49 changes: 29 additions & 20 deletions src/dptlib/dpt10.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,33 +65,42 @@ const config: DatapointConfig = {
// 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)
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 (buf.length !== 3) {
log.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
)
return log.warn(
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.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
},

Expand Down
16 changes: 1 addition & 15 deletions src/dptlib/dpt18.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,12 @@ import { hasProp } from '../utils'
end
*/

// TODO: implement fromBuffer, formatAPDU

const log = logger

const config: DatapointConfig = {
id: 'DPT18',
formatAPDU(value) {
if (value == null) log.warn('DPT18: cannot write null value')
if (!value) log.warn('DPT18: cannot write null value')
else {
const apdu_data = Buffer.alloc(1)
if (
Expand All @@ -47,7 +45,6 @@ const config: DatapointConfig = {
const 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(
Expand All @@ -59,14 +56,12 @@ const config: DatapointConfig = {
},

fromBuffer(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 {
const 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,
Expand All @@ -91,12 +86,3 @@ const config: DatapointConfig = {
}

export default config

/*
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}
*/
3 changes: 1 addition & 2 deletions src/dptlib/dpt19.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { logger } from 'log-driver'
import type { DatapointConfig } from '.'

const log = logger
// TODO: implement fromBuffer, formatAPDU

//
// DPT19: 8-byte Date and Time
Expand All @@ -16,7 +15,7 @@ const log = logger
const config: DatapointConfig = {
id: 'DPT19',
formatAPDU: (value) => {
if (!(value instanceof Date))
if (typeof value !== 'object' || value.constructor.name !== 'Date')
return log.error('DPT19: Must supply a Date object')

// Sunday is 0 in Javascript, but 7 in KNX.
Expand Down
2 changes: 1 addition & 1 deletion src/dptlib/dpt2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const config: DatapointConfig = {
// DPT2 frame description.
// Always 8-bit aligned.
formatAPDU: (value: Dpt2Value) => {
if (value == null) return log.error('DPT2: cannot write null value')
if (!value) return log.error('DPT2: cannot write null value')

if (
typeof value === 'object' &&
Expand Down
16 changes: 11 additions & 5 deletions src/dptlib/dpt20.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,26 @@ const log = logger
const config: DatapointConfig = {
id: 'DPT20',
formatAPDU: (value) => {
log.debug(`./knx/src/dpt20.js : input value = ${value}`)
return Buffer.from([value])
const apdu_data = Buffer.alloc(1)
apdu_data[0] = value
log.debug(
`./knx/src/dpt20.js : input value = ${value} apdu_data = ${apdu_data}`,
)
return apdu_data
},

fromBuffer: (buf) => {
if (buf.length !== 1) throw Error('Buffer should be 1 bytes long')
if (buf.length !== 1) {
log.warn('DPT20: Buffer should be 1 byte long, got', buf.length)
return null
}
const ret = buf.readUInt8(0)
log.debug(` dpt20.js fromBuffer : ${ret}`)
return ret
},

basetype: {
bitlength: 8,
range: [undefined, undefined], // TODO: verify
range: [undefined, undefined],
valuetype: 'basic',
desc: '1-byte',
},
Expand Down
3 changes: 0 additions & 3 deletions src/dptlib/dpt21.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const log = logger
// - AlarmUnAck b3
// - reseverd b4-7

// FIXME: help needed
const config: DatapointConfig = {
id: 'DPT21',
formatAPDU(value) {
Expand Down Expand Up @@ -56,14 +55,12 @@ const config: DatapointConfig = {
}
// return ret;
},

basetype: {
bitlength: 8,
range: [undefined, undefined],
valuetype: 'composite',
desc: '1-byte',
},

subtypes: {
// 21.001 status - 5 bits
'001': {
Expand Down
147 changes: 147 additions & 0 deletions src/dptlib/dpt213.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* knx.js - a KNX protocol stack in pure Javascript
* (C) 2016-2018 Elias Karakoulakis
*/

import { logger } from 'log-driver'
import type { DatapointConfig } from '.'
import { frexp, hasProp, ldexp } from '../utils'

const log = logger

//
// 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.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.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
Loading

0 comments on commit de9a06a

Please sign in to comment.