UNPKG

uhppoted

Version:

NodeJS module wrapper for the interface to UHPPOTE TCP/IP Wiegand Access Controllers

900 lines (763 loc) 23.7 kB
const errors = require('./errors.js') const { Buffer } = require('node:buffer') 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 ipx = require('./ipx.js') 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-listener request. * * @param {number} deviceId Controller serial number * @param {string} address IPv4 listener address for controller events * @param {number} port IPv4 listener port for controller events * @param {number} interval Auto-send interval ([0..255] seconds), 0 disables auto-send. * * @return {buffer} 64 byte NodeJS buffer with encoded set-address request */ SetListener: function (deviceId, { address, port, interval } = {}) { const ipx = require('./ipx.js') 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 }, /** * 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-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('./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 errors.InvalidDoorControl(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-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 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 keypad PIN ${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 (controller) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0x54, 1) request.writeUInt32LE(controller, 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 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 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) } }) }) } HHmm2bin(task.start).copy(request, 23) request.writeUInt8(task.door, 25) if (!isNaN(task.task)) { request.writeUInt8(task.task - 1, 26) } else { 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}'`) } } if (task.cards) { request.writeUInt8(task.cards, 27) } 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 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 }, /** * 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 }, /** * 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 }, /** * 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 }, /** * Encode a set-interlock request. * * @param {number} deviceId Controller serial number * @param {number} interlock Interlock mode (0,1,2,3,4 or 8) * * @return {buffer} 64 byte NodeJS buffer with encoded set-pc-control 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} controller 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 set-pc-control request. */ ActivateKeypads: function (controller, { keypads } = {}) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xa4, 1) request.writeUInt32LE(controller, 4) request.writeUInt8(keypads['1'] ? 1 : 0, 8) request.writeUInt8(keypads['2'] ? 1 : 0, 9) request.writeUInt8(keypads['3'] ? 1 : 0, 10) request.writeUInt8(keypads['4'] ? 1 : 0, 11) return request }, /** * Encodes a set-door-passcodes request. * * @param {number} controller Controller serial number * @param {number} door Door ID [1..4] * @param {object} passcodes Array of up to 4 passcodes in the range [0..999999] * (0 corresponds to 'no code') * * @return {buffer} 64 byte NodeJS buffer with encoded set-pc-control request. */ SetDoorPasscodes: function (controller, { door, passcodes } = {}) { const request = Buffer.alloc(64) const codes = [0, 0, 0, 0] for (let i = 0; i < 4; i++) { if (passcodes.length > i && passcodes[i] > 0 && passcodes[i] < 1000000) { codes[i] = passcodes[i] } } request.writeUInt8(0x17, 0) request.writeUInt8(0x8c, 1) request.writeUInt32LE(controller, 4) request.writeUInt8(door, 8) request.writeUInt32LE(codes[0], 12) request.writeUInt32LE(codes[1], 16) request.writeUInt32LE(codes[2], 20) request.writeUInt32LE(codes[3], 24) return request }, /** * Restore default parameters request. * * @param {number} controller Controller serial number * * @return {buffer} 64 byte NodeJS buffer with encoded restore-default-parameters request. */ RestoreDefaultParameters: function (controller) { const request = Buffer.alloc(64) request.writeUInt8(0x17, 0) request.writeUInt8(0xc8, 1) request.writeUInt32LE(controller, 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. * @private */ 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 {number} offset Index of time in buffer * * @param {buffer} 4 byte NodeJS buffer with BCD encoded timestamp * @private */ 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) }