Removing business logic from models. First pass at overrides feature
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Brandon Watson 2021-12-28 20:11:13 -05:00
parent c22a8a0325
commit e6e3d45b5b
9 changed files with 148 additions and 88 deletions

View File

@ -69,7 +69,7 @@ export class DeviceButton {
//Get device command if we don't have it //Get device command if we don't have it
if (!this._device) { if (!this._device) {
this._device = this._dataProvider.getDeviceFromName( this._device = this._dataProvider.getDeviceByName(
this._deviceInfo.DeviceName this._deviceInfo.DeviceName
); );
} }
@ -82,12 +82,18 @@ export class DeviceButton {
//change state if stateful //change state if stateful
if (this._deviceInfo.IsStateful && this._buttonState != newState) { if (this._deviceInfo.IsStateful && this._buttonState != newState) {
this._buttonState = newState; this._buttonState = newState;
await this._device.sendCommand(this._deviceInfo.ButtonName); await this._dataProvider.sendCommand(
this._deviceInfo.ButtonName,
this._device
);
return callback(); return callback();
} else if (!this._deviceInfo.IsStateful) { } else if (!this._deviceInfo.IsStateful) {
//Send the number of configured key presses //Send the number of configured key presses
for (let i = 0; i < this._deviceInfo.NumberOfKeyPresses; i++) { for (let i = 0; i < this._deviceInfo.NumberOfKeyPresses; i++) {
await this._device.sendCommand(this._deviceInfo.ButtonName); await this._dataProvider.sendCommand(
this._deviceInfo.ButtonName,
this._device
);
} }
this._switchService this._switchService

View File

@ -59,7 +59,7 @@ export class Sequence {
if (!deviceName) { if (!deviceName) {
continue; continue;
} }
const device = this._dataProvider.getDeviceFromName(deviceName); const device = this._dataProvider.getDeviceByName(deviceName);
if (device) { if (device) {
this._devices[deviceName] = device; this._devices[deviceName] = device;
} else { } else {
@ -87,7 +87,7 @@ export class Sequence {
step.DeviceCommand && step.DeviceCommand &&
device.supportsCommand(step.DeviceCommand) device.supportsCommand(step.DeviceCommand)
) { ) {
await device.sendCommand(step.DeviceCommand); await this._dataProvider.sendCommand(step.DeviceCommand, device);
} else { } else {
this._platform.log.warn( this._platform.log.warn(
`Attempted to execute command ${step.DeviceCommand} on device ${step.DeviceName} but the device or command was not found` `Attempted to execute command ${step.DeviceCommand} on device ${step.DeviceName} but the device or command was not found`

View File

@ -1,65 +1,83 @@
import { Logging } from "homebridge"; import { Logging } from "homebridge";
import { inject, injectable } from "tsyringe"; import { inject, injectable } from "tsyringe";
import { ICommand } from "../models";
import { IConfig } from "../models/config"; import { IConfig } from "../models/config";
import { IDeviceConfig } from "../models/config/deviceConfig"; import { IDeviceConfig } from "../models/config/deviceConfig";
import { IHub } from "../models/config/hub"; import { IHub } from "../models/config/hub";
import { HarmonyDevice } from "../models/harmonyDevice"; import { HarmonyDevice } from "../models/harmonyDevice";
import { HarmonyHub } from "../models/harmonyHub"; import { HarmonyHub } from "../models/harmonyHub";
import { sleep } from "../util";
@injectable() @injectable()
export class HarmonyDataProvider { export class HarmonyDataProvider {
private _hubs: { [hubName: string]: HarmonyHub } = {}; private _hubs: { [hubName: string]: HarmonyHub } = {};
private _hubsByDevice: { [deviceName: string]: string } = {}; private _deviceConfigByName: { [deviceName: string]: IDeviceConfig } = {};
constructor( constructor(
@inject("IConfig") private _config: IConfig, @inject("IConfig") private _config: IConfig,
@inject("log") private _log: Logging @inject("log") private _log: Logging
) { ) {
_config.Devices.forEach((deviceConfig: IDeviceConfig) => { _config.Devices.forEach((deviceConfig: IDeviceConfig) => {
this._hubsByDevice[deviceConfig.Name] = deviceConfig.Hub; this._deviceConfigByName[deviceConfig.Name] = deviceConfig;
}); });
// this._deviceConfigs = props.deviceConfigs;
this.connect(_config.Hubs); this.connect(_config.Hubs);
this.emitInfo(); this.emitInfo();
} }
public async turnOnDevices(devices: Array<HarmonyDevice>): Promise<void> { public async powerOnDevices(devices: Array<HarmonyDevice>): Promise<void> {
await Promise.all( await Promise.all(
devices.map(async (device: HarmonyDevice) => { devices.map(async (device: HarmonyDevice) => {
if (device && device.name) { if (device && device.name) {
if (!device.on) { if (!device.on) {
this._log.info(`Turning on device ${device.name}`); this._log.info(`Turning on device ${device.name}`);
await device.powerOn(); await this.powerOnDevice(device);
} }
} }
}) })
); );
} }
public async turnOffDevices(devices: Array<HarmonyDevice>) { public async powerOffDevices(devices: Array<HarmonyDevice>) {
await Promise.all( await Promise.all(
//Turn off devices //Turn off devices
devices.map(async (device: HarmonyDevice) => { devices.map(async (device: HarmonyDevice) => {
if (device) { if (device) {
if (device.on) { if (device.on) {
this._log.info(`Turning off device ${device.name}`); this._log.info(`Turning off device ${device.name}`);
await device.powerOff(); await this.powerOffDevice(device);
} }
} }
}) })
); );
} }
public async sendCommand(
commandName: string,
harmonyDevice: HarmonyDevice
): Promise<void> {
let command!: ICommand;
commandName = this.getOverrideCommand(commandName, harmonyDevice);
if (harmonyDevice.supportsCommand(commandName)) {
command = harmonyDevice.getCommand(commandName);
}
const hub = this.getHubByDevice(harmonyDevice);
await hub.sendCommand(command);
}
/** /**
* Get the IDevice by name. * Get the IDevice by name.
* @param deviceName The device to retrieve. * @param deviceName The device to retrieve.
*/ */
public getDeviceFromName(deviceName: string): HarmonyDevice { public getDeviceByName(deviceName: string): HarmonyDevice {
let device: HarmonyDevice | undefined; let device: HarmonyDevice | undefined;
try { try {
device = device =
this._hubs[this._hubsByDevice[deviceName]].getDeviceByName(deviceName); this._hubs[this._deviceConfigByName[deviceName].Hub].getDeviceByName(
deviceName
);
} catch (err) { } catch (err) {
this._log.info(`Error retrieving device from hub: ${err}`); this._log.info(`Error retrieving device from hub: ${err}`);
} }
@ -101,4 +119,49 @@ export class HarmonyDataProvider {
}); });
} }
} }
private getHubByDevice(device: HarmonyDevice) {
return this._hubs[this._deviceConfigByName[device.name].Hub];
}
private async powerOnDevice(harmonyDevice: HarmonyDevice): Promise<void> {
let powerOnCommand: string = "Power On";
let powerToggleCommand: string = "Power Toggle";
if (harmonyDevice.supportsCommand(powerOnCommand)) {
await this.sendCommand(powerOnCommand, harmonyDevice);
harmonyDevice.on = true;
} else if (harmonyDevice.supportsCommand(powerToggleCommand)) {
await this.sendCommand(powerToggleCommand, harmonyDevice);
harmonyDevice.on = true;
} else {
await this.sendCommand(powerOnCommand, harmonyDevice);
}
}
private async powerOffDevice(harmonyDevice: HarmonyDevice): Promise<void> {
let powerOffCommand: string = "Power Off";
let powerToggleCommand: string = "Power Toggle";
if (harmonyDevice.supportsCommand(powerOffCommand)) {
await this.sendCommand(powerOffCommand, harmonyDevice);
harmonyDevice.on = false;
} else if (harmonyDevice.supportsCommand(powerToggleCommand)) {
await this.sendCommand(powerToggleCommand, harmonyDevice);
harmonyDevice.on = false;
}
}
private getOverrideCommand(
commandName: string,
harmonyDevice: HarmonyDevice
) {
const deviceConfig: IDeviceConfig =
this._deviceConfigByName[harmonyDevice.name];
if (!deviceConfig.Overrides) {
return commandName;
}
const overrideCommand = deviceConfig.Overrides.find(
(e) => e.Command == commandName
);
return overrideCommand ? overrideCommand.Override : commandName;
}
} }

View File

@ -1,4 +1,5 @@
export interface IDeviceConfig { export interface IDeviceConfig {
Name: string; Name: string;
Hub: string; Hub: string;
} Overrides: Array<{ Command: string; Override: string }>;
}

View File

@ -10,15 +10,12 @@ export interface IHarmonyDeviceProps {
} }
export class HarmonyDevice { export class HarmonyDevice {
private _harmony: any;
private _log: any;
private _commands: { [name: string]: ICommand } = {}; private _commands: { [name: string]: ICommand } = {};
private _on: boolean; private _on: boolean;
constructor(props: IHarmonyDeviceProps) { constructor(props: IHarmonyDeviceProps) {
this.id = props.id; this.id = props.id;
this.name = props.name; this.name = props.name;
this._harmony = props.harmony;
this._on = false; this._on = false;
this._commands = props.commands; this._commands = props.commands;
} }
@ -30,6 +27,10 @@ export class HarmonyDevice {
return this._on; return this._on;
} }
public set on(value: boolean) {
this._on = value;
}
public get commands(): { [name: string]: ICommand } { public get commands(): { [name: string]: ICommand } {
return this._commands; return this._commands;
} }
@ -43,51 +44,4 @@ export class HarmonyDevice {
public getCommand(commandName: string): ICommand { public getCommand(commandName: string): ICommand {
return this._commands[commandName]; return this._commands[commandName];
} }
public async powerOn(): Promise<void> {
let powerOnCommand: string = "Power On";
let powerToggleCommand: string = "Power Toggle";
if (this.supportsCommand(powerOnCommand)) {
await this.sendCommand(powerOnCommand);
this._on = true;
} else if (this.supportsCommand(powerToggleCommand)) {
await this.sendCommand(powerToggleCommand);
this._on = true;
}
}
public async powerOff(): Promise<void> {
let powerOffCommand: string = "Power Off";
let powerToggleCommand: string = "Power Toggle";
if (this.supportsCommand(powerOffCommand)) {
await this.sendCommand(powerOffCommand);
this._on = false;
} else if (this.supportsCommand(powerToggleCommand)) {
await this.sendCommand(powerToggleCommand);
this._on = false;
}
}
public async sendCommand(commandName: string): Promise<void> {
let command!: ICommand;
if (this.supportsCommand(commandName)) {
command = this.getCommand(commandName);
}
try {
//Execute command
//HACK to fix Harmon Kardon receiver not turning off
if (command.command === "PowerOff") {
for (let i = 0; i < 2; i++) {
await this._harmony.sendCommand(JSON.stringify(command));
}
}
await this._harmony.sendCommand(JSON.stringify(command));
//Sleep
await sleep(800);
} catch (err) {
this._log(`ERROR - error sending command to harmony: ${err}`);
}
}
} }

View File

@ -2,6 +2,7 @@ import { HarmonyDevice } from "./harmonyDevice";
const Harmony = require("harmony-websocket"); const Harmony = require("harmony-websocket");
import { ICommand } from "./device"; import { ICommand } from "./device";
import { EventEmitter } from "events"; import { EventEmitter } from "events";
import { sleep } from "../util";
export class HarmonyHub extends EventEmitter { export class HarmonyHub extends EventEmitter {
private _devices: { [deviceName: string]: HarmonyDevice } = {}; private _devices: { [deviceName: string]: HarmonyDevice } = {};
@ -72,4 +73,22 @@ export class HarmonyHub extends EventEmitter {
private connect = async (): Promise<void> => { private connect = async (): Promise<void> => {
await this._harmony.Connect(this._ip); await this._harmony.Connect(this._ip);
}; };
public async sendCommand(command: ICommand): Promise<void> {
try {
//Execute command
//HACK to fix Harmon Kardon receiver not turning off
if (command.command === "PowerOff") {
for (let i = 0; i < 2; i++) {
await this._harmony.sendCommand(JSON.stringify(command));
}
}
await this._harmony.sendCommand(JSON.stringify(command));
//Sleep
await sleep(800);
} catch (err) {
this._log(`ERROR - error sending command to harmony: ${err}`);
}
}
} }

View File

@ -34,7 +34,7 @@ export class ActivityService {
controlUnitName controlUnitName
); );
await this._harmonyDataProvider.turnOnDevices(devicesToTurnOn); await this._harmonyDataProvider.powerOnDevices(devicesToTurnOn);
await this.assignDeviceInput(activity); await this.assignDeviceInput(activity);
@ -47,7 +47,7 @@ export class ActivityService {
lastActivity, lastActivity,
activity activity
); );
await this._harmonyDataProvider.turnOffDevices(devicesToTurnOff); await this._harmonyDataProvider.powerOffDevices(devicesToTurnOff);
this._stateDataProvider.updateState(activity, controlUnitName); this._stateDataProvider.updateState(activity, controlUnitName);
}; };
@ -66,7 +66,7 @@ export class ActivityService {
controlUnitName, controlUnitName,
lastActivity lastActivity
); );
await this._harmonyDataProvider.turnOffDevices(devicesToTurnOff); await this._harmonyDataProvider.powerOffDevices(devicesToTurnOff);
this._stateDataProvider.removeState(controlUnitName); this._stateDataProvider.removeState(controlUnitName);
}; };
@ -157,7 +157,7 @@ export class ActivityService {
private buildPotentialDeviceList(activity: IActivity): HarmonyDevice[] { private buildPotentialDeviceList(activity: IActivity): HarmonyDevice[] {
return activity.DeviceSetupList.map( return activity.DeviceSetupList.map(
(value: IDeviceSetupItem): HarmonyDevice => { (value: IDeviceSetupItem): HarmonyDevice => {
return this._harmonyDataProvider.getDeviceFromName(value.DeviceName); return this._harmonyDataProvider.getDeviceByName(value.DeviceName);
} }
); );
} }
@ -165,12 +165,15 @@ export class ActivityService {
private async assignDeviceInput(activity: IActivity): Promise<void> { private async assignDeviceInput(activity: IActivity): Promise<void> {
await Promise.all( await Promise.all(
activity.DeviceSetupList.map(async (value: IDeviceSetupItem) => { activity.DeviceSetupList.map(async (value: IDeviceSetupItem) => {
let device: HarmonyDevice = this._harmonyDataProvider.getDeviceFromName( let device: HarmonyDevice = this._harmonyDataProvider.getDeviceByName(
value.DeviceName value.DeviceName
); );
if (device && device.supportsCommand(`Input${value.Input}`)) { if (device && device.supportsCommand(`Input${value.Input}`)) {
await device.sendCommand(`Input${value.Input}`); await this._harmonyDataProvider.sendCommand(
`Input${value.Input}`,
device
);
} }
}) })
); );
@ -190,7 +193,7 @@ export class ActivityService {
let outputCommandName: string = `Out ${output.OutputLetter}`; let outputCommandName: string = `Out ${output.OutputLetter}`;
let matrixDevice: HarmonyDevice = let matrixDevice: HarmonyDevice =
this._harmonyDataProvider.getDeviceFromName( this._harmonyDataProvider.getDeviceByName(
this._config.Matrix.DeviceName this._config.Matrix.DeviceName
); );
@ -199,10 +202,22 @@ export class ActivityService {
matrixDevice.supportsCommand(inputCommandName) && matrixDevice.supportsCommand(inputCommandName) &&
matrixDevice.supportsCommand(outputCommandName) matrixDevice.supportsCommand(outputCommandName)
) { ) {
await matrixDevice.sendCommand(outputCommandName); await this._harmonyDataProvider.sendCommand(
await matrixDevice.sendCommand(inputCommandName); outputCommandName,
await matrixDevice.sendCommand(outputCommandName); matrixDevice
await matrixDevice.sendCommand(inputCommandName); );
await this._harmonyDataProvider.sendCommand(
inputCommandName,
matrixDevice
);
await this._harmonyDataProvider.sendCommand(
outputCommandName,
matrixDevice
);
await this._harmonyDataProvider.sendCommand(
inputCommandName,
matrixDevice
);
} }
} }
} }

View File

@ -23,7 +23,7 @@ export class CommandService {
if (currentActivity) { if (currentActivity) {
let commandName: string = ""; let commandName: string = "";
let device: HarmonyDevice = this._harmonyDataProvider.getDeviceFromName( let device: HarmonyDevice = this._harmonyDataProvider.getDeviceByName(
currentActivity.ControlDevice currentActivity.ControlDevice
); );
switch (key) { switch (key) {
@ -65,7 +65,7 @@ export class CommandService {
} }
} }
await device.sendCommand(commandName); await this._harmonyDataProvider.sendCommand(commandName, device);
} }
}; };
} }

View File

@ -19,10 +19,11 @@ export class VolumeService {
let currentActivity = this._stateDataProvider.getState(controlUnitName); let currentActivity = this._stateDataProvider.getState(controlUnitName);
if (currentActivity) { if (currentActivity) {
let volumeDevice: HarmonyDevice = let volumeDevice: HarmonyDevice =
this._harmonyDataProvider.getDeviceFromName( this._harmonyDataProvider.getDeviceByName(currentActivity.VolumeDevice);
currentActivity.VolumeDevice await this._harmonyDataProvider.sendCommand(
); volumeUpCommand,
await volumeDevice.sendCommand(volumeUpCommand); volumeDevice
);
} }
}; };
@ -34,10 +35,11 @@ export class VolumeService {
let currentActivity = this._stateDataProvider.getState(controlUnitName); let currentActivity = this._stateDataProvider.getState(controlUnitName);
if (currentActivity) { if (currentActivity) {
let volumeDevice: HarmonyDevice = let volumeDevice: HarmonyDevice =
this._harmonyDataProvider.getDeviceFromName( this._harmonyDataProvider.getDeviceByName(currentActivity.VolumeDevice);
currentActivity.VolumeDevice await this._harmonyDataProvider.sendCommand(
); volumeDownCommand,
await volumeDevice.sendCommand(volumeDownCommand); volumeDevice
);
} }
}; };
} }