Created sequence accessory
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		
							
								
								
									
										101
									
								
								src/Accessories/Sequence.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/Accessories/Sequence.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| import { | ||||
|   CharacteristicGetCallback, | ||||
|   CharacteristicSetCallback, | ||||
|   PlatformAccessory, | ||||
|   Service, | ||||
| } from "homebridge"; | ||||
| import HarmonyDataProvider from "../DataProviders/HarmonyDataProvider"; | ||||
| import { ISequence } from "../Models/Config/ISequence"; | ||||
| import { HarmonyDevice } from "../Models/HarmonyDevice"; | ||||
| import { Platform } from "../platform"; | ||||
| import { sleep } from "../Util"; | ||||
|  | ||||
| export class Sequence { | ||||
|   private _devices: { [deviceName: string]: HarmonyDevice }; | ||||
|   private _switchService: Service; | ||||
|  | ||||
|   constructor( | ||||
|     private readonly _platform: Platform, | ||||
|     private readonly _accessory: PlatformAccessory, | ||||
|     private _dataProvider: HarmonyDataProvider, | ||||
|     private _sequence: ISequence | ||||
|   ) { | ||||
|     this._accessory | ||||
|       .getService(this._platform.Service.AccessoryInformation)! | ||||
|       .setCharacteristic( | ||||
|         this._platform.Characteristic.Manufacturer, | ||||
|         "Brandon Watson" | ||||
|       ) | ||||
|       .setCharacteristic(this._platform.Characteristic.Model, "Sequence Button") | ||||
|       .setCharacteristic( | ||||
|         this._platform.Characteristic.SerialNumber, | ||||
|         "123-456-789" | ||||
|       ); | ||||
|  | ||||
|     const switchUUID = this._platform.api.hap.uuid.generate( | ||||
|       `${this._accessory.displayName} Switch` | ||||
|     ); | ||||
|  | ||||
|     this._switchService = | ||||
|       this._accessory.getService(this._platform.Service.Switch) || | ||||
|       this._accessory.addService( | ||||
|         this._platform.Service.Switch, | ||||
|         this._accessory.displayName, | ||||
|         switchUUID | ||||
|       ); | ||||
|  | ||||
|     this._switchService | ||||
|       .getCharacteristic(this._platform.Characteristic.On) | ||||
|       .on("set", this.onSwitchSet) | ||||
|       .updateValue(false) | ||||
|       .on("get", (callback: CharacteristicGetCallback): void => { | ||||
|         return callback(null); | ||||
|       }); | ||||
|  | ||||
|     this._devices = {}; | ||||
|     // Get devices in sequence | ||||
|     for (const deviceName of _sequence.Steps.map((e) => e.DeviceName)) { | ||||
|       if (!deviceName) { | ||||
|         continue; | ||||
|       } | ||||
|       const device = this._dataProvider.getDeviceFromName(deviceName); | ||||
|       if (device) { | ||||
|         this._devices[deviceName] = device; | ||||
|       } else { | ||||
|         this._platform.log.warn( | ||||
|           `Device ${deviceName} was not found in harmony configuration` | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Handler for switchSet command | ||||
|    * @param callback | ||||
|    */ | ||||
|   public async onSwitchSet(callback: CharacteristicSetCallback): Promise<void> { | ||||
|     // Execute sequence | ||||
|     for (const step of this._sequence.Steps) { | ||||
|       await sleep(step.Delay); | ||||
|       const device: HarmonyDevice = this._devices[step.DeviceName ?? ""]; | ||||
|       if ( | ||||
|         device && | ||||
|         step.DeviceCommand && | ||||
|         device.supportsCommand(step.DeviceCommand) | ||||
|       ) { | ||||
|         await device.sendCommand(step.DeviceCommand); | ||||
|       } else { | ||||
|         this._platform.log.warn( | ||||
|           `Attempted to execute command ${step.DeviceCommand} on device ${step.DeviceName} but the device or command was not found` | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Deactivate button | ||||
|     this._switchService | ||||
|       .getCharacteristic(this._platform.Characteristic.On) | ||||
|       .updateValue(false); | ||||
|  | ||||
|     callback(null); | ||||
|   } | ||||
| } | ||||
| @@ -1,10 +1,10 @@ | ||||
| export interface ISequence { | ||||
|   name: string; | ||||
|   steps: Array<IStep>; | ||||
|   DisplayName: string; | ||||
|   Steps: Array<IStep>; | ||||
| } | ||||
|  | ||||
| export interface IStep { | ||||
|   deviceName: string; | ||||
|   deviceCommand: string; | ||||
|   delay: number; | ||||
|   DeviceName?: string; | ||||
|   DeviceCommand?: string; | ||||
|   Delay: number; | ||||
| } | ||||
|   | ||||
| @@ -8,8 +8,10 @@ import { | ||||
|   Service, | ||||
| } from "homebridge"; | ||||
| import { ControlUnit, DeviceButton } from "./Accessories"; | ||||
| import { Sequence } from "./Accessories/Sequence"; | ||||
| import HarmonyDataProvider from "./DataProviders/HarmonyDataProvider"; | ||||
| import { IConfig, IControlUnit, IDeviceButton } from "./Models/Config"; | ||||
| import { ISequence } from "./Models/Config/ISequence"; | ||||
| import { HarmonyDevice } from "./Models/HarmonyDevice"; | ||||
| import { HarmonyHub } from "./Models/HarmonyHub"; | ||||
| import { PLATFORM_NAME, PLUGIN_NAME } from "./settings"; | ||||
| @@ -33,7 +35,6 @@ export class Platform implements DynamicPlatformPlugin { | ||||
|  | ||||
|     this.api.on("didFinishLaunching", async () => { | ||||
|       log.debug("Executed didFinishLaunching callback"); | ||||
|       this.discoverDevices(dataProvider); | ||||
|     }); | ||||
|  | ||||
|     this.dataProvider = null; | ||||
| @@ -51,6 +52,9 @@ export class Platform implements DynamicPlatformPlugin { | ||||
|  | ||||
|       this.dataProvider.on("Ready", () => { | ||||
|         this.log.info("All hubs connected"); | ||||
|         this.discoverControlUnitAccessories(dataProvider); | ||||
|         this.discoverDeviceButtonAccessories(dataProvider); | ||||
|         this.discoverSequenceAccessories(dataProvider); | ||||
|         if (this.config.EmitDevicesOnStartup) { | ||||
|           const hubs = this.dataProvider!.hubs; | ||||
|           Object.values(hubs).forEach((hub: HarmonyHub) => { | ||||
| @@ -78,10 +82,17 @@ export class Platform implements DynamicPlatformPlugin { | ||||
|   public config: IConfig; | ||||
|   public dataProvider: HarmonyDataProvider | null; | ||||
|  | ||||
|   public discoverDevices(dataProvider: HarmonyDataProvider) { | ||||
|   /** | ||||
|    * Discover new control unit accessories | ||||
|    * @param dataProvider | ||||
|    */ | ||||
|   private discoverControlUnitAccessories( | ||||
|     dataProvider: HarmonyDataProvider | ||||
|   ): void { | ||||
|     this.config.ControlUnits.forEach((unit: IControlUnit) => { | ||||
|       const uuid = this.api.hap.uuid.generate(unit.DisplayName); | ||||
|       const existingAccessory = this.accessories.find((e) => e.UUID === uuid); | ||||
|  | ||||
|       if (existingAccessory) { | ||||
|         this.log.info( | ||||
|           "Restoring existing accessory from cache: " + | ||||
| @@ -106,7 +117,15 @@ export class Platform implements DynamicPlatformPlugin { | ||||
|         console.log("Publishing external accessory: " + uuid); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Discover new device button accessories | ||||
|    * @param dataProvider | ||||
|    */ | ||||
|   private discoverDeviceButtonAccessories( | ||||
|     dataProvider: HarmonyDataProvider | ||||
|   ): void { | ||||
|     this.config.DeviceButtons.forEach((button: IDeviceButton) => { | ||||
|       const uuid = this.api.hap.uuid.generate(button.DisplayName); | ||||
|       const existingAccessory = this.accessories.find((e) => e.UUID === uuid); | ||||
| @@ -133,6 +152,65 @@ export class Platform implements DynamicPlatformPlugin { | ||||
|         ]); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // Remove old device buttons | ||||
|     for (const accessory of this.accessories) { | ||||
|       if ( | ||||
|         this.config.DeviceButtons.filter( | ||||
|           (button) => button.DisplayName === accessory.displayName | ||||
|         ).length === 0 | ||||
|       ) { | ||||
|         this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ | ||||
|           accessory, | ||||
|         ]); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Discover new sequence accessories | ||||
|    * @param dataProvider | ||||
|    */ | ||||
|   public discoverSequenceAccessories(dataProvider: HarmonyDataProvider): void { | ||||
|     this.config.Sequences.forEach((sequence: ISequence) => { | ||||
|       const uuid = this.api.hap.uuid.generate(sequence.DisplayName); | ||||
|       const existingAccessory = this.accessories.find((e) => e.UUID === uuid); | ||||
|       if (existingAccessory) { | ||||
|         this.log.info( | ||||
|           "Restoring existing accessory from cache: " + | ||||
|             existingAccessory.displayName | ||||
|         ); | ||||
|  | ||||
|         new Sequence(this, existingAccessory, dataProvider, sequence); | ||||
|         this.api.updatePlatformAccessories([existingAccessory]); | ||||
|       } else { | ||||
|         this.log.info("Adding new accessory: " + sequence.DisplayName); | ||||
|         const accessory = new this.api.platformAccessory( | ||||
|           sequence.DisplayName, | ||||
|           uuid | ||||
|         ); | ||||
|         accessory.context["DeviceName"] = sequence.DisplayName; | ||||
|  | ||||
|         new Sequence(this, accessory, dataProvider, sequence); | ||||
|  | ||||
|         this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ | ||||
|           accessory, | ||||
|         ]); | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     // Remove old device buttons | ||||
|     for (const accessory of this.accessories) { | ||||
|       if ( | ||||
|         this.config.Sequences.filter( | ||||
|           (sequence) => sequence.DisplayName === accessory.displayName | ||||
|         ).length === 0 | ||||
|       ) { | ||||
|         this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ | ||||
|           accessory, | ||||
|         ]); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   configureAccessory(accessory: PlatformAccessory<Record<string, any>>): void { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user