knxultimate
Version:
KNX IP protocol implementation for Node. This is the ENGINE of Node-Red KNX-Ultimate node.
291 lines (252 loc) • 6.4 kB
text/typescript
/**
* Represents KNX interface device information blocks.
*
* Written in Italy with love, sun and passion, by Massimo Saccani.
*
* Released under the MIT License.
* Use at your own risk; the author assumes no liability for damages.
*/
import { KNX_CONSTANTS } from './KNXConstants'
import { splitIP, validateKNXAddress } from './KNXUtils'
const DEVICE_INFO_LENGTH = 0x36
const SERIALNUMBER_LENGTH = 6
const MACADDRESS_LENGTH = 6
const NAME_LENGTH = 30
export enum Medium {
TP1 = KNX_CONSTANTS.TP1,
PL110 = KNX_CONSTANTS.PL110,
RF = KNX_CONSTANTS.RF,
IP = KNX_CONSTANTS.IP,
}
export default class DeviceInfo {
private _type: number = KNX_CONSTANTS.DEVICE_INFO
private _medium: Medium
private _status: number
private _address: number
private _projectID: number
private _serialNumber: number[]
private _splitIP: RegExpMatchArray
private _macAddress: number[]
private _name: string
constructor(
medium: Medium,
status: number,
address: number,
projectID: number,
serialNumber: number[],
ip: string,
macAddress: number[],
name: string,
) {
this._medium = medium
this._status = status
this._address = address
this._projectID = projectID
this._serialNumber = serialNumber
this.ip = ip
this._macAddress = macAddress
this.name = name
}
get type(): number {
return this._type
}
set ip(ip: string) {
this._splitIP = splitIP(ip)
}
get ip(): string {
return this._splitIP.input
}
get status(): number {
return this._status
}
set status(status: number) {
this._status = status & 1
}
set name(name: string) {
if (name.length > NAME_LENGTH) {
throw new Error(
`Invalid name format or too long - ${name}(${name.length}`,
)
}
this._name = name
}
get name(): string {
return this._name
}
set projectID(id: number) {
const _id = Number(id)
if (isNaN(_id) || _id > 0xffff || _id < 0) {
throw new Error('Invalid project id')
}
this._projectID = _id
}
get projectID(): number {
return this._projectID
}
set serialNumber(serialNumber: number[]) {
this._serialNumber = DeviceInfo.validArray(
serialNumber,
SERIALNUMBER_LENGTH,
)
}
get serialNumber(): number[] {
return this._serialNumber
}
set macAddress(macAddress: number[]) {
this._macAddress = DeviceInfo.validArray(macAddress, MACADDRESS_LENGTH)
}
get macAddress(): number[] {
return this._macAddress
}
set medium(medium: Medium) {
this._medium = medium
}
get medium(): Medium {
return this._medium
}
get formattedMedium(): string {
switch (this._medium) {
case KNX_CONSTANTS.TP1:
return 'TP1'
case KNX_CONSTANTS.PL110:
return 'PL110'
case KNX_CONSTANTS.RF:
return 'RF'
case KNX_CONSTANTS.IP:
return 'IP'
}
return 'Unknown'
}
set address(address: number) {
this._address = validateKNXAddress(address)
}
get address(): number {
return this._address
}
get formattedAddress(): string {
let address = ''
if (this._address > 0xfff) {
address = `${(this._address & 0xf000) >> 12}.`
}
address += `${(this._address & 0x0f00) >> 8}.${this._address & 0xff}`
return address
}
get length(): number {
return DEVICE_INFO_LENGTH
}
static createFromBuffer(buffer: Buffer, offset: number = 0): DeviceInfo {
if (offset + this.length >= buffer.length) {
throw new Error(
`offset ${offset} out of buffer range ${buffer.length}`,
)
}
const structureLength = buffer.readUInt8(offset)
if (offset + structureLength > buffer.length) {
throw new Error(
`offset ${offset} block length: ${structureLength} out of buffer range ${buffer.length}`,
)
}
offset++
const type = buffer.readUInt8(offset++)
if (type !== KNX_CONSTANTS.DEVICE_INFO) {
throw new Error(`Invalid DeviceInfo type ${type}`)
}
const medium = buffer.readUInt8(offset++)
const status = buffer.readUInt8(offset++)
const address = buffer.readUInt16BE(offset)
offset += 2
const projectID = buffer.readUInt16BE(offset)
offset += 2
const serialNumber = [0, 0, 0, 0, 0, 0]
for (let i = 0; i < SERIALNUMBER_LENGTH; i++) {
serialNumber[i] = buffer.readUInt8(offset++)
}
const ip = []
for (let i = 1; i <= 4; i++) {
ip.push(buffer.readUInt8(offset++))
}
const textIP = ip.join('.')
const macAddress = [0, 0, 0, 0, 0, 0]
for (let i = 0; i < MACADDRESS_LENGTH; i++) {
macAddress[i] = buffer.readUInt8(offset++)
}
let name = ''
for (let i = 0; i < NAME_LENGTH; i++) {
const char = buffer.readUInt8(offset++)
if (char !== 0) {
name += String.fromCharCode(char)
} else {
break
}
}
return new DeviceInfo(
medium,
status,
address,
projectID,
serialNumber,
textIP,
macAddress,
name,
)
}
setMediumFromString(medium: string) {
switch (medium) {
case 'TP1':
this._medium = KNX_CONSTANTS.TP1
break
case 'PL110':
this._medium = KNX_CONSTANTS.PL110
break
case 'RF':
this._medium = KNX_CONSTANTS.RF
break
case 'IP':
this._medium = KNX_CONSTANTS.IP
break
default:
throw new Error(`Invalid medium ${medium}`)
}
}
toBuffer(): Buffer {
const buffer = Buffer.alloc(DEVICE_INFO_LENGTH)
let offset = 0
buffer.writeUInt8(this.length, offset++)
buffer.writeUInt8(KNX_CONSTANTS.DEVICE_INFO, offset++)
buffer.writeUInt8(this.medium, offset++)
buffer.writeUInt8(this.status, offset++)
buffer.writeUInt16BE(this.address, offset)
offset += 2
buffer.writeUInt16BE(this.projectID, offset)
offset += 2
for (let i = 0; i < this.serialNumber.length; i++) {
buffer.writeUInt8(this.serialNumber[i], offset++)
}
for (let i = 1; i <= KNX_CONSTANTS.IPV4_ADDRESS_LENGTH; i++) {
buffer.writeUInt8(Number(this._splitIP[i]), offset++)
}
for (let i = 0; i < this.macAddress.length; i++) {
buffer.writeUInt8(this.macAddress[i], offset++)
}
for (let i = 0; i < NAME_LENGTH; i++) {
buffer.writeUInt8(
i >= this.name.length ? 0 : Number(this.name[i]),
offset++,
)
}
return buffer
}
static validArray(a: number[], length: number): number[] {
if (!Array.isArray(a) || a.length !== length) {
throw new Error('Invalid array format')
}
const validA = [0, 0, 0, 0, 0, 0]
for (let i = 0; i < a.length; i++) {
validA[i] = Number(a[i])
if (isNaN(validA[i]) || validA[i] < 0 || validA[i] > 255) {
throw new Error(`Invalid byte at pos ${i}`)
}
}
return validA
}
}