import logger from './logger'; import { ACTION_HOOK_TYPE_NAME, COMMANDS, EVENT_TYPE, HOOK_STATUS, IDeviceInfo, INPUT_REPORT_EVENT_NAME } from './types'; import { DEVICE_USAGE, TELEPHONY_DEVICE_USAGE_PAGE, requestTelephonyHID } from './utils'; /** * WebHID manager that incorporates all hid specific logic. * * @class WebHidManager */ export default class WebHidManager extends EventTarget { hidSupport: boolean; deviceInfo: IDeviceInfo; availableDevices: HIDDevice[]; isParseDescriptorsSuccess: boolean; outputEventGenerators: { [key: string]: Function; }; deviceCommand = { outputReport: { mute: { reportId: 0, usageOffset: -1 }, offHook: { reportId: 0, usageOffset: -1 }, ring: { reportId: 0, usageOffset: 0 }, hold: { reportId: 0, usageOffset: 0 } }, inputReport: { hookSwitch: { reportId: 0, usageOffset: -1, isAbsolute: false }, phoneMute: { reportId: 0, usageOffset: -1, isAbsolute: false } } }; private static instance: WebHidManager; /** * WebHidManager getInstance. * * @static * @returns {WebHidManager} - WebHidManager instance. */ static getInstance(): WebHidManager { if (!this.instance) { this.instance = new WebHidManager(); } return this.instance; } /** * Creates an instance of WebHidManager. * */ constructor() { super(); this.deviceInfo = {} as IDeviceInfo; this.hidSupport = this.isSupported(); this.availableDevices = []; this.isParseDescriptorsSuccess = false; this.outputEventGenerators = {}; } /** * Check support of hid in navigator. * - experimental API in Chrome. * * @returns {boolean} - True if supported, otherwise false. */ isSupported(): boolean { // @ts-ignore return !(!window.navigator.hid || !window.navigator.hid.requestDevice); } /** * Handler for requesting telephony hid devices. * * @returns {HIDDevice[]|null} */ async requestHidDevices() { if (!this.hidSupport) { logger.warn('The WebHID API is NOT supported!'); return null; } if (this.deviceInfo?.device && this.deviceInfo.device.opened) { await this.close(); } // @ts-ignore const devices = await navigator.hid.requestDevice(requestTelephonyHID); if (!devices || !devices.length) { logger.warn('No HID devices selected.'); return false; } this.availableDevices = devices; return devices; } /** * Handler for listen to already connected hid. * * @returns {void} */ async listenToConnectedHid() { const devices = await this.loadPairedDevices(); if (!devices || !devices.length) { logger.warn('No hid device found.'); return; } const telephonyDevice = this.getTelephonyDevice(devices); if (!telephonyDevice) { logger.warn('No HID device to request'); return; } await this.open(telephonyDevice); // restore the default state of hook and mic LED this.resetDeviceState(); // switch headsets to OFF_HOOK for mute/unmute commands this.sendDeviceReport({ command: COMMANDS.OFF_HOOK }); } /** * Get first telephony device from availableDevices. * * @param {HIDDevice[]} availableDevices -. * @returns {HIDDevice} -. */ private getTelephonyDevice(availableDevices: HIDDevice[]) { if (!availableDevices || !availableDevices.length) { logger.warn('No HID device to request'); return undefined; } return availableDevices?.find(device => this.findTelephonyCollectionInfo(device.collections)); } /** * Find telephony collection info from a list of collection infos. * * @private * @param {HIDCollectionInfo[]} deviceCollections -. * @returns {HIDCollectionInfo} - Hid collection info. */ private findTelephonyCollectionInfo(deviceCollections: HIDCollectionInfo[]) { return deviceCollections?.find( (collection: HIDCollectionInfo) => collection.usagePage === TELEPHONY_DEVICE_USAGE_PAGE ); } /** * Open the hid device and start listening to inputReport events. * * @param {HIDDevice} telephonyDevice -. * @returns {void} -. */ private async open(telephonyDevice: HIDDevice) { try { this.deviceInfo = { device: telephonyDevice } as IDeviceInfo; if (!this.deviceInfo || !this.deviceInfo.device) { logger.warn('no HID device found'); return; } if (!this.deviceInfo.device.opened) { await this.deviceInfo.device.open(); } this.isParseDescriptorsSuccess = await this.parseDeviceDescriptors(this.deviceInfo.device); if (!this.isParseDescriptorsSuccess) { logger.warn('Failed to parse webhid'); return; } this.dispatchEvent(new CustomEvent(EVENT_TYPE.INIT_DEVICE, { detail: { deviceInfo: { ...this.deviceInfo } as IDeviceInfo } })); // listen for input reports by registering an oninputreport event listener this.deviceInfo.device.oninputreport = await this.handleInputReport.bind(this); this.resetDeviceState(); } catch (e) { logger.error(`Error content open device:${e}`); } } /** * Close device and reset state. * * @returns {void}. */ async close() { try { await this.resetDeviceState(); if (this.availableDevices) { logger.info('clear available devices list'); this.availableDevices = []; } if (!this.deviceInfo) { return; } if (this.deviceInfo?.device?.opened) { await this.deviceInfo.device.close(); } if (this.deviceInfo.device) { this.deviceInfo.device.oninputreport = null; } this.deviceInfo = {} as IDeviceInfo; } catch (e) { logger.error(e); } } /** * Get paired hid devices. * * @returns {HIDDevice[]} */ async loadPairedDevices() { try { // @ts-ignore const devices = await navigator.hid.getDevices(); this.availableDevices = devices; return devices; } catch (e) { logger.error('loadPairedDevices error:', e); } } /** * Parse device descriptors - input and output reports. * * @param {HIDDevice} device -. * @returns {boolean} - True if descriptors have been parsed with success. */ parseDeviceDescriptors(device: HIDDevice) { try { this.outputEventGenerators = {}; if (!device || !device.collections) { logger.error('Undefined device collection'); return false; } const telephonyCollection = this.findTelephonyCollectionInfo(device.collections); if (!telephonyCollection || Object.keys(telephonyCollection).length === 0) { logger.error('No telephony collection'); return false; } if (telephonyCollection.inputReports) { if (!this.parseInputReports(telephonyCollection.inputReports)) { logger.warn('parse inputReports failed'); return false; } logger.warn('parse inputReports success'); } if (telephonyCollection.outputReports) { if (!this.parseOutputReports(telephonyCollection.outputReports)) { logger.warn('parse outputReports failed'); return false; } logger.warn('parse outputReports success'); return true; } logger.warn('parseDeviceDescriptors: returns false, end'); return false; } catch (e) { logger.error(`parseDeviceDescriptors error:${JSON.stringify(e, null, ' ')}`); return false; } } /** * HandleInputReport. * * @param {HIDInputReportEvent} event -. * @returns {void} -. */ handleInputReport(event: HIDInputReportEvent) { try { const { data, device, reportId } = event; if (reportId === 0) { logger.warn('handleInputReport: ignore invalid reportId'); return; } const inputReport = this.deviceCommand.inputReport; logger.warn(`current inputReport:${JSON.stringify(inputReport, null, ' ')}, reporId: ${reportId}`); if (reportId !== inputReport.hookSwitch.reportId && reportId !== inputReport.phoneMute.reportId) { logger.warn('handleInputReport:ignore unknown reportId'); return; } let hookStatusChange = false; let muteStatusChange = false; const reportData = new Uint8Array(data.buffer); const needReply = true; if (reportId === inputReport.hookSwitch.reportId) { const item = inputReport.hookSwitch; const byteIndex = Math.trunc(item.usageOffset / 8); const bitPosition = item.usageOffset % 8; // eslint-disable-next-line no-bitwise const usageOn = (data.getUint8(byteIndex) & (0x01 << bitPosition)) !== 0; logger.warn('recv hookSwitch ', usageOn ? HOOK_STATUS.OFF : HOOK_STATUS.ON); if (inputReport.hookSwitch.isAbsolute) { if (this.deviceInfo.hookStatus === HOOK_STATUS.ON && usageOn) { this.deviceInfo.hookStatus = HOOK_STATUS.OFF; hookStatusChange = true; } else if (this.deviceInfo.hookStatus === HOOK_STATUS.OFF && !usageOn) { this.deviceInfo.hookStatus = HOOK_STATUS.ON; hookStatusChange = true; } } else if (usageOn) { this.deviceInfo.hookStatus = this.deviceInfo.hookStatus === HOOK_STATUS.OFF ? HOOK_STATUS.ON : HOOK_STATUS.OFF; hookStatusChange = true; } } if (reportId === inputReport.phoneMute.reportId) { const item = inputReport.phoneMute; const byteIndex = Math.trunc(item.usageOffset / 8); const bitPosition = item.usageOffset % 8; // eslint-disable-next-line no-bitwise const usageOn = (data.getUint8(byteIndex) & (0x01 << bitPosition)) !== 0; logger.warn('recv phoneMute ', usageOn ? HOOK_STATUS.ON : HOOK_STATUS.OFF); if (inputReport.phoneMute.isAbsolute) { if (this.deviceInfo.muted !== usageOn) { this.deviceInfo.muted = usageOn; muteStatusChange = true; } } else if (usageOn) { this.deviceInfo.muted = !this.deviceInfo.muted; muteStatusChange = true; } } const inputReportData = { productName: device.productName, reportId: this.getHexByte(reportId), reportData, eventName: '', isMute: false, hookStatus: '' }; if (hookStatusChange) { // Answer key state change inputReportData.eventName = INPUT_REPORT_EVENT_NAME.ON_DEVICE_HOOK_SWITCH; inputReportData.hookStatus = this.deviceInfo.hookStatus; logger.warn(`hook status change: ${this.deviceInfo.hookStatus}`); } if (muteStatusChange) { // Mute key state change inputReportData.eventName = INPUT_REPORT_EVENT_NAME.ON_DEVICE_MUTE_SWITCH; inputReportData.isMute = this.deviceInfo.muted; logger.warn(`mute status change: ${this.deviceInfo.muted}`); } const actionResult = this.extractActionResult(inputReportData); this.dispatchEvent( new CustomEvent(EVENT_TYPE.UPDATE_DEVICE, { detail: { actionResult, deviceInfo: this.deviceInfo } }) ); logger.warn( `hookStatusChange=${ hookStatusChange }, muteStatusChange=${ muteStatusChange }, needReply=${ needReply}` ); if (needReply && (hookStatusChange || muteStatusChange)) { let newOffHook; if (this.deviceInfo.hookStatus === HOOK_STATUS.OFF) { newOffHook = true; } else if (this.deviceInfo.hookStatus === HOOK_STATUS.ON) { newOffHook = false; } else { logger.warn('Invalid hook status'); return; } this.sendReplyReport(reportId, newOffHook, this.deviceInfo.muted); } else { logger.warn(`Not sending reply report: needReply ${needReply}, hookStatusChange: ${hookStatusChange}, muteStatusChange: ${muteStatusChange}`); } } catch (e) { logger.error(e); } } /** * Extract action result. * * @private * @param {*} data -. * @returns {{eventName: string}} - EventName. */ private extractActionResult(data: any) { switch (data.eventName) { case INPUT_REPORT_EVENT_NAME.ON_DEVICE_HOOK_SWITCH: return { eventName: data.hookStatus === HOOK_STATUS.ON ? ACTION_HOOK_TYPE_NAME.HOOK_SWITCH_ON : ACTION_HOOK_TYPE_NAME.HOOK_SWITCH_OFF }; case INPUT_REPORT_EVENT_NAME.ON_DEVICE_MUTE_SWITCH: return { eventName: data.isMute ? ACTION_HOOK_TYPE_NAME.MUTE_SWITCH_ON : ACTION_HOOK_TYPE_NAME.MUTE_SWITCH_OFF }; case 'ondevicevolumechange': return { eventName: data.volumeStatus === 'up' ? ACTION_HOOK_TYPE_NAME.VOLUME_CHANGE_UP : ACTION_HOOK_TYPE_NAME.VOLUME_CHANGE_DOWN }; default: break; } } /** * Reset device state. * * @returns {void} -. */ resetDeviceState() { if (!this.deviceInfo || !this.deviceInfo.device || !this.deviceInfo.device.opened) { return; } this.deviceInfo.hookStatus = HOOK_STATUS.ON; this.deviceInfo.muted = false; this.deviceInfo.ring = false; this.deviceInfo.hold = false; this.sendDeviceReport({ command: COMMANDS.ON_HOOK }); this.sendDeviceReport({ command: COMMANDS.MUTE_OFF }); } /** * Parse input reports. * * @param {HIDReportInfo[]} inputReports -. * @returns {void} -. */ private parseInputReports(inputReports: HIDReportInfo[]) { inputReports.forEach(report => { if (!report || !report.items?.length || report.reportId === undefined) { return; } let usageOffset = 0; report.items.forEach((item: HIDReportItem) => { if ( item.usages === undefined || item.reportSize === undefined || item.reportCount === undefined || item.isAbsolute === undefined ) { logger.warn('parseInputReports invalid parameters!'); return; } const reportSize = item.reportSize ?? 0; const reportId = report.reportId ?? 0; item.usages.forEach((usage: number, i: number) => { switch (usage) { case DEVICE_USAGE.hookSwitch.usageId: this.deviceCommand.inputReport.hookSwitch = { reportId, usageOffset: usageOffset + (i * reportSize), isAbsolute: item.isAbsolute ?? false }; break; case DEVICE_USAGE.phoneMute.usageId: this.deviceCommand.inputReport.phoneMute = { reportId, usageOffset: usageOffset + (i * reportSize), isAbsolute: item.isAbsolute ?? false }; break; default: break; } }); usageOffset += item.reportCount * item.reportSize; }); }); if (!this.deviceCommand.inputReport.phoneMute || !this.deviceCommand.inputReport.hookSwitch) { logger.warn('parseInputReports - no phoneMute or hookSwitch. Skip. Returning false'); return false; } return true; } /** * Parse output reports. * * @private * @param {HIDReportInfo[]} outputReports -. * @returns {void} -. */ private parseOutputReports(outputReports: HIDReportInfo[]) { outputReports.forEach((report: HIDReportInfo) => { if (!report || !report.items?.length || report.reportId === undefined) { return; } let usageOffset = 0; const usageOffsetMap: Map = new Map(); report.items.forEach(item => { if (item.usages === undefined || item.reportSize === undefined || item.reportCount === undefined) { logger.warn('parseOutputReports invalid parameters!'); return; } const reportSize = item.reportSize ?? 0; const reportId = report.reportId ?? 0; item.usages.forEach((usage: number, i: number) => { switch (usage) { case DEVICE_USAGE.mute.usageId: this.deviceCommand.outputReport.mute = { reportId, usageOffset: usageOffset + (i * reportSize) }; usageOffsetMap.set(usage, usageOffset + (i * reportSize)); break; case DEVICE_USAGE.offHook.usageId: this.deviceCommand.outputReport.offHook = { reportId, usageOffset: usageOffset + (i * reportSize) }; usageOffsetMap.set(usage, usageOffset + (i * reportSize)); break; case DEVICE_USAGE.ring.usageId: this.deviceCommand.outputReport.ring = { reportId, usageOffset: usageOffset + (i * reportSize) }; usageOffsetMap.set(usage, usageOffset + (i * reportSize)); break; case DEVICE_USAGE.hold.usageId: this.deviceCommand.outputReport.hold = { reportId, usageOffset: usageOffset = i * reportSize }; usageOffsetMap.set(usage, usageOffset + (i * reportSize)); break; default: break; } }); usageOffset += item.reportCount * item.reportSize; }); const reportLength = usageOffset; for (const [ usage, offset ] of usageOffsetMap) { this.outputEventGenerators[usage] = (val: number) => { const reportData = new Uint8Array(reportLength / 8); if (offset >= 0 && val) { const byteIndex = Math.trunc(offset / 8); const bitPosition = offset % 8; // eslint-disable-next-line no-bitwise reportData[byteIndex] = 1 << bitPosition; } return reportData; }; } }); let hook, mute, ring; for (const item in this.outputEventGenerators) { if (Object.prototype.hasOwnProperty.call(this.outputEventGenerators, item)) { let newItem = this.getHexByte(item); newItem = `0x0${newItem}`; if (DEVICE_USAGE.mute.usageId === Number(newItem)) { mute = this.outputEventGenerators[DEVICE_USAGE.mute.usageId]; } else if (DEVICE_USAGE.offHook.usageId === Number(newItem)) { hook = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId]; } else if (DEVICE_USAGE.ring.usageId === Number(newItem)) { ring = this.outputEventGenerators[DEVICE_USAGE.ring.usageId]; } } } if (!mute && !ring && !hook) { return false; } return true; } /** * Send device report. * * @param {{ command: string }} data -. * @returns {void} -. */ async sendDeviceReport(data: { command: string; }) { if (!data || !data.command || !this.deviceInfo || !this.deviceInfo.device || !this.deviceInfo.device.opened || !this.isParseDescriptorsSuccess) { logger.warn('There are currently non-compliant conditions'); return; } logger.warn(`sendDeviceReport data.command: ${data.command}`); if (data.command === COMMANDS.MUTE_ON || data.command === COMMANDS.MUTE_OFF) { if (!this.outputEventGenerators[DEVICE_USAGE.mute.usageId]) { logger.warn('current no parse mute event'); return; } } else if (data.command === COMMANDS.ON_HOOK || data.command === COMMANDS.OFF_HOOK) { if (!this.outputEventGenerators[DEVICE_USAGE.offHook.usageId]) { logger.warn('current no parse offHook event'); return; } } else if (data.command === COMMANDS.ON_RING || data.command === COMMANDS.OFF_RING) { if (!this.outputEventGenerators[DEVICE_USAGE.ring.usageId]) { logger.warn('current no parse ring event'); return; } } let oldOffHook; let newOffHook; let newMuted; let newRing; let newHold; let offHookReport; let muteReport; let ringReport; let holdReport; let reportData = new Uint8Array(); const reportId = this.matchReportId(data.command); if (reportId === 0) { logger.warn(`Unsupported command ${data.command}`); return; } /* keep old status. */ const oldMuted = this.deviceInfo.muted; if (this.deviceInfo.hookStatus === HOOK_STATUS.OFF) { oldOffHook = true; } else if (this.deviceInfo.hookStatus === HOOK_STATUS.ON) { oldOffHook = false; } else { logger.warn('Invalid hook status'); return; } const oldRing = this.deviceInfo.ring; const oldHold = this.deviceInfo.hold; logger.warn( `send device command: old_hook=${oldOffHook}, old_muted=${oldMuted}, old_ring=${oldRing}` ); /* get new status. */ switch (data.command) { case COMMANDS.MUTE_ON: newMuted = true; break; case COMMANDS.MUTE_OFF: newMuted = false; break; case COMMANDS.ON_HOOK: newOffHook = false; break; case COMMANDS.OFF_HOOK: newOffHook = true; break; case COMMANDS.ON_RING: newRing = true; break; case COMMANDS.OFF_RING: newRing = false; break; case COMMANDS.ON_HOLD: newHold = true; break; case COMMANDS.OFF_HOLD: newHold = false; break; default: logger.info(`Unknown command ${data.command}`); return; } logger.warn( `send device command: new_hook = ${newOffHook}, new_muted = ${newMuted}, new_ring = ${newRing} new_hold = ${newHold}` ); if (this.outputEventGenerators[DEVICE_USAGE.mute.usageId]) { if (newMuted === undefined) { muteReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](oldMuted); } else { muteReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](newMuted); } } if (this.outputEventGenerators[DEVICE_USAGE.offHook.usageId]) { if (newOffHook === undefined) { offHookReport = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId](oldOffHook); } else { offHookReport = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId](newOffHook); } } if (this.outputEventGenerators[DEVICE_USAGE.ring.usageId]) { if (newRing === undefined) { ringReport = this.outputEventGenerators[DEVICE_USAGE.ring.usageId](oldRing); } else { ringReport = this.outputEventGenerators[DEVICE_USAGE.ring.usageId](newRing); } } if (this.outputEventGenerators[DEVICE_USAGE.hold.usageId]) { holdReport = this.outputEventGenerators[DEVICE_USAGE.hold.usageId](oldHold); } if (reportId === this.deviceCommand.outputReport.mute.reportId) { reportData = new Uint8Array(muteReport); } if (reportId === this.deviceCommand.outputReport.offHook.reportId) { reportData = new Uint8Array(offHookReport); } if (reportId === this.deviceCommand.outputReport.ring.reportId) { reportData = new Uint8Array(ringReport); } if (reportId === this.deviceCommand.outputReport.hold.reportId) { reportData = new Uint8Array(holdReport); } logger.warn(`[sendDeviceReport] send device command (before call webhid API) ${data.command}: reportId=${reportId}, reportData=${reportData}`); logger.warn(`reportData is ${JSON.stringify(reportData, null, ' ')}`); await this.deviceInfo.device.sendReport(reportId, reportData); /* update new status. */ this.updateDeviceStatus(data); } /** * Update device status. * * @private * @param {{ command: string; }} data -. * @returns {void} */ private updateDeviceStatus(data: { command: string; }) { switch (data.command) { case COMMANDS.MUTE_ON: this.deviceInfo.muted = true; break; case COMMANDS.MUTE_OFF: this.deviceInfo.muted = false; break; case COMMANDS.ON_HOOK: this.deviceInfo.hookStatus = HOOK_STATUS.ON; break; case COMMANDS.OFF_HOOK: this.deviceInfo.hookStatus = HOOK_STATUS.OFF; break; case COMMANDS.ON_RING: this.deviceInfo.ring = true; break; case COMMANDS.OFF_RING: this.deviceInfo.ring = false; break; case COMMANDS.ON_HOLD: this.deviceInfo.hold = true; break; case 'offHold': this.deviceInfo.hold = false; break; default: logger.warn(`Unknown command ${data.command}`); break; } logger.warn( `[updateDeviceStatus] device status after send command: hook=${this.deviceInfo.hookStatus}, muted=${this.deviceInfo.muted}, ring=${this.deviceInfo.ring}` ); } /** * Math given command with known commands. * * @private * @param {string} command -. * @returns {number} ReportId. */ private matchReportId(command: string) { switch (command) { case COMMANDS.MUTE_ON: case COMMANDS.MUTE_OFF: return this.deviceCommand.outputReport.mute.reportId; case COMMANDS.ON_HOOK: case COMMANDS.OFF_HOOK: return this.deviceCommand.outputReport.offHook.reportId; case COMMANDS.ON_RING: case COMMANDS.OFF_RING: return this.deviceCommand.outputReport.ring.reportId; case COMMANDS.ON_HOLD: case COMMANDS.OFF_HOLD: return this.deviceCommand.outputReport.hold.reportId; default: logger.info(`Unknown command ${command}`); return 0; } } /** * Send reply report to device. * * @param {number} inputReportId -. * @param {(string | boolean | undefined)} curOffHook -. * @param {(string | undefined)} curMuted -. * @returns {void} -. */ private async sendReplyReport( inputReportId: number, curOffHook: string | boolean | undefined, curMuted: boolean | string | undefined ) { const reportId = this.retriveInputReportId(inputReportId); if (!this.deviceInfo || !this.deviceInfo.device || !this.deviceInfo.device.opened) { logger.warn('[sendReplyReport] device is not opened or does not exist'); return; } if (reportId === 0 || curOffHook === undefined || curMuted === undefined) { logger.warn(`[sendReplyReport] return, provided data not valid, reportId: ${reportId}, curOffHook: ${curOffHook}, curMuted: ${curMuted}`); return; } let reportData = new Uint8Array(); let muteReport; let offHookReport; let ringReport; if (this.deviceCommand.outputReport.offHook.reportId === this.deviceCommand.outputReport.mute.reportId) { muteReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](curMuted); offHookReport = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId](curOffHook); reportData = new Uint8Array(offHookReport); for (const [ i, data ] of muteReport.entries()) { // eslint-disable-next-line no-bitwise reportData[i] |= data; } } else if (reportId === this.deviceCommand.outputReport.offHook.reportId) { offHookReport = this.outputEventGenerators[DEVICE_USAGE.offHook.usageId](curOffHook); reportData = new Uint8Array(offHookReport); } else if (reportId === this.deviceCommand.outputReport.mute.reportId) { muteReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](curMuted); reportData = new Uint8Array(muteReport); } else if (reportId === this.deviceCommand.outputReport.ring.reportId) { ringReport = this.outputEventGenerators[DEVICE_USAGE.mute.usageId](curMuted); reportData = new Uint8Array(ringReport); } logger.warn(`[sendReplyReport] send device reply: reportId=${reportId}, reportData=${reportData}`); await this.deviceInfo.device.sendReport(reportId, reportData); } /** * Retrieve input report id. * * @private * @param {number} inputReportId -. * @returns {number} ReportId -. */ private retriveInputReportId(inputReportId: number) { let reportId = 0; if (this.deviceCommand.outputReport.offHook.reportId === this.deviceCommand.outputReport.mute.reportId) { reportId = this.deviceCommand.outputReport.offHook.reportId; } else if (inputReportId === this.deviceCommand.inputReport.hookSwitch.reportId) { reportId = this.deviceCommand.outputReport.offHook.reportId; } else if (inputReportId === this.deviceCommand.inputReport.phoneMute.reportId) { reportId = this.deviceCommand.outputReport.mute.reportId; } return reportId; } /** * Get the hexadecimal bytes. * * @param {number|string} data -. * @returns {string} */ getHexByte(data: number | string) { let hex = Number(data).toString(16); while (hex.length < 2) { hex = `0${hex}`; } return hex; } }