'use strict'; import { BufferLengthError } from './errors/BufferLengthError'; import { InvalidValueError } from './errors/InvalidValueError'; import { DPT } from './definitions'; /** * 19.001 Date & Time * *
 *             +-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 (U8 [r4U4] [r3U5] [r3U5] [r2U6] [r2U6] B16)
 * 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)
 * 
*

* 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. *

* "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 *

* 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', }, }; }