diff --git a/client/package-lock.json b/client/package-lock.json index 2f6715a..1084572 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -1,6 +1,6 @@ { "name": "rdio-scanner-client", - "version": "4.5.1", + "version": "4.5.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/client/package.json b/client/package.json index 6d1c32d..21583c7 100644 --- a/client/package.json +++ b/client/package.json @@ -33,5 +33,5 @@ "build": "ng build --base-href ./ --prod", "start": "ng serve" }, - "version": "4.5.1" + "version": "4.5.2" } diff --git a/client/src/app/components/rdio-scanner/rdio-scanner.component.ts b/client/src/app/components/rdio-scanner/rdio-scanner.component.ts index e9c8919..9d149be 100644 --- a/client/src/app/components/rdio-scanner/rdio-scanner.component.ts +++ b/client/src/app/components/rdio-scanner/rdio-scanner.component.ts @@ -26,6 +26,7 @@ import { BehaviorSubject, NEVER, Subscription, timer } from 'rxjs'; import { name as appName, version } from '../../../../package.json'; import { RdioScannerAvoidOptions, + RdioScannerBeepStyle, RdioScannerCall, RdioScannerConfig, RdioScannerEvent, @@ -144,10 +145,15 @@ export class AppRdioScannerComponent implements AfterViewInit, OnDestroy { if (this.auth) { this.authFocus(); - } else if (!this.liveFeedOffline) { - this.appRdioScannerService.beep(options || this.call || this.callPrevious ? 1 : 3); + } else { + if (options || this.call || this.callPrevious) { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Activate); - this.appRdioScannerService.avoid(options); + this.appRdioScannerService.avoid(options); + + } else { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Denied); + } this.updateDimmer(); } @@ -176,10 +182,15 @@ export class AppRdioScannerComponent implements AfterViewInit, OnDestroy { if (this.auth) { this.authFocus(); - } else if (!this.liveFeedOffline) { - this.appRdioScannerService.beep(this.call || this.callPrevious ? this.holdSys ? 2 : 1 : 3); + } else { + if (this.call || this.callPrevious) { + this.appRdioScannerService.beep(this.holdSys ? RdioScannerBeepStyle.Deactivate : RdioScannerBeepStyle.Activate); - this.appRdioScannerService.holdSystem(); + this.appRdioScannerService.holdSystem(); + + } else { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Denied); + } this.updateDimmer(); } @@ -189,10 +200,15 @@ export class AppRdioScannerComponent implements AfterViewInit, OnDestroy { if (this.auth) { this.authFocus(); - } else if (!this.liveFeedOffline) { - this.appRdioScannerService.beep(this.call || this.callPrevious ? this.holdTg ? 2 : 1 : 3); + } else { + if (this.call || this.callPrevious) { + this.appRdioScannerService.beep(this.holdTg ? RdioScannerBeepStyle.Deactivate : RdioScannerBeepStyle.Activate); - this.appRdioScannerService.holdTalkgroup(); + this.appRdioScannerService.holdTalkgroup(); + + } else { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Denied); + } this.updateDimmer(); } @@ -204,17 +220,17 @@ export class AppRdioScannerComponent implements AfterViewInit, OnDestroy { } else { if (this.liveFeedOffline) { - this.appRdioScannerService.beep(2); + this.appRdioScannerService.beep(RdioScannerBeepStyle.Deactivate); this.stopOfflinePlay(); } else { - this.appRdioScannerService.beep(this.liveFeedActive ? 2 : 1); + this.appRdioScannerService.beep(this.liveFeedActive ? RdioScannerBeepStyle.Deactivate : RdioScannerBeepStyle.Activate); this.appRdioScannerService.liveFeed(); - - this.updateDimmer(); } + + this.updateDimmer(); } } @@ -439,9 +455,14 @@ export class AppRdioScannerComponent implements AfterViewInit, OnDestroy { this.authFocus(); } else { - this.appRdioScannerService.beep(this.liveFeedPaused ? 2 : 1); + if (this.liveFeedPaused) { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Deactivate).then(() => this.appRdioScannerService.pause()); - this.appRdioScannerService.pause(); + } else { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Activate); + + this.appRdioScannerService.pause(); + } this.updateDimmer(); } @@ -473,9 +494,14 @@ export class AppRdioScannerComponent implements AfterViewInit, OnDestroy { this.authFocus(); } else { - this.appRdioScannerService.beep(this.call || this.callPrevious ? 1 : 3); + if (!this.liveFeedPaused && (this.call || this.callPrevious)) { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Activate); - this.appRdioScannerService.replay(); + this.appRdioScannerService.replay(); + + } else { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Denied); + } this.updateDimmer(); } @@ -579,10 +605,15 @@ export class AppRdioScannerComponent implements AfterViewInit, OnDestroy { if (this.auth) { this.authFocus(); - } else if (!this.callPending) { - this.appRdioScannerService.beep(this.call || this.callPrevious ? 1 : 3); + } else { + if (this.call && !this.callPending) { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Activate); - this.appRdioScannerService.skip(options); + this.appRdioScannerService.skip(options); + + } else { + this.appRdioScannerService.beep(RdioScannerBeepStyle.Denied); + } this.updateDimmer(); } @@ -804,19 +835,23 @@ export class AppRdioScannerComponent implements AfterViewInit, OnDestroy { } private updateDimmer(): void { - this.dimmerTimer?.unsubscribe(); + if (this.config?.useDimmer) { + const delay = typeof this.config.useDimmer === 'boolean' ? 5000 : this.config.useDimmer * 1000; - this.dimmer = true; - - this.dimmerTimer = timer(5 * 1000).subscribe(() => { this.dimmerTimer?.unsubscribe(); - this.dimmerTimer = undefined; + this.dimmer = true; - this.dimmer = false; + this.dimmerTimer = timer(delay).subscribe(() => { + this.dimmerTimer?.unsubscribe(); - this.ngDetectChanges(); - }); + this.dimmerTimer = undefined; + + this.dimmer = false; + + this.ngDetectChanges(); + }); + } } private updateDisplay(time = this.callTime): void { diff --git a/client/src/app/components/rdio-scanner/rdio-scanner.service.ts b/client/src/app/components/rdio-scanner/rdio-scanner.service.ts index 1780fd6..dd7ef52 100644 --- a/client/src/app/components/rdio-scanner/rdio-scanner.service.ts +++ b/client/src/app/components/rdio-scanner/rdio-scanner.service.ts @@ -23,6 +23,7 @@ import { interval, timer } from 'rxjs'; import { takeWhile } from 'rxjs/operators'; import { RdioScannerAvoidOptions, + RdioScannerBeepStyle, RdioScannerCall, RdioScannerConfig, RdioScannerEvent, @@ -70,7 +71,7 @@ export class AppRdioScannerService implements OnDestroy { private config: RdioScannerConfig = { allowDownload: true, - keyBeep: true, + keypadBeeps: false, systems: [], useDimmer: true, useGroup: true, @@ -165,25 +166,42 @@ export class AppRdioScannerService implements OnDestroy { }); } - beep(style = 1): void { - if (this.config.keyBeep) { - switch (style) { - case 1: - this.beep1(); - break; + beep(style = RdioScannerBeepStyle.Activate): Promise { + return new Promise((resolve) => { + if (!this.beepContext || !this.config.keypadBeeps) { + resolve(); - case 2: - this.beep2(); - break; - - case 3: - this.beep3(); - break; - - default: - this.beep1(); + return; } - } + + const context = this.beepContext; + + const gn = context.createGain(); + + const seq = this.config.keypadBeeps[style]; + + gn.gain.value = .1; + + gn.connect(context.destination); + + seq.forEach((beep, index) => { + const osc = context.createOscillator(); + + osc.connect(gn); + + osc.frequency.value = beep.frequency; + + osc.type = beep.type; + + if (index === seq.length - 1) { + osc.onended = () => resolve(); + } + + osc.start(context.currentTime + beep.begin); + + osc.stop(context.currentTime + beep.end); + }); + }); } holdSystem(options?: { resubscribe: boolean }): void { @@ -489,98 +507,8 @@ export class AppRdioScannerService implements OnDestroy { } } - private beep1(): void { - const context = this.beepContext; - - if (context) { - const gn = context.createGain(); - - const osc = context.createOscillator(); - - gn.connect(context.destination); - - gn.gain.value = .1; - - osc.connect(gn); - - osc.frequency.value = 1200; - - osc.type = 'square'; - - context.resume().then(() => { - osc.start(); - - osc.stop(context.currentTime + .05); - }); - } - } - - private beep2(): void { - const context = this.beepContext; - - if (context) { - const gn = context.createGain(); - - const osc1 = context.createOscillator(); - const osc2 = context.createOscillator(); - - gn.connect(context.destination); - - gn.gain.value = .1; - - osc1.connect(gn); - osc2.connect(gn); - - osc1.frequency.value = 1200; - osc2.frequency.value = 925; - - osc1.type = 'square'; - osc2.type = 'square'; - - context.resume().then(() => { - osc1.start(); - osc1.stop(context.currentTime + .1); - - osc2.start(context.currentTime + .1); - osc2.stop(context.currentTime + .2); - }); - } - } - - private beep3(): void { - const context = this.beepContext; - - if (context) { - const gn = context.createGain(); - - const osc1 = context.createOscillator(); - const osc2 = context.createOscillator(); - - gn.connect(context.destination); - - gn.gain.value = .1; - - osc1.connect(gn); - osc2.connect(gn); - - osc1.frequency.value = 925; - osc2.frequency.value = 925; - - osc1.type = 'square'; - osc2.type = 'square'; - - context.resume().then(() => { - osc1.start(); - osc1.stop(context.currentTime + .05); - - osc2.start(context.currentTime + .1); - osc2.stop(context.currentTime + .15); - }); - } - } - private bootstrapAudio(): void { - const events = ['mousedown', 'touchdown']; + const events = ['keydown', 'mousedown', 'touchdown']; const bootstrap = async () => { if (!this.audioContext) { @@ -603,7 +531,13 @@ export class AppRdioScannerService implements OnDestroy { await this.audioContext.resume(); this.audioContext.onstatechange = () => resume(); + } + if (this.beepContext) { + await this.beepContext.resume(); + } + + if (this.audioContext && this.beepContext) { events.forEach((event) => document.body.removeEventListener(event, bootstrap)); } }; diff --git a/client/src/app/components/rdio-scanner/rdio-scanner.ts b/client/src/app/components/rdio-scanner/rdio-scanner.ts index 89a6b71..94c935b 100644 --- a/client/src/app/components/rdio-scanner/rdio-scanner.ts +++ b/client/src/app/components/rdio-scanner/rdio-scanner.ts @@ -25,6 +25,19 @@ export interface RdioScannerAvoidOptions { status?: boolean; } +export interface RdioScannerBeep { + begin: number; + end: number; + frequency: number; + type: OscillatorType; +} + +export enum RdioScannerBeepStyle { + Activate = 'activate', + Deactivate = 'deactivate', + Denied = 'denied', +} + export interface RdioScannerCall { audio?: { type: 'Buffer'; @@ -59,9 +72,9 @@ export interface RdioScannerCallSource { export interface RdioScannerConfig { allowDownload: boolean; - keyBeep: boolean; + keypadBeeps: RdioScannerKeypadBeeps | false; systems: RdioScannerSystem[]; - useDimmer: boolean; + useDimmer: boolean | number; useGroup: boolean; useLed: boolean; } @@ -92,6 +105,12 @@ export interface RdioScannerGroup { status: RdioScannerGroupStatus; } +export interface RdioScannerKeypadBeeps { + [RdioScannerBeepStyle.Activate]: RdioScannerBeep[]; + [RdioScannerBeepStyle.Deactivate]: RdioScannerBeep[]; + [RdioScannerBeepStyle.Denied]: RdioScannerBeep[]; +} + export interface RdioScannerList { count: number; dateStart: Date; diff --git a/docs/config.md b/docs/config.md index 3f037a4..ab490a7 100644 --- a/docs/config.md +++ b/docs/config.md @@ -63,16 +63,18 @@ This file is at the heart of [Rdio Scanner](https://github.com/chuot/rdio-scanne "disableAudioConversion": false, // (boolean) Emit beeps when clicking buttons - // Default value is true - "keyBeep": true, + // Default value is true which is converted to Uniden style beeps + // See KeypadBeeps section below for more details + "keypadBeeps": true, // (number) Clear the database for audio files older than the specified number of days // Default value is 7 // Specifying a value of 0 will disable this feature "pruneDays": 7 - // (boolean) Turn off the screen backlight when it is inactive - // Default value is true + // (boolean | number) Turn off the screen backlight when it is inactive + // You can specify the delay as a number of seconds + // Default value is true which the same as 5 seconds "useDimmer": true // (boolean) Also toggle talkgroups based on their group assignment @@ -494,6 +496,133 @@ Default value is an empty array `[]` . > Note in this example for API key `d2079382-07df-4aa9-8940-8fb9e4ef5f2e` that systems 12, 15 and 31 will downstream all of their talkgroups, unlike systems 11 and 21 which will only downstream some of their talkgroups. +## KeypadBeeps - rdioScanner.options.keypadBeeps + +Each button press can emit a specific beep depending on whether a function is activated, deactivated or denied. + +**Structure of the rdioScanner.options.keypadBeeps object** + +``` typescript +options: { + keypadBeeps: false | { + activate: { // beeps sequence when a function is activated + begin: number; // seconds + end: number; // seconds + frequency: number; // hertz + type: 'sine' | 'square' | 'sawtooth' | 'triangle'; + }[], + deactivate: { // beeps sequence when a function is deactivates + begin: number; // seconds + end: number; // seconds + frequency: number; // hertz + type: 'sine' | 'square' | 'sawtooth' | 'triangle'; + }[], + denied: { // beeps sequence when a function is denied + begin: number; // seconds + end: number; // seconds + frequency: number; // hertz + type: 'sine' | 'square' | 'sawtooth' | 'triangle'; + }[], + }; +}[] +``` + +Default value is (*Uniden Style*): + +``` json +"options": { + "keypadBeeps": { + "activate": [ + { + "begin": 0, + "end": 0.05, + "frequency": 1200, + "type": "square" + } + ], + "deactivate": [ + { + "begin": 0, + "end": 0.1, + "frequency": 1200, + "type": "square" + }, + { + "begin": 0.1, + "end": 0.2, + "frequency": 925, + "type": "square" + } + ], + "denied": [ + { + "begin": 0, + "end": 0.05, + "frequency": 925, + "type": "square" + }, + { + "begin": 0.1, + "end": 0.15, + "frequency": 925, + "type": "square" + } + ] + } +} +``` + +**Example - Whistler style** + +``` json +"options": { + "keypadBeeps": { + "activate": [ + { + "begin": 0, + "end": 0.05, + "frequency": 2000, + "type": "triangle" + } + ], + "deactivate": [ + { + "begin": 0, + "end": 0.04, + "frequency": 1500, + "type": "triangle" + }, + { + "begin": 0.04, + "end": 0.08, + "frequency": 1400, + "type": "triangle" + } + ], + "denied": [ + { + "begin": 0, + "end": 0.04, + "frequency": 1400, + "type": "triangle" + }, + { + "begin": 0.05, + "end": 0.09, + "frequency": 1400, + "type": "triangle" + }, + { + "begin": 0.1, + "end": 0.14, + "frequency": 1400, + "type": "triangle" + } + ] + } +} +``` + ## Systems - rdioScanner.systems The heart of [Rdio Scanner](https://github.com/chuot/rdio-scanner) where all your systems and talkgroups are defined. Audio files to unknown systems / talkgroups will not be ingested. diff --git a/package.json b/package.json index 6cf4749..dfb341e 100644 --- a/package.json +++ b/package.json @@ -11,5 +11,5 @@ "start": "node run", "update": "node update" }, - "version": "4.5.1" + "version": "4.5.2" } diff --git a/server/.eslintrc.json b/server/.eslintrc.json index d097bf7..5ec2405 100644 --- a/server/.eslintrc.json +++ b/server/.eslintrc.json @@ -13,5 +13,11 @@ "ecmaVersion": 2018 }, "rules": { + "max-len": [ + "error", + { + "code":140 + } + ] } } \ No newline at end of file diff --git a/server/lib/rdio-scanner/controller.js b/server/lib/rdio-scanner/controller.js index 9488fb4..b9bc189 100644 --- a/server/lib/rdio-scanner/controller.js +++ b/server/lib/rdio-scanner/controller.js @@ -49,137 +49,7 @@ class Controller extends EventEmitter { this.setMaxListeners(0); - this.config = ctx.config; - - this.config.access = this.config.access !== undefined ? this.config.access : null; - - this.config.apiKeys = this.config.apiKeys !== undefined ? this.config.apiKeys : uuid.v4(); - - if (this.config.options === null || typeof this.config.options !== 'object') { - this.config.options = {}; - } - - this.config.options.allowDownload = typeof this.config.options.allowDownload === 'boolean' ? this.config.options.allowDownload - : typeof this.config.allowDownload === 'boolean' ? this.config.allowDownload : true; - - if (this.config.allowDownload !== null || this.config.allowDownload !== undefined) { - delete this.config.allowDownload; - } - - this.config.options.disableAudioConversion = typeof this.config.options.disableAudioConversion === 'boolean' ? this.config.options.disableAudioConversion - : typeof this.config.disableAudioConversion === 'boolean' ? this.config.disableAudioConversion : false; - - if (this.config.disableAudioConversion !== null || this.config.disableAudioConversion !== undefined) { - delete this.config.disableAudioConversion; - } - - this.config.options.keyBeep = typeof this.config.options.keyBeep === 'boolean' ? this.config.options.keyBeep : true; - - this.config.options.pruneDays = typeof this.config.options.pruneDays === 'number' ? this.config.options.pruneDays - : typeof this.config.pruneDays === 'number' ? this.config.pruneDays : 7; - - if (this.config.pruneDays !== null || this.config.pruneDays !== undefined) { - delete this.config.pruneDays; - } - - this.config.options.useDimmer = typeof this.config.options.useDimmer === 'boolean' ? this.config.options.useDimmer - : typeof this.config.useDimmer === 'boolean' ? this.config.useDimmer : true; - - if (this.config.useDimmer !== null || this.config.useDimmer !== undefined) { - delete this.config.useDimmer; - } - - this.config.options.useGroup = typeof this.config.options.useGroup === 'boolean' ? this.config.options.useGroup - : typeof this.config.useGroup === 'boolean' ? this.config.useGroup : true; - - if (this.config.useGroup !== null || this.config.useGroup !== undefined) { - delete this.config.useGroup; - } - - this.config.options.useLed = typeof this.config.options.useLed === 'boolean' ? this.config.options.useLed - : typeof this.config.useLed === 'boolean' ? this.config.useLed : true; - - if (this.config.useLed !== null || this.config.useLed !== undefined) { - delete this.config.useLed; - } - - this.config.systems = Array.isArray(this.config.systems) ? this.config.systems : systemsDefault; - - this.config.systems.forEach((system) => { - if (system === null || typeof system !== 'object') { - console.error(`Config: Unknown system definition: ${JSON.stringify(system)}`); - - return; - } - - if (typeof system.id !== 'number') { - console.error(`Config: System.id ${JSON.stringify(system.id)} not a number`); - } - - if (typeof system.label !== 'string' || !system.label.length) { - console.error(`Config: System ${system.id}, label not a string: ${JSON.stringify(system.label)}`); - } - - if (!Array.isArray(system.talkgroups)) { - system.talkgroups = []; - } - - if (!system.talkgroups.length) { - console.error(`Config: System ${system.id}, no talkgroups`); - } - - system.talkgroups.forEach((talkgroup) => { - if (talkgroup === null || typeof talkgroup !== 'object') { - console.error(`Config: System ${system.id}, unknown talkgroup definition: ${JSON.stringify(talkgroup)}`); - - return; - } - - if (typeof talkgroup.id !== 'number') { - console.error(`Config: System ${system.id}, talkgroup.id not a number: ${JSON.stringify(talkgroup.id)}`); - } - - if (this.config.useGroup && typeof talkgroup.group !== 'string' || !talkgroup.group.length) { - console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, group not a string: ${JSON.stringify(talkgroup.group)}`); - } - - if (typeof talkgroup.label !== 'string' || !talkgroup.label.length) { - console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, label not a string: ${JSON.stringify(talkgroup.label)}`); - } - - if (typeof talkgroup.name !== 'string' || !talkgroup.name.length) { - console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, name not a string: ${JSON.stringify(talkgroup.name)}`); - } - - if (talkgroup.patches !== undefined && !Array.isArray(talkgroup.patches)) { - console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, patches not an array: ${JSON.stringify(talkgroup.patches)}`); - } - - if (typeof talkgroup.tag !== 'string' || !talkgroup.tag.length) { - console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, tag not a string: ${JSON.stringify(talkgroup.tag)}`); - } - }); - - if (!Array.isArray(system.units)) { - system.units = []; - } - - system.units.forEach((unit) => { - if (unit === null || typeof unit !== 'object') { - console.error(`Config: System ${system.id}, unknown unit definition: ${JSON.stringify(unit)}`); - - return; - } - - if (typeof unit.id !== 'number') { - console.error(`Config: System ${system.id}, unit.id not a number: ${JSON.stringify(unit.id)}`); - } - - if (typeof unit.label !== 'string' || !unit.label.length) { - console.error(`Config: System ${system.id}, unit ${unit.id}, label not a string: ${JSON.stringify(unit.label)}`); - } - }); - }) + this.config = parseConfig(ctx.config); this.models = ctx.models; @@ -384,7 +254,7 @@ class Controller extends EventEmitter { getOptions() { return { - keyBeep: this.config.options.keyBeep, + keypadBeeps: this.config.options.keypadBeeps, useDimmer: this.config.options.useDimmer, useGroup: this.config.options.useGroup, useLed: this.config.options.useLed, @@ -481,7 +351,8 @@ class Controller extends EventEmitter { }, }); - return systems.map((system) => (system.talkgroups = system.talkgroups.filter((tg) => scope[system.id].includes(tg.id))) && system); + return systems.map((system) => (system.talkgroups = system.talkgroups + .filter((tg) => scope[system.id].includes(tg.id))) && system); } else { const systems = await this.models.system.findAll(); @@ -508,7 +379,8 @@ class Controller extends EventEmitter { }) : null; if (!system || !talkgroup) { - console.log(`NewCall: system=${call.system || 'unknown'} talkgroup=${call.talkgroup || 'unknown'} file=${call.audioName} No matching system/talkgroup`); + console.log(`NewCall: system=${call.system || 'unknown'} talkgroup=${call.talkgroup || 'unknown'} ` + + `file=${call.audioName} No matching system/talkgroup`); return; } @@ -702,4 +574,226 @@ class Controller extends EventEmitter { } } +function parseConfig(config) { + const ledColors = ['blue', 'cyan', 'green', 'magenta', 'red', 'white', 'yellow'] + + const oscillatorNodeType = ['sine', 'square', 'sawtooth', 'triangle']; + + config.access = config.access !== undefined ? config.access : null; + + config.apiKeys = config.apiKeys !== undefined ? config.apiKeys : uuid.v4(); + + if (config.options === null || typeof config.options !== 'object') { + config.options = {}; + } + + config.options.allowDownload = typeof config.options.allowDownload === 'boolean' ? config.options.allowDownload + : typeof config.allowDownload === 'boolean' ? config.allowDownload : true; + + if (config.allowDownload !== null || config.allowDownload !== undefined) { + delete config.allowDownload; + } + + config.options.disableAudioConversion = typeof config.options.disableAudioConversion === 'boolean' + ? config.options.disableAudioConversion : typeof config.disableAudioConversion === 'boolean' + ? config.disableAudioConversion : false; + + if (config.disableAudioConversion !== null || config.disableAudioConversion !== undefined) { + delete config.disableAudioConversion; + } + + config.options.keypadBeeps = config.options.keypadBeeps === false ? false : config.options.keyBeep === false + ? false : config.options.keypadBeeps !== null && typeof config.options.keypadBeeps === 'object' + ? config.options.keypadBeeps : {}; + + if (config.options.keyBeep !== undefined) { + delete config.options.keyBeep; + } + + if (typeof config.options.keypadBeeps === 'object') { + if (Array.isArray(config.options.keypadBeeps.activate)) { + config.options.keypadBeeps.activate.forEach((beep, index) => { + beep.begin = typeof beep.begin === 'number' ? beep.begin : index * 0.05; + beep.end = typeof beep.end === 'number' ? beep.end : index * 0.05 + 0.05; + beep.frequency = typeof beep.frequency === 'number' ? beep.frequency : 1200; + beep.type = oscillatorNodeType.includes(beep.type) ? beep.type : 'square'; + }); + + } else { + config.options.keypadBeeps.activate = [{ + begin: 0, + end: 0.05, + frequency: 1200, + type: 'square', + }]; + + } + + if (Array.isArray(config.options.keypadBeeps.deactivate)) { + config.options.keypadBeeps.deactivate.forEach((beep, index) => { + beep.begin = typeof beep.begin === 'number' ? beep.begin : index * 0.1; + beep.end = typeof beep.end === 'number' ? beep.end : index * 0.1 + 0.1; + beep.frequency = typeof beep.frequency === 'number' ? beep.frequency : 925; + beep.type = oscillatorNodeType.includes(beep.type) ? beep.type : 'square'; + }); + + } else { + config.options.keypadBeeps.deactivate = [{ + begin: 0, + end: 0.1, + frequency: 1200, + type: 'square', + }, { + begin: 0.1, + end: 0.2, + frequency: 925, + type: 'square', + }]; + } + + if (Array.isArray(config.options.keypadBeeps.denied)) { + config.options.keypadBeeps.denied.forEach((beep, index) => { + beep.begin = typeof beep.begin === 'number' ? beep.begin : index * 0.1; + beep.end = typeof beep.end === 'number' ? beep.end : index * 0.1 + 0.05; + beep.frequency = typeof beep.frequency === 'number' ? beep.frequency : 925; + beep.type = oscillatorNodeType.includes(beep.type) ? beep.type : 'square'; + }); + + } else { + config.options.keypadBeeps.denied = [{ + begin: 0, + end: 0.05, + frequency: 925, + type: 'square', + }, { + begin: 0.1, + end: 0.15, + frequency: 925, + type: 'square', + }]; + } + } + + config.options.pruneDays = typeof config.options.pruneDays === 'number' ? config.options.pruneDays + : typeof config.pruneDays === 'number' ? config.pruneDays : 7; + + if (config.pruneDays !== null || config.pruneDays !== undefined) { + delete config.pruneDays; + } + + config.options.useDimmer = typeof config.options.useDimmer === 'number' ? config.options.useDimmer + : typeof config.options.useDimmer === 'boolean' ? config.options.useDimmer + : typeof config.useDimmer === 'boolean' ? config.useDimmer : true; + + if (config.useDimmer !== null || config.useDimmer !== undefined) { + delete config.useDimmer; + } + + config.options.useGroup = typeof config.options.useGroup === 'boolean' ? config.options.useGroup + : typeof config.useGroup === 'boolean' ? config.useGroup : true; + + if (config.useGroup !== null || config.useGroup !== undefined) { + delete config.useGroup; + } + + config.options.useLed = typeof config.options.useLed === 'boolean' ? config.options.useLed + : typeof config.useLed === 'boolean' ? config.useLed : true; + + if (config.useLed !== null || config.useLed !== undefined) { + delete config.useLed; + } + + config.systems = Array.isArray(config.systems) ? config.systems : systemsDefault; + + config.systems.forEach((system) => { + if (system === null || typeof system !== 'object') { + console.error(`Config: Unknown system definition: ${JSON.stringify(system)}`); + + return; + } + + if (typeof system.id !== 'number') { + console.error(`Config: System.id ${JSON.stringify(system.id)} not a number`); + } + + if (typeof system.label !== 'string' || !system.label.length) { + console.error(`Config: System ${system.id}, label not a string: ${JSON.stringify(system.label)}`); + } + + if (typeof system.led !== 'undefined') { + system.led = ledColors.includes(system.led) ? system.led : 'green'; + } + + if (!Array.isArray(system.talkgroups)) { + system.talkgroups = []; + } + + if (!system.talkgroups.length) { + console.error(`Config: System ${system.id}, no talkgroups`); + } + + system.talkgroups.forEach((talkgroup) => { + if (talkgroup === null || typeof talkgroup !== 'object') { + console.error(`Config: System ${system.id}, unknown talkgroup definition: ${JSON.stringify(talkgroup)}`); + + return; + } + + if (typeof talkgroup.id !== 'number') { + console.error(`Config: System ${system.id}, talkgroup.id not a number: ${JSON.stringify(talkgroup.id)}`); + } + + if (config.useGroup && typeof talkgroup.group !== 'string' || !talkgroup.group.length) { + console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, ` + + `group not a string: ${JSON.stringify(talkgroup.group)}`); + } + + if (typeof talkgroup.label !== 'string' || !talkgroup.label.length) { + console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, ` + + `label not a string: ${JSON.stringify(talkgroup.label)}`); + } + + if (typeof talkgroup.led !== 'undefined') { + talkgroup.led = ledColors.includes(talkgroup.led) ? talkgroup.led : 'green'; + } + + if (typeof talkgroup.name !== 'string' || !talkgroup.name.length) { + console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, ` + + `name not a string: ${JSON.stringify(talkgroup.name)}`); + } + + if (talkgroup.patches !== undefined && !Array.isArray(talkgroup.patches)) { + console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, ` + + `patches not an array: ${JSON.stringify(talkgroup.patches)}`); + } + + if (typeof talkgroup.tag !== 'string' || !talkgroup.tag.length) { + console.error(`Config: System ${system.id}, talkgroup ${talkgroup.id}, tag not a string: ${JSON.stringify(talkgroup.tag)}`); + } + }); + + if (!Array.isArray(system.units)) { + system.units = []; + } + + system.units.forEach((unit) => { + if (unit === null || typeof unit !== 'object') { + console.error(`Config: System ${system.id}, unknown unit definition: ${JSON.stringify(unit)}`); + + return; + } + + if (typeof unit.id !== 'number') { + console.error(`Config: System ${system.id}, unit.id not a number: ${JSON.stringify(unit.id)}`); + } + + if (typeof unit.label !== 'string' || !unit.label.length) { + console.error(`Config: System ${system.id}, unit ${unit.id}, label not a string: ${JSON.stringify(unit.label)}`); + } + }); + }); + + return config; +} + module.exports = Controller; diff --git a/server/package-lock.json b/server/package-lock.json index 21c0a10..c3388ad 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,6 +1,6 @@ { "name": "rdio-scanner-server", - "version": "4.5.1", + "version": "4.5.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/server/package.json b/server/package.json index ebf9384..91ba00c 100644 --- a/server/package.json +++ b/server/package.json @@ -39,5 +39,5 @@ "build": "webpack", "start": "nodemon" }, - "version": "4.5.1" + "version": "4.5.2" }