UNPKG

node-red-contrib-uhppoted

Version:

A set of nodes for the UHPPOTE Wiegand Access Controller

950 lines (797 loc) 25.4 kB
const { Buffer } = require('node:buffer') const ipx = require('./ipx.js') module.exports = { /** * Encodes a get-device request. * * @param {number} deviceId Controller serial number (0 implies a get-all-devices) * * @return {buffer} 64 byte NodeJS buffer with encoded get-device request */ GetDevice: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x94, 1) if (deviceId !== null) { request.writeUInt32LE(deviceId, 4) } return request }, /** * Encodes a set-address request. * * @param {number} deviceId Controller serial number * @param {string} address IPv4 address assigned to controller * @param {string} netmask IPv4 subnet mask assigned to controller * @param {string} gateway IPv4 gateway address * * @return {buffer} 64 byte NodeJS buffer with encoded set-address request */ SetIP: function (deviceId, { address, netmask, gateway } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x96, 1) request.writeUInt32LE(deviceId, 4) ipx.toBuffer(address, request, 8) ipx.toBuffer(netmask, request, 12) ipx.toBuffer(gateway, request, 16) request.writeUInt32LE(0x55aaaa55, 20) return request }, /** * Encodes a set-time request. * * @param {number} deviceId Controller serial number * @param {date} datetime Date and time to which to set controller time * * @return {buffer} 64 byte NodeJS buffer with encoded set-time request */ SetTime: function (deviceId, { datetime } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x30, 1) request.writeUInt32LE(deviceId, 4) datetime2bin(datetime).copy(request, 8) return request }, /** * Encodes a get-time request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded get-time request. */ GetTime: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x32, 1) request.writeUInt32LE(deviceId, 4) return request }, /** * Encodes a set-door-control request. * * @param {number} deviceId Controller serial number * @param {number} door Door number in the range [1..4] * @param {number} delay Door open delay (in seconds) * @param {string} control Door control state ('normally open', 'normally closed' or 'controlled') * * @return {buffer} 64 byte NodeJS buffer with encoded set-door-control request */ SetDoorControl: function (deviceId, { door, delay, control } = {}) { const opcodes = require('../nodes/opcodes.js') const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x80, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt8(door, 8) request.writeUInt8(delay, 10) switch (control) { case opcodes.NormallyOpen: request.writeUInt8(1, 9) break case opcodes.NormallyClosed: request.writeUInt8(2, 9) break case opcodes.Controlled: request.writeUInt8(3, 9) break default: throw new Error(`invalid door control ${control}`) } return request }, /** * Encodes a get-door-control request. * * @param {number} deviceId Controller serial number * @param {number} door Door number in the range [1..4] * * @return {buffer} 64 byte NodeJS buffer with encoded get-door-control request. */ GetDoorControl: function (deviceId, { door } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x82, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt8(door, 8) return request }, /** * Encodes a get-listener request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded get-listener request */ GetListener: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x92, 1) request.writeUInt32LE(deviceId, 4) return request }, /** * Encodes a set-listener request. * * @param {number} deviceId Controller serial number * @param {string} address IPv4 listener address for controller events * @param {uint16} port IPv4 listener port for controller events * @param {uint8} interval Auto-send interval (seconds) - 0 for no auto-send. * * @return {buffer} 64 byte NodeJS buffer with encoded set-address request */ SetListener: function (deviceId, { address, port, interval } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x90, 1) request.writeUInt32LE(deviceId, 4) ipx.toBuffer(address, request, 8) request.writeUInt16LE(port, 12) request.writeUInt8(interval, 14) return request }, /** * Encode a record-special-events request. * * @param {number} deviceId Controller serial number * @param {number} enable true/false * * @return {buffer} 64 byte NodeJS buffer with encoded record-special-events request. */ RecordSpecialEvents: function (deviceId, { enable } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x8e, 1) request.writeUInt32LE(deviceId, 4) if (enable) { request.writeUInt8(1, 8) } else { request.writeUInt8(0, 8) } return request }, /** * Encodes a get-status request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded get-status request. */ GetStatus: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x20, 1) request.writeUInt32LE(deviceId, 4) return request }, /** * Encodes an open-door request. * * @param {number} deviceId Controller serial number * @param {number} door Door number in the range [1..4] * * @return {buffer} 64 byte NodeJS buffer with encoded open-door request. */ OpenDoor: function (deviceId, { door } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x40, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt8(door, 8) return request }, /** * Encodes a get-cards request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded get-cards request. */ GetCards: function (deviceId, _object) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x58, 1) request.writeUInt32LE(deviceId, 4) return request }, /** * Encode a get-card-by-id request. * * @param {number} deviceId Controller serial number * @param {number} card Card number * * @return {buffer} 64 byte NodeJS buffer with encoded get-card-by-id request. */ GetCardByID: function (deviceId, { card } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x5a, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(card, 8) return request }, /** * Encode a get-card-by-index request. * * @param {number} deviceId Controller serial number * @param {number} index Card index * * @return {buffer} 64 byte NodeJS buffer with encoded get-card-by-index request. */ GetCardByIndex: function (deviceId, { index } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x5c, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(index, 8) return request }, /** * Encode a put-card request. * * @param {number} deviceId Controller serial number * @param {number} card Card number * @param {date} from Card validity start date * @param {date} to Card validity end date * @param {object} doors Object mapping door numbers 1..4 to access permission. A permission * may be true, false or a time profile in the range [2..254] * @param {number} PIN Optional card PIN code in the range [0..999999] * * @return {buffer} 64 byte NodeJS buffer with encoded put-card request. */ PutCard: function (deviceId, { card, from, to, doors, PIN } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x50, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(card, 8) date2bin(from).copy(request, 12) date2bin(to).copy(request, 16) ;['1', '2', '3', '4'].forEach((door, index) => { if (Object.prototype.hasOwnProperty.call(doors, door)) { const permission = doors[door] const offset = 20 + index if (typeof permission === 'boolean') { request.writeUInt8(permission ? 0x01 : 0x00, offset) } else { const profileID = Number(permission) if (!Number.isNaN(profileID) && Number.isInteger(profileID) && profileID >= 2 && profileID <= 254) { request.writeUInt8(profileID, offset) } } } }) if (PIN) { const pin = Number.parseInt(`${PIN}`) if (Number.isNaN(pin) || pin < 0 || pin > 999999) { throw new Error(`invalid card PIN value ${PIN}`) } else { uint24(pin).copy(request, 24) } } return request }, /** * Encode a delete-card request. * * @param {number} deviceId Controller serial number * @param {number} card Card number to delete * * @return {buffer} 64 byte NodeJS buffer with encoded delete-card request. */ DeleteCard: function (deviceId, { card } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x52, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(card, 8) return request }, /** * Encode a delete-all-cards request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded delete-all-cards request. */ DeleteCards: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x54, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(0x55aaaa55, 8) return request }, /** * Encodes a get-time-profile request. * * @param {number} deviceId Controller serial number * @param {number} profileId Time profile ID [2..254] * * @return {buffer} 64 byte NodeJS buffer with encoded get-time-profile request. */ GetTimeProfile: function (deviceId, { profileId } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x98, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt8(profileId, 8) return request }, /** * Encodes a set-time-profile request. * * @param {number} deviceId Controller serial number * @param {object} profile Time profile * * @return {buffer} 64 byte NodeJS buffer with encoded set-time-profile request. */ SetTimeProfile: function (deviceId, { profile } = {}) { const map = new Map([ ['mo', 17], ['tu', 18], ['we', 19], ['th', 20], ['fr', 21], ['sa', 22], ['su', 23], ]) let profileID = 0 let linked = 0 if (profile.id) { profileID = Number(profile.id) } if (Number.isNaN(profileID) || !Number.isInteger(profileID) || profileID < 2 || profileID > 254) { throw new Error(`invalid profile ID (${profile.id})`) } if (profile.linkedTo) { linked = Number(profile.linkedTo) if (Number.isNaN(linked) || !Number.isInteger(linked) || linked < 2 || linked > 254) { throw new Error(`invalid linked profile (${profile.linkedTo})`) } } const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x88, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt8(profile.id, 8) date2bin(profile.valid.from).copy(request, 9) date2bin(profile.valid.to).copy(request, 13) if (profile.weekdays) { profile.weekdays.forEach((day) => { map.forEach((v, k) => { if (day.toLowerCase().startsWith(k)) { request.writeUInt8(1, v) } }) }) } if (profile.segments) { const re = /[0-9]{2}:[0-9]{2}/ let offset = 24 profile.segments.slice(0, 3).forEach((segment) => { if (segment.start && segment.end && re.test(segment.start) && re.test(segment.end)) { HHmm2bin(segment.start).copy(request, offset) HHmm2bin(segment.end).copy(request, offset + 2) offset = offset + 4 } }) } request.writeUInt8(linked, 36) return request }, /** * Encodes a clear-time-profiles request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded clear-time-profiles request. */ ClearTimeProfiles: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x8a, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(0x55aaaa55, 8) return request }, /** * Encodes an add-task request. * * @param {number} deviceId Controller serial number * @param {object} task Task definition * * @return {buffer} 64 byte NodeJS buffer with encoded add-task request. */ AddTask: function (deviceId, { task } = {}) { const days = new Map([ ['mo', 16], ['tu', 17], ['we', 18], ['th', 19], ['fr', 20], ['sa', 21], ['su', 22], ]) const tasks = new Map([ ['doorcontrolled', 0], ['doornormallyopen', 1], ['doornormallyclosed', 2], ['disabletimeprofile', 3], ['enabletimeprofile', 4], ['cardnopassword', 5], ['cardinpassword', 6], ['cardpassword', 7], ['enablemorecards', 8], ['disablemorecards', 9], ['triggeronce', 10], ['disablepushbutton', 11], ['enablepushbutton', 12], ]) const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xa8, 1) request.writeUInt32LE(deviceId, 4) date2bin(task.valid.from).copy(request, 8) date2bin(task.valid.to).copy(request, 12) if (task.weekdays) { task.weekdays.forEach((day) => { days.forEach((v, k) => { if (day.toLowerCase().startsWith(k)) { request.writeUInt8(1, v) } }) }) } if (task.start && /[0-9]{2}:[0-9]{2}/.test(task.start)) { HHmm2bin(task.start).copy(request, 23) } else { throw new Error(`invalid task start time (${task.start})`) } request.writeUInt8(task.door, 25) if (isNaN(task.task)) { const key = task.task.replace(/[^a-z]+/gi, '') if (tasks.has(key)) { request.writeUInt8(tasks.get(key), 26) } else { throw new Error(`invalid task '${task.task}'`) } } else if (!Number.isInteger(task.task) || task.task < 1 || task > 13) { throw new Error(`invalid task ID '${task.task}' - valid range is [1..13]`) } else { request.writeUInt8(task.task - 1, 26) } if (task.cards) { if (!isNaN(task.cards) && Number.isInteger(task.cards)) { request.writeUInt8(task.cards, 27) } else { throw new Error(`invalid value for 'cards' (${task.cards})`) } } return request }, /** * Encodes a clear-task-list request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded clear-task-list request. */ ClearTaskList: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xa6, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(0x55aaaa55, 8) return request }, /** * Encodes a refresh-task-list request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded refresh-task-list request. */ RefreshTaskList: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xac, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(0x55aaaa55, 8) return request }, /** * Encode a get-event-index request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded get-event-index request. */ GetEventIndex: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xb4, 1) request.writeUInt32LE(deviceId, 4) return request }, /** * Encode a set-event-index request. * * @param {number} deviceId Controller serial number * @param {number} index Index to which to set controller internal event index * * @return {buffer} 64 byte NodeJS buffer with encoded set-event-index request. */ SetEventIndex: function (deviceId, { index } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xb2, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(index, 8) request.writeUInt32LE(0x55aaaa55, 12) return request }, /** * Encode a get-event request. * * @param {number} deviceId Controller serial number * @param {number} index Index of event to retrieve * * @return {buffer} 64 byte NodeJS buffer with encoded get-event request. */ GetEvent: function (deviceId, { index } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xb0, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(index, 8) return request }, /** * Encode a set-pc-control request. * * @param {number} deviceId Controller serial number * @param {number} enable true/false * * @return {buffer} 64 byte NodeJS buffer with encoded set-pc-control request. */ SetPCControl: function (deviceId, { enable } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xa0, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(0x55aaaa55, 8) if (enable) { request.writeUInt8(1, 12) } else { request.writeUInt8(0, 12) } return request }, /** * Encodes a set-interlock request. * * @param {number} deviceId Controller serial number * @param {number} interlock 0,1,2,3,4 or 8, corresponding to modes * - 1&2 * - 3&4 * - 1&2,3&4 * - 1&2&3 * - 1&2&3&4 * * @return {buffer} 64 byte NodeJS buffer with encoded set-interlock request. */ SetInterlock: function (deviceId, { interlock } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xa2, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt8(interlock, 8) return request }, /** * Encodes an activate-keypads request. * * @param {number} deviceId Controller serial number * @param {object} keypads Object with activated/deactivated keypads: * { * 1: true/false, * 2: true/false, * 3: true/false, * 4: true/false * } * * @return {buffer} 64 byte NodeJS buffer with encoded activate-keypads request. */ ActivateKeypads: function (deviceId, { keypads } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xa4, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt8(keypads[1] ? 0x01 : 0x00, 8) request.writeUInt8(keypads[2] ? 0x01 : 0x00, 9) request.writeUInt8(keypads[3] ? 0x01 : 0x00, 10) request.writeUInt8(keypads[4] ? 0x01 : 0x00, 11) return request }, /** * Encodes a set-door-passcodes request. * * @param {number} deviceId Controller serial number * @param {number} door Controller door ID (in the range [1..4]) * @param {array} passcodes Array of passcodes (in the range [0..999999]) * * @return {buffer} 64 byte NodeJS buffer with encoded activate-keypads request. */ SetDoorPasscodes: function (deviceId, { door, passcodes } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x8c, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt8(door, 8) for (let i = 0; i < 4; i++) { if (passcodes && passcodes.length > i) { const passcode = passcodes[i] > 0 && passcodes[i] < 1000000 ? passcodes[i] : 0 request.writeUInt32LE(passcode, 12 + 4 * i) } } return request }, /** * Encodes a get-antipassback request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded get-antipassback request. */ GetAntiPassback: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x86, 1) request.writeUInt32LE(deviceId, 4) return request }, /** * Encodes a set-antipassback request. * * @param {number} deviceId Controller serial number * @param {string} antipassback Anti-passback mode ('disabled', '(1:2);(3:4)', '(1,3):(2,4)', '1:(2,3)' or '1:(2,3,4)') * * @return {buffer} 64 byte NodeJS buffer with encoded set-antipassback request */ SetAntiPassback: function (deviceId, { antipassback } = {}) { const opcodes = require('../nodes/opcodes.js') const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x84, 1) request.writeUInt32LE(deviceId, 4) switch (antipassback) { case opcodes.AntiPassbackDisabled: request.writeUInt8(0, 8) break case opcodes.AntiPassback12_34: request.writeUInt8(1, 8) break case opcodes.AntiPassback13_24: request.writeUInt8(2, 8) break case opcodes.AntiPassback1_23: request.writeUInt8(3, 8) break case opcodes.AntiPassback1_234: request.writeUInt8(4, 8) break default: throw new Error(`invalid anti-passback ${antipassback}`) } return request }, /** * Encode a restore-default-parameters request. * * @param {number} deviceId Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded restore-default-parameters request. */ RestoreDefaultParameters: function (deviceId) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xc8, 1) request.writeUInt32LE(deviceId, 4) request.writeUInt32LE(0x55aaaa55, 8) return request }, } /** * Internal utility function to encode a PIN code 24-bit binary. * * @param {number} PIN Numeric PIN code in the rangee 0 to 999999 * @param {number} offset Index of time in buffer * * @param {buffer} 3 byte NodeJS buffer with little-endian encoded PIN * @private */ function uint24(PIN) { const bytes = [] bytes.push((PIN >> 0) & 0x00ff) bytes.push((PIN >> 8) & 0x00ff) bytes.push((PIN >> 16) & 0x00ff) return Buffer.from(bytes) } /** * Internal utility function to encode a timestamp as BCD. * * @param {string} datetime Timestamp, formatted as yyyy-mm-dd HH:mm:ss * @param {number} offset Index of time in buffer * * @param {buffer} 6 byte NodeJS buffer with BCD encoded timestamp. */ function datetime2bin(datetime) { const bytes = [] const re = /([0-9]{2})([0-9]{2})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/ const match = datetime.match(re) for (const m of match.slice(1)) { const b = parseInt(m, 10) const byte = ((b / 10) << 4) | b % 10 bytes.push(byte) } return Buffer.from(bytes) } /** * Internal utility function to encode a date as BCD. * * @param {string} date Date, formatted as yyyy-mm-dd * * @param {buffer} 4 byte NodeJS buffer with BCD encoded timestamp * */ function date2bin(date) { const bytes = [] const re = /([0-9]{2})([0-9]{2})-([0-9]{2})-([0-9]{2})/ const match = date.match(re) for (const m of match.slice(1)) { const b = parseInt(m, 10) const byte = ((b / 10) << 4) | b % 10 bytes.push(byte) } return Buffer.from(bytes) } /** * Internal utility function to convert an HHmm value to BCD * * @param {string} hhmm HHmm, formatted as HH:mm * * @param {buffer} 4 byte NodeJS buffer with BCD encoded timestamp */ function HHmm2bin(hhmm) { const bytes = [] const re = /([0-9]{2}):([0-9]{2})/ const match = hhmm.match(re) for (const m of match.slice(1)) { const b = parseInt(m, 10) const byte = ((b / 10) << 4) | b % 10 bytes.push(byte) } return Buffer.from(bytes) }