import { Logging } from "homebridge"; import { inject, injectable } from "tsyringe"; import { HarmonyDataProvider } from "../dataProviders/harmonyDataProvider"; import { StateDataProvider } from "../dataProviders/stateDataProvider"; import { IActivityState } from "../models/activityState"; import { IActivity, IConfig, IDeviceSetupItem, IInput, IOutput, } from "../models/config"; import { HarmonyDevice } from "../models/harmonyDevice"; @injectable() export class ActivityService { constructor( @inject(HarmonyDataProvider) private _harmonyDataProvider: HarmonyDataProvider, @inject(StateDataProvider) private _stateDataProvider: StateDataProvider, @inject("IConfig") private _config: IConfig, @inject("log") private _log: Logging ) {} public startLastOrDefaultActivity = async ( controlUnitName: string, defaultActivity: IActivity ) => { const lastActivity = this.getLastActivity(controlUnitName); if (!lastActivity) { return this.startActivity(controlUnitName, defaultActivity); } return this.startActivity(controlUnitName, lastActivity); }; public startActivity = async ( controlUnitName: string, activity: IActivity ) => { this._log.info( `Starting activity ${activity.DisplayName} for controlUnit: ${controlUnitName}` ); let devicesToTurnOn: Array = this.getDevicesToTurnOn( activity, controlUnitName ); await this._harmonyDataProvider.powerOnDevices(devicesToTurnOn); await this.assignDeviceInput(activity); await this.routeInputsToOutputs(activity); let lastActivity = this.getCurrentActivity(controlUnitName); let devicesToTurnOff: Array = this.getDevicesToTurnOff( controlUnitName, lastActivity, activity ); await this._harmonyDataProvider.powerOffDevices(devicesToTurnOff); this._stateDataProvider.updateState(activity, controlUnitName); }; public stopCurrentActivity = async ( controlUnitName: string ): Promise => { if (!this.getCurrentActivity(controlUnitName)) { return; } let lastActivity: IActivity | undefined = this.getCurrentActivity(controlUnitName); let devicesToTurnOff: Array = this.getDevicesToTurnOff( controlUnitName, lastActivity ); await this._harmonyDataProvider.powerOffDevices(devicesToTurnOff); this._stateDataProvider.deactivateState(controlUnitName); }; public getCurrentActivity(controlUnitName: string): IActivity | undefined { return this._stateDataProvider.getState(controlUnitName)?.currentActivity; } public getLastActivity(controlUnitName: string): IActivity | undefined { return this._stateDataProvider.getState(controlUnitName)?.lastActivity; } /** * Return if a control unit is active * @param controlUnitName */ public getIsActive(controlUnitName: string): boolean { const state = this._stateDataProvider.getState(controlUnitName); return state != undefined && state.currentActivity != undefined; } /** * Helper function to make sure no control unit depends on device list. * @param devicesToTurnOn The list of devices to modify. * @param controlUnitName The name of the control unit in question. */ private sanitizeDeviceList( devicesToTurnOn: Array, controlUnitName: string ): Array { for (let controlUnitKey in this._stateDataProvider.states) { //Skip self if (controlUnitKey === controlUnitName) { continue; } let currentOtherState: IActivityState = this._stateDataProvider.states[controlUnitKey]!; if (!currentOtherState || !currentOtherState.currentActivity) { return devicesToTurnOn; } currentOtherState.currentActivity!.DeviceSetupList.forEach( (value: IDeviceSetupItem) => { //there are devices to remove if (devicesToTurnOn.some((e) => e && e.name === value.DeviceName)) { let deviceToRemove: HarmonyDevice = devicesToTurnOn.filter( (i) => i.name === value.DeviceName )[0]; delete devicesToTurnOn[devicesToTurnOn.indexOf(deviceToRemove)]; } } ); } return devicesToTurnOn; } private getDevicesToTurnOn( activity: IActivity, controlUnitName: string ): Array { let potentialDevices = this.buildPotentialDeviceList(activity); return this.sanitizeDeviceList(potentialDevices, controlUnitName); } private getDevicesToTurnOff( controlUnitName: string, lastActivity?: IActivity, nextActivity?: IActivity ) { let potentialDevices = lastActivity ? this.buildPotentialDeviceList(lastActivity) : []; //remove devices that will be used for next activity from list //delete array[index] is stupid because it just nulls out the index. But now i have to deal with nulls if (nextActivity) { potentialDevices.forEach((device: HarmonyDevice, index: number) => { if ( device && device.name && nextActivity.DeviceSetupList.some((e) => { return e && e.DeviceName === device.name; }) ) { delete potentialDevices[index]; } }); } return this.sanitizeDeviceList(potentialDevices, controlUnitName); } private buildPotentialDeviceList(activity: IActivity): HarmonyDevice[] { return activity.DeviceSetupList.map( (value: IDeviceSetupItem): HarmonyDevice => { return this._harmonyDataProvider.getDeviceByName(value.DeviceName); } ); } private async assignDeviceInput(activity: IActivity): Promise { await Promise.all( activity.DeviceSetupList.map(async (value: IDeviceSetupItem) => { let device: HarmonyDevice = this._harmonyDataProvider.getDeviceByName( value.DeviceName ); if (device && device.supportsCommand(`Input${value.Input}`)) { await this._harmonyDataProvider.sendCommand( `Input${value.Input}`, device ); } }) ); } private async routeInputsToOutputs(activity: IActivity) { if (activity.UseMatrix) { //get input and output let input: IInput = this._config.Matrix.Inputs.filter( (e) => e.InputDevice === activity.ControlDevice )[0]; let output: IOutput = this._config.Matrix.Outputs.filter( (e) => e.OutputDevice === activity.OutputDevice )[0]; let inputCommandName: string = `In ${input.InputNumber}`; let outputCommandName: string = `Out ${output.OutputLetter}`; let matrixDevice: HarmonyDevice = this._harmonyDataProvider.getDeviceByName( this._config.Matrix.DeviceName ); //Route hdmi if ( matrixDevice.supportsCommand(inputCommandName) && matrixDevice.supportsCommand(outputCommandName) ) { await this._harmonyDataProvider.sendCommand( outputCommandName, matrixDevice ); await this._harmonyDataProvider.sendCommand( inputCommandName, matrixDevice ); await this._harmonyDataProvider.sendCommand( outputCommandName, matrixDevice ); await this._harmonyDataProvider.sendCommand( inputCommandName, matrixDevice ); } } } }