Compare commits
	
		
			4 Commits
		
	
	
		
			1.2.2
			...
			feature/ne
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ed0b418b4d | ||
|  | 2475aea1c8 | ||
|  | 31c5c5b9e2 | ||
|  | a539287b9e | 
							
								
								
									
										1
									
								
								.bash_profile
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.bash_profile
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| source /nvm.sh | ||||
							
								
								
									
										80
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -1,80 +0,0 @@ | ||||
| kind: pipeline | ||||
| type: docker | ||||
| name: default | ||||
|  | ||||
| clone: | ||||
|   disable: true | ||||
|  | ||||
| steps: | ||||
|   - name: clone | ||||
|     image: alpine/git | ||||
|     commands: | ||||
|       - git clone https://gitea.watsonlabs.net/watsonb8/homebridge-harmony-control.git . | ||||
|       - git checkout $DRONE_COMMIT | ||||
|  | ||||
|   - name: build | ||||
|     image: node | ||||
|     commands: | ||||
|       - npm install | ||||
|       - npm run build | ||||
|  | ||||
|   - name: version | ||||
|     image: node | ||||
|     commands: | ||||
|       - export version=`node -p "require('./package.json').version"` | ||||
|       - export commit=`echo $DRONE_COMMIT | cut -c1-5` | ||||
|       - npm version prerelease --preid=$commit --git-tag-version=false --allow-same-version=true | ||||
|     when: | ||||
|       event: | ||||
|         exclude: | ||||
|           - tag | ||||
|           - pull_request | ||||
|       branch: | ||||
|         include: | ||||
|           - master | ||||
|  | ||||
|   - name: publish pre | ||||
|     image: plugins/npm | ||||
|     settings: | ||||
|       username: | ||||
|         from_secret: npm_username | ||||
|       password: | ||||
|         from_secret: npm_password | ||||
|       email: brandon@watsonlabs.net | ||||
|       registry: "http://10.44.1.6:4873/" | ||||
|     when: | ||||
|       event: | ||||
|         exclude: | ||||
|           - tag | ||||
|           - pull_request | ||||
|       branch: | ||||
|         include: | ||||
|           - master | ||||
|  | ||||
|   - name: publish tagged version | ||||
|     image: plugins/npm | ||||
|     settings: | ||||
|       username: | ||||
|         from_secret: npm_username | ||||
|       password: | ||||
|         from_secret: npm_password | ||||
|       email: brandon@watsonlabs.net | ||||
|       registry: "http://10.44.1.6:4873/" | ||||
|     when: | ||||
|       event: | ||||
|         - tag | ||||
|       exclude: | ||||
|         - pull_request | ||||
|  | ||||
|   - name: Notify | ||||
|     image: drillster/drone-email | ||||
|     settings: | ||||
|       host: 10.44.1.13 | ||||
|       username: srvGitea | ||||
|       password: | ||||
|         from_secret: smtp_password | ||||
|       from: drone@watsonlabs.net | ||||
|       skip_verify: true | ||||
|     when: | ||||
|       status: | ||||
|         - failure | ||||
							
								
								
									
										33
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,19 +1,16 @@ | ||||
| { | ||||
|   // Use IntelliSense to learn about possible attributes. | ||||
|   // Hover to view descriptions of existing attributes. | ||||
|   // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||||
|   "version": "0.2.0", | ||||
|   "configurations": [ | ||||
|     { | ||||
|       "type": "node", | ||||
|       "request": "launch", | ||||
|       "name": "Launch Program", | ||||
|       "preLaunchTask": "build", | ||||
|       "program": "/Users/brandonwatson/.nvm/versions/node/v14.15.0/lib/node_modules/homebridge/bin/homebridge", | ||||
|       "env": { | ||||
|         "HOMEBRIDGE_OPTS": "/Users/brandonwatson/.homebridge" | ||||
|       }, | ||||
|       "sourceMaps": true | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|     // Use IntelliSense to learn about possible attributes. | ||||
|     // Hover to view descriptions of existing attributes. | ||||
|     // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||||
|     "version": "0.2.0", | ||||
|     "configurations": [ | ||||
|         { | ||||
|             "type": "node", | ||||
|             "request": "launch", | ||||
|             "name": "Launch Program", | ||||
|             "preLaunchTask": "build and install", | ||||
|             "program": "/Users/brandonwatson/.npm-global/bin/homebridge", | ||||
|             "sourceMaps": true, | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										33
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								.vscode/tasks.json
									
									
									
									
										vendored
									
									
								
							| @@ -1,18 +1,17 @@ | ||||
| { | ||||
|   // See https://go.microsoft.com/fwlink/?LinkId=733558 | ||||
|   // for the documentation about the tasks.json format | ||||
|   "version": "2.0.0", | ||||
|   "tasks": [ | ||||
|     { | ||||
|       "type": "npm", | ||||
|       "script": "build", | ||||
|       "label": "build", | ||||
|       "problemMatcher": [] | ||||
|     }, | ||||
|     { | ||||
|       "type": "shell", | ||||
|       "label": "build and install", | ||||
|       "command": "npm run build&&sudo npm install -g --unsafe-perm ." | ||||
|     } | ||||
|   ] | ||||
| } | ||||
|     // See https://go.microsoft.com/fwlink/?LinkId=733558  | ||||
|     // for the documentation about the tasks.json format | ||||
|     "version": "2.0.0", | ||||
|     "tasks": [ | ||||
|         { | ||||
|             "type": "npm", | ||||
|             "script": "build", | ||||
|             "problemMatcher": [] | ||||
|         }, | ||||
|         { | ||||
|             "type": "shell", | ||||
|             "label": "build and install", | ||||
|             "command": "npm run build&&sudo npm install -g --unsafe-perm ." | ||||
|         } | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										178
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										178
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,178 +0,0 @@ | ||||
| # homebridge-harmony-control | ||||
|  | ||||
| A convenient way to control your home through homekit utilizing one or many Logitech Harmony remotes. | ||||
|  | ||||
| ## Description | ||||
|  | ||||
| This plugin not only allows for control of your one or many entertainment consoles, but it also supports use of a more complicated matrix switch if applicable. In addition, this plugin can expose any entertainment devices directly to homekit via stateless or stateful switch. | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| 1. Clone the repository by running `git clone ssh://git@thebword.ddns.net:3122/watsonb8/homebridge-harmony-control.git` | ||||
| 2. Run `npm install` to install required modules | ||||
| 3. Run `npm run build` to build the module | ||||
| 4. Run `npm link` or install globally to link this instance to your global homebridge instance | ||||
|  | ||||
| ## Configuration | ||||
|  | ||||
| ``` | ||||
| { | ||||
|       "platform": "HarmonyHubMatrix", | ||||
|       "EmitDevicesOnStartup": false, | ||||
|       "Hubs": [ | ||||
|         { | ||||
|           "Name": "LivingRoomHub", | ||||
|           "Ip": "exampleOne.com" | ||||
|         }, | ||||
|         { | ||||
|           "Name": "RackHub", | ||||
|           "Ip": "exampleTwo.com" | ||||
|         } | ||||
|       ], | ||||
|       "Devices": [ | ||||
|         { | ||||
|           "Name": "LG TV", | ||||
|           "Hub": "LivingRoomHub" | ||||
|         }, | ||||
|         { | ||||
|           "Name": "JVC AV Receiver", | ||||
|           "Hub": "LivingRoomHub" | ||||
|         }, | ||||
|         { | ||||
|           "Name": "Microsoft Xbox One", | ||||
|           "Hub": "RackHub" | ||||
|         }, | ||||
|         { | ||||
|           "Name": "Apple TV Gen 4", | ||||
|           "Hub": "RackHub" | ||||
|         }, | ||||
|         { | ||||
|           "Name": "Gefen AV Switch", | ||||
|           "Hub": "RackHub" | ||||
|         } | ||||
|       ], | ||||
|       "Matrix": { | ||||
|         "DeviceName": "Gefen AV Switch", | ||||
|         "Inputs": [ | ||||
|           { | ||||
|             "InputNumber": "1", | ||||
|             "InputDevice": "Microsoft Xbox One" | ||||
|           }, | ||||
|           { | ||||
|             "InputNumber": "5", | ||||
|             "InputDevice": "Apple TV Gen 4" | ||||
|           }, | ||||
|         ], | ||||
|         "Outputs": [ | ||||
|           { | ||||
|             "OutputLetter": "B", | ||||
|             "OutputDevice": "LG TV" | ||||
|           }, | ||||
|         ] | ||||
|       }, | ||||
|       "ControlUnits": [ | ||||
|         { | ||||
|           "DisplayName": "Living Room", | ||||
|           "Activities": [ | ||||
|             { | ||||
|               "DisplayName": "Xbox One", | ||||
|               "DeviceSetupList": [ | ||||
|                 { | ||||
|                   "DeviceName": "Microsoft Xbox One", | ||||
|                   "Input": [] | ||||
|                 }, | ||||
|                 { | ||||
|                   "DeviceName": "LG TV", | ||||
|                   "Input": "Hdmi1" | ||||
|                 }, | ||||
|                 { | ||||
|                   "DeviceName": "JVC AV Receiver", | ||||
|                   "Input": "Dvd" | ||||
|                 } | ||||
|               ], | ||||
|               "VolumeDevice": "JVC AV Receiver", | ||||
|               "ControlDevice": "Microsoft Xbox One", | ||||
|               "OutputDevice": "LG TV", | ||||
|               "UseMatrix": true | ||||
|             }, | ||||
|             { | ||||
|               "DisplayName": "Apple TV", | ||||
|               "DeviceSetupList": [ | ||||
|                 { | ||||
|                   "DeviceName": "Apple TV Gen 4", | ||||
|                   "Input": [] | ||||
|                 }, | ||||
|                 { | ||||
|                   "DeviceName": "LG TV", | ||||
|                   "Input": "Hdmi1" | ||||
|                 }, | ||||
|                 { | ||||
|                   "DeviceName": "JVC AV Receiver", | ||||
|                   "Input": "Dvd" | ||||
|                 } | ||||
|               ], | ||||
|               "VolumeDevice": "JVC AV Receiver", | ||||
|               "ControlDevice": "Apple TV Gen 4", | ||||
|               "OutputDevice": "LG TV", | ||||
|               "UseMatrix": true | ||||
|             }, | ||||
|           ] | ||||
|         }, | ||||
|       ], | ||||
|       "DeviceButtons": [ | ||||
|         { | ||||
|           "DeviceName": "JVC AV Receiver", | ||||
|           "ButtonName": "SurroundToggle", | ||||
|           "DisplayName": "Surround Toggle", | ||||
|           "IsStateful": true | ||||
|         }, | ||||
|       ] | ||||
|     } | ||||
| ``` | ||||
|  | ||||
| #### Platform | ||||
|  | ||||
| - `EmitDevicesOnStartup`: If true, each device frome each hub will be written to the console on startup | ||||
|  | ||||
| - `Hubs`: A list of harmony hub configurations | ||||
|  | ||||
| - `Devices`: A list of devices to be used and corresponding hubs | ||||
|  | ||||
| - `Matrix`: An optional configuration for an hdmi matrix | ||||
|  | ||||
| - `ControlUnits`: Configures one logical control unit. This is usually a single TV in a single room. | ||||
|  | ||||
| #### Devices | ||||
|  | ||||
| - `Name`: The name of the device to use from an existing harmony hub configuration | ||||
|  | ||||
| > Note: Use `EmitDevicesOnStartup` if you are unsure of which devices are available. | ||||
|  | ||||
| - `Hub`: The name of the hub to look the device on | ||||
|  | ||||
| #### Matrix | ||||
|  | ||||
| - `DeviceName`: The name of the matrix device as seen in the harmony configuration | ||||
|  | ||||
| > Note: This device must appear in the device list | ||||
|  | ||||
| - `Inputs`: A list of input devices and their corresponding input number as it relates to the matrix switch | ||||
|  | ||||
| - `Outputs`: A list of output devices and their corresponding output letter as it relates to the matrix switch | ||||
|  | ||||
| #### ControlUnits | ||||
|  | ||||
| This is the logical display unit. It will appear as a tv accessory in the home app. | ||||
|  | ||||
| - `DisplayName`: The name of the control unit | ||||
|  | ||||
| - `Activities`: A list of activity configurations. These will be the inputs associated with the tv accessory | ||||
| - `DeviceSetupList`: A list of devices associated with an activity and their corresponding input setting | ||||
|  | ||||
| - `VolumeDevice`: The device that should be used to control the volume of the activity. This is usually a stereo or a tv | ||||
|  | ||||
| - `ControlDevice`: The device that is used to control UI navigation | ||||
|  | ||||
| - `OutputDevice`: The device that is used to display the content. This is usually a TV | ||||
|  | ||||
| - `UseMatrix`: If true, corresponding commands will be issued to the matrix device to route input to output | ||||
							
								
								
									
										336
									
								
								config.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										336
									
								
								config.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,336 @@ | ||||
| { | ||||
|     "bridge": { | ||||
|         "name": "Homebridge", | ||||
|         "username": "CC:22:3D:E3:CE:42", | ||||
|         "port": 51826, | ||||
|         "pin": "031-45-154" | ||||
|     }, | ||||
|     "platforms": [ | ||||
|         { | ||||
|             "platform": "websocket", | ||||
|             "name": "websocket", | ||||
|             "port": 4050 | ||||
|         }, | ||||
|         { | ||||
|             "platform": "HarmonyHubMatrix", | ||||
|             "harmonyHubIp": "192.168.1.14", | ||||
|             "neeoHubIp": "192.168.1.24", | ||||
|             "Matrix": { | ||||
|                 "Hub": "Harmony", | ||||
|                 "DeviceName": "Gefen AV Switch", | ||||
|                 "Inputs": [ | ||||
|                     { | ||||
|                         "InputNumber": "1", | ||||
|                         "InputDevice": "Microsoft Xbox One" | ||||
|                     }, | ||||
|                     { | ||||
|                         "InputNumber": "5", | ||||
|                         "InputDevice": "Apple TV Gen 4" | ||||
|                     }, | ||||
|                     { | ||||
|                         "InputNumber": "3", | ||||
|                         "InputDevice": "Chromecast" | ||||
|                     }, | ||||
|                     { | ||||
|                         "InputNumber": "2", | ||||
|                         "InputDevice": "Sony PS4" | ||||
|                     } | ||||
|                 ], | ||||
|                 "Outputs": [ | ||||
|                     { | ||||
|                         "OutputLetter": "B", | ||||
|                         "OutputDevice": "LG TV" | ||||
|                     }, | ||||
|                     { | ||||
|                         "OutputLetter": "C", | ||||
|                         "OutputDevice": "Vizio TV" | ||||
|                     }, | ||||
|                     { | ||||
|                         "OutputLetter": "D", | ||||
|                         "OutputDevice": "Westinghouse TV" | ||||
|                     } | ||||
|                 ] | ||||
|             }, | ||||
|             "ControlUnits": [ | ||||
|                 { | ||||
|                     "DisplayName": "Living Room", | ||||
|                     "Activities": [ | ||||
|                         { | ||||
|                             "DisplayName": "Chromecast", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "LG TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "JVC AV Receiver", | ||||
|                                     "Input": "DVD" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Chromecast", | ||||
|                                     "Input": [] | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "JVC AV Receiver", | ||||
|                             "ControlDevice": "Chromecast", | ||||
|                             "OutputDevice": "LG TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         }, | ||||
|                         { | ||||
|                             "DisplayName": "Play Xbox One", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Microsoft Xbox One", | ||||
|                                     "Input": [] | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "LG TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "JVC AV Receiver", | ||||
|                                     "Input": "DVD" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "JVC AV Receiver", | ||||
|                             "ControlDevice": "Microsoft Xbox One", | ||||
|                             "OutputDevice": "LG TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         }, | ||||
|                         { | ||||
|                             "DisplayName": "Play PS4", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Sony PS4", | ||||
|                                     "Input": [] | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "LG TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "JVC AV Receiver", | ||||
|                                     "Input": "DVD" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "JVC AV Receiver", | ||||
|                             "ControlDevice": "Sony PS4", | ||||
|                             "OutputDevice": "LG TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         }, | ||||
|                         { | ||||
|                             "DisplayName": "Apple TV", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Apple TV Gen 4", | ||||
|                                     "Input": [] | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "LG TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "JVC AV Receiver", | ||||
|                                     "Input": "DVD" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "JVC AV Receiver", | ||||
|                             "ControlDevice": "Apple TV Gen 4", | ||||
|                             "OutputDevice": "LG TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         }, | ||||
|                         { | ||||
|                             "DisplayName": "Listen to Music", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "JVC AV Receiver", | ||||
|                                     "Input": "DVD" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "JVC AV Receiver", | ||||
|                             "ControlDevice": [], | ||||
|                             "OutputDevice": [], | ||||
|                             "UseMatrix": "false" | ||||
|                         } | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "DisplayName": "Bedroom", | ||||
|                     "Activities": [ | ||||
|                         { | ||||
|                             "DisplayName": "Chromecast", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Neeo", | ||||
|                                     "DeviceName": "Vizio TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Neeo", | ||||
|                                     "DeviceName": "Harmon Kardon Receicer", | ||||
|                                     "Input": "Game" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Chromecast", | ||||
|                                     "Input": [] | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "Harmon Kardon Receicer", | ||||
|                             "ControlDevice": "Chromecast", | ||||
|                             "OutputDevice": "Vizio TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         }, | ||||
|                         { | ||||
|                             "DisplayName": "Play Xbox One", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Microsoft Xbox One", | ||||
|                                     "Input": [] | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Neeo", | ||||
|                                     "DeviceName": "Vizio TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Neeo", | ||||
|                                     "DeviceName": "Harmon Kardon Receicer", | ||||
|                                     "Input": "Game" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "Harmon Kardon Receicer", | ||||
|                             "ControlDevice": "Microsoft Xbox One", | ||||
|                             "OutputDevice": "Vizio TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         }, | ||||
|                         { | ||||
|                             "DisplayName": "Play PS4", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Sony PS4", | ||||
|                                     "Input": [] | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Neeo", | ||||
|                                     "DeviceName": "Vizio TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Neeo", | ||||
|                                     "DeviceName": "Harmon Kardon Receicer", | ||||
|                                     "Input": "DVD" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "Harmon Kardon Receicer", | ||||
|                             "ControlDevice": "Sony PS4", | ||||
|                             "OutputDevice": "LG TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         }, | ||||
|                         { | ||||
|                             "DisplayName": "Apple TV", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Apple TV Gen 4", | ||||
|                                     "Input": [] | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Neeo", | ||||
|                                     "DeviceName": "Vizio TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Neeo", | ||||
|                                     "DeviceName": "Harmon Kardon Receicer", | ||||
|                                     "Input": "Game" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "Harmon Kardon Receicer", | ||||
|                             "ControlDevice": "Apple TV Gen 4", | ||||
|                             "OutputDevice": "Vizio TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         } | ||||
|                     ] | ||||
|                 }, | ||||
|                 { | ||||
|                     "DisplayName": "Kitchen", | ||||
|                     "Activities": [ | ||||
|                         { | ||||
|                             "DisplayName": "Chromecast", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Chromecast", | ||||
|                                     "Input": [] | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Westinghouse TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "Westinghouse TV", | ||||
|                             "ControlDevice": "Chromecast", | ||||
|                             "OutputDevice": "Westinghouse TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         }, | ||||
|                         { | ||||
|                             "DisplayName": "Watch Apple Tv", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Apple TV Gen 4", | ||||
|                                     "Input": [] | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Westinghouse TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "Westinghouse TV", | ||||
|                             "ControlDevice": "Apple TV Gen 4", | ||||
|                             "OutputDevice": "Westinghouse TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         }, | ||||
|                         { | ||||
|                             "DisplayName": "Play Xbox One", | ||||
|                             "DeviceSetupList": [ | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Westinghouse TV", | ||||
|                                     "Input": "HDMI1" | ||||
|                                 }, | ||||
|                                 { | ||||
|                                     "Hub": "Harmony", | ||||
|                                     "DeviceName": "Microsoft Xbox One", | ||||
|                                     "Input": [] | ||||
|                                 } | ||||
|                             ], | ||||
|                             "VolumeDevice": "Westinghouse TV", | ||||
|                             "ControlDevice": "Microsoft Xbox One", | ||||
|                             "OutputDevice": "Westinghouse TV", | ||||
|                             "UseMatrix": "true" | ||||
|                         } | ||||
|                     ] | ||||
|                 } | ||||
|             ] | ||||
|         } | ||||
|     ] | ||||
| } | ||||
| @@ -3,8 +3,5 @@ | ||||
| 		{ | ||||
| 			"path": "." | ||||
| 		} | ||||
|     ], | ||||
|     "settings": { | ||||
|         "editor.tabSize": 2 | ||||
|     } | ||||
| 	] | ||||
| } | ||||
							
								
								
									
										2072
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2072
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										21
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,13 +1,10 @@ | ||||
| { | ||||
|     "name": "@watsonb8/homebridge-harmony-control", | ||||
|     "version": "1.2.2", | ||||
|     "name": "homebridge-harmony-control", | ||||
|     "version": "1.0.0", | ||||
|     "description": "Homebridge platform to control smart home equipment by room.", | ||||
|     "main": "bin/index.js", | ||||
|     "publishConfig": { | ||||
|         "registry": "http://10.44.1.6:4873/" | ||||
|     }, | ||||
|     "scripts": { | ||||
|         "build": "tsc --build", | ||||
|         "build": "tsc", | ||||
|         "prepublishOnly": "npm run build" | ||||
|     }, | ||||
|     "repository": { | ||||
| @@ -24,24 +21,24 @@ | ||||
|         "homekit" | ||||
|     ], | ||||
|     "engines": { | ||||
|         "homebridge": ">=1.1.6", | ||||
|         "homebridge": ">=0.4.21", | ||||
|         "node": ">=7.6.0" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "homebridge": "^1.2.5", | ||||
|         "rollup-plugin-typescript": "^1.0.1", | ||||
|         "tslib": "^1.10.0", | ||||
|         "tslint": "^5.17.0", | ||||
|         "typescript": "^3.9.10" | ||||
|         "typescript": "^3.5.1" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@babel/core": "^7.4.5", | ||||
|         "@babel/preset-env": "^7.4.5", | ||||
|         "@babel/preset-typescript": "^7.3.3", | ||||
|         "@types/node": "^12.0.7", | ||||
|         "@types/node-fetch": "^2.5.0", | ||||
|         "harmony-websocket": "^1.1.0", | ||||
|         "reflect-metadata": "^0.1.13", | ||||
|         "request": "^2.88.0", | ||||
|         "tsyringe": "^4.6.0" | ||||
|         "homebridge": "^0.4.50", | ||||
|         "node-fetch": "^2.6.0", | ||||
|         "request": "^2.88.0" | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										315
									
								
								src/Accessories/ControlUnit.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								src/Accessories/ControlUnit.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,315 @@ | ||||
| import { Activity } from '../Models/Activity'; | ||||
| import { Matrix } from '../Models/Matrix'; | ||||
| import { IAccessory } from './IAccessory'; | ||||
| import callbackify from '../Util/Callbackify'; | ||||
| import HubDataProvider from '../DataProviders/HubDataProvider'; | ||||
|  | ||||
| let Service: HAPNodeJS.Service; | ||||
| let Characteristic: HAPNodeJS.Characteristic; | ||||
| let Api: any; | ||||
| let homebridge: any; | ||||
|  | ||||
| /** | ||||
|  * Enum describing remote key presses from homebridge. | ||||
|  */ | ||||
| export enum RemoteKey { | ||||
|     REWIND = 0, | ||||
|     FAST_FORWARD = 1, | ||||
|     NEXT_TRACK = 2, | ||||
|     PREVIOUS_TRACK = 3, | ||||
|     ARROW_UP = 4, | ||||
|     ARROW_DOWN = 5, | ||||
|     ARROW_LEFT = 6, | ||||
|     ARROW_RIGHT = 7, | ||||
|     SELECT = 8, | ||||
|     BACK = 9, | ||||
|     EXIT = 10, | ||||
|     PLAY_PAUSE = 11, | ||||
|     INFORMATION = 15, | ||||
| } | ||||
|  | ||||
| export interface IControlUnitProps { | ||||
|     dataProvider: HubDataProvider, | ||||
|     displayName: string, | ||||
|     activities: Array<Activity>, | ||||
|     api: any, | ||||
|     log: any, | ||||
|     homebridge: any, | ||||
|     isExternal: boolean, | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * ControlUnit accessory | ||||
|  */ | ||||
| export class ControlUnit implements IAccessory { | ||||
|     //Required by homebridge | ||||
|     name: string = ""; | ||||
|  | ||||
|     //fields | ||||
|     private log: any = {}; | ||||
|     private displayName: string = ""; | ||||
|     private isExternal: boolean = false; | ||||
|  | ||||
|     //Service fields | ||||
|     private televisionService: HAPNodeJS.Service | undefined; | ||||
|     private televisionSpeakerService: HAPNodeJS.Service | undefined; | ||||
|     private informationService: HAPNodeJS.Service | undefined; | ||||
|     private inputServices: Array<HAPNodeJS.Service | undefined> = []; | ||||
|  | ||||
|     //Harmony fields | ||||
|     private activities: Array<Activity> = []; | ||||
|     private dataProvider: HubDataProvider; | ||||
|  | ||||
|     public platformAccessory: any; | ||||
|  | ||||
|     /** | ||||
|      * Constructor | ||||
|      * @param props Input properties | ||||
|      */ | ||||
|     constructor(props: IControlUnitProps) { | ||||
|         //Assign class variables | ||||
|         this.log = props.log; | ||||
|         Api = props.api; | ||||
|         Service = props.api.hap.Service; | ||||
|         Characteristic = props.api.hap.Characteristic; | ||||
|         this.name = props.displayName; | ||||
|         this.displayName = props.isExternal ? `${props.displayName}-Remote` : props.displayName; | ||||
|         this.isExternal = props.isExternal; | ||||
|  | ||||
|         this.activities = props.activities; | ||||
|  | ||||
|         this.dataProvider = props.dataProvider; | ||||
|         homebridge = props.homebridge; | ||||
|  | ||||
|         this.platformAccessory = new homebridge.platformAccessory(this.displayName, this.generateUUID(), homebridge.hap.Accessory.Categories.TELEVISION); | ||||
|  | ||||
|         //Configure services | ||||
|         this.configureTvService(); | ||||
|  | ||||
|         this.configureTvSpeakerService(); | ||||
|  | ||||
|  | ||||
|         // this.configureAccessoryInformation(); | ||||
|         this.configureInputSourceService(); | ||||
|  | ||||
|         //Configure external services | ||||
|         this.getServices().forEach(service => { | ||||
|             try { | ||||
|                 this.platformAccessory.addService(service); | ||||
|             } catch (error) { } | ||||
|  | ||||
|             //@ts-ignore | ||||
|             if (service.linked) { | ||||
|                 //@ts-ignore | ||||
|                 this.televisionService!.addLinkedService(service); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
|     /************* | ||||
|      *  | ||||
|      * Tv Service | ||||
|      *  | ||||
|      *************/ | ||||
|  | ||||
|     /** | ||||
|      * Configure television service | ||||
|      */ | ||||
|     private configureTvService(): void { | ||||
|         this.televisionService = new Service.Television( | ||||
|             this.displayName, | ||||
|             'tvService' | ||||
|         ) | ||||
|  | ||||
|         this.televisionService.setCharacteristic(Characteristic.ConfiguredName, this.displayName); | ||||
|         //@ts-ignore | ||||
|         this.televisionService.setCharacteristic(Characteristic.SleepDiscoveryMode, Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE); | ||||
|         this.televisionService.setCharacteristic(Characteristic.ActiveIdentifier, 1); | ||||
|         this.televisionService.setCharacteristic(Characteristic.Active, false); | ||||
|  | ||||
|         //setup listeners | ||||
|         this.televisionService.getCharacteristic(Characteristic.Active) | ||||
|             //@ts-ignore | ||||
|             .on("set", callbackify(this.onSetAccessoryActive)) | ||||
|             //@ts-ignore | ||||
|             .on("get", callbackify(this.onGetAccessoryActive)); | ||||
|  | ||||
|         //Set remote characteristics if is external | ||||
|         if (this.isExternal) { | ||||
|             this.televisionService.getCharacteristic(Characteristic.RemoteKey) | ||||
|                 //@ts-ignore | ||||
|                 .on("set", callbackify(this.onSetRemoteKey)); | ||||
|         } | ||||
|  | ||||
|         this.televisionService.getCharacteristic(Characteristic.ActiveIdentifier) | ||||
|             //@ts-ignore | ||||
|             .on("set", callbackify(this.onSetActiveIdentifier)) | ||||
|             //@ts-ignore | ||||
|             .on("get", callbackify(this.onGetActiveIdentifier)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Event handler for SET active characteristic | ||||
|      */ | ||||
|     private onSetAccessoryActive = async (value: any) => { | ||||
|         if (!this.isExternal) { | ||||
|             switch (value) { | ||||
|                 case 0: this.dataProvider.powerOff(this.name); break; | ||||
|                 //Turn on with first activity | ||||
|                 case 1: this.dataProvider.powerOn(this.name, this.activities[0]); break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Event handler for GET active characteristic | ||||
|      */ | ||||
|     private onGetAccessoryActive = async () => { | ||||
|         //@ts-ignore | ||||
|         return this.dataProvider.getIsActive() ? Characteristic.Active.Active : Characteristic.Active.Inactive | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Event handler for SET remote key | ||||
|      */ | ||||
|     private onSetRemoteKey = async (key: any) => { | ||||
|         if (this.isExternal) { | ||||
|             this.dataProvider.sendKeyPress(this.name, key); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Event handler for SET active identifier characteristic | ||||
|      */ | ||||
|     private onSetActiveIdentifier = async (identifier: any) => { | ||||
|         if (!this.isExternal) { | ||||
|             this.dataProvider.startActivity(this.name, this.activities[identifier]); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Event handler for GET active identifier characteristic | ||||
|      */ | ||||
|     private onGetActiveIdentifier = async () => { | ||||
|         let currentActivity: Activity = this.dataProvider.getIsActive(this.name)!; | ||||
|         let identifier: number = 0; | ||||
|         if (currentActivity) { | ||||
|             identifier = this.activities.findIndex(e => e.displayName === currentActivity.displayName); | ||||
|         } | ||||
|         return identifier; | ||||
|     } | ||||
|  | ||||
|     /****************** | ||||
|      *  | ||||
|      * Speaker Service | ||||
|      *  | ||||
|      *****************/ | ||||
|  | ||||
|     /** | ||||
|      * Configure Speaker Service | ||||
|      */ | ||||
|     private configureTvSpeakerService(): void { | ||||
|         this.televisionSpeakerService = new Service.TelevisionSpeaker( | ||||
|             this.displayName, | ||||
|             'tvSpeakerService' | ||||
|         ); | ||||
|         this.televisionSpeakerService.setCharacteristic(Characteristic.Name, this.displayName); | ||||
|         //@ts-ignore | ||||
|         this.televisionSpeakerService.setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE); | ||||
|         //@ts-ignore | ||||
|         this.televisionSpeakerService.setCharacteristic(Characteristic.VolumeControlType, Characteristic.VolumeControlType.ABSOLUTE); | ||||
|         this.televisionSpeakerService.subtype = this.displayName + "Volume"; | ||||
|         if (this.televisionService) { | ||||
|             //@ts-ignore | ||||
|             this.televisionService.addLinkedService(this.televisionSpeakerService); | ||||
|         } | ||||
|  | ||||
|         //Setup listeners | ||||
|         this.televisionSpeakerService.getCharacteristic(Characteristic.VolumeSelector) | ||||
|             //@ts-ignore | ||||
|             .on("set", callbackify(this.onSetVolumeSelector)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Event handler for SET volume characteristic | ||||
|      */ | ||||
|     private onSetVolumeSelector = async (value: any) => { | ||||
|         if (this.isExternal) { | ||||
|             switch (value) { | ||||
|                 case 0: this.dataProvider.volumeUp(this.name); break; | ||||
|                 case 1: this.dataProvider.volumeDown(this.name); break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /********************* | ||||
|      *  | ||||
|      * Information Service | ||||
|      *  | ||||
|      ********************/ | ||||
|  | ||||
|     /** | ||||
|      * Configure information service | ||||
|      */ | ||||
|     private configureAccessoryInformation(): void { | ||||
|         this.informationService = new Service.AccessoryInformation(this.displayName, 'information'); | ||||
|         this.informationService | ||||
|             .setCharacteristic(Characteristic.Manufacturer, 'Loftux Carwings') | ||||
|             .setCharacteristic(Characteristic.Model, 'Heater-Cooler') | ||||
|     } | ||||
|  | ||||
|     /***************** | ||||
|      *  | ||||
|      * Input services | ||||
|      *  | ||||
|      *****************/ | ||||
|  | ||||
|     /** | ||||
|      * Configure input service | ||||
|      */ | ||||
|     private configureInputSourceService(): void { | ||||
|         let inputs: Array<HAPNodeJS.Service> = []; | ||||
|         this.activities.forEach((activity: Activity, index: number) => { | ||||
|             let inputService = new Service.InputSource(activity.displayName, 'activity' + activity.displayName); | ||||
|             inputService | ||||
|                 .setCharacteristic(Characteristic.Identifier, index) | ||||
|                 .setCharacteristic( | ||||
|                     Characteristic.ConfiguredName, | ||||
|                     activity.displayName) | ||||
|                 .setCharacteristic( | ||||
|                     Characteristic.IsConfigured, | ||||
|                     //@ts-ignore | ||||
|                     Characteristic.IsConfigured.CONFIGURED | ||||
|                 ) | ||||
|                 //@ts-ignore | ||||
|                 .setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.HDMI); | ||||
|  | ||||
|             //@ts-ignore | ||||
|             this.televisionService!.addLinkedService(inputService); | ||||
|             inputs.push(inputService); | ||||
|         }); | ||||
|         this.inputServices = inputs; | ||||
|     } | ||||
|  | ||||
|     private generateUUID(): string { // Public Domain/MIT | ||||
|         var d = new Date().getTime(); | ||||
|         if (typeof performance !== 'undefined' && typeof performance.now === 'function') { | ||||
|             d += performance.now(); //use high-precision timer if available | ||||
|         } | ||||
|         return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { | ||||
|             var r = (d + Math.random() * 16) % 16 | 0; | ||||
|             d = Math.floor(d / 16); | ||||
|             return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called by homebridge to gather services for this accessory. | ||||
|      */ | ||||
|     getServices(): Array<HAPNodeJS.Service> { | ||||
|         let services: Array<HAPNodeJS.Service> = [this.televisionService!, this.televisionSpeakerService!]; | ||||
|         this.inputServices.forEach((service: HAPNodeJS.Service | undefined) => { | ||||
|             services.push(service!); | ||||
|         }); | ||||
|         return (services); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/Accessories/IAccessory.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Accessories/IAccessory.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
|  | ||||
| /** | ||||
|  * Interface to describe homebridge required elements. | ||||
|  */ | ||||
| export interface IAccessory { | ||||
|     /** | ||||
|      * Required by homebridge. | ||||
|      */ | ||||
|     name: string, | ||||
|     /** | ||||
|      * Called by homebridge to gather services. | ||||
|      */ | ||||
|     getServices(): Array<HAPNodeJS.Service>, | ||||
| } | ||||
							
								
								
									
										508
									
								
								src/DataProviders/HubDataProvider.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										508
									
								
								src/DataProviders/HubDataProvider.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,508 @@ | ||||
| import { Activity } from "../Models/Activity"; | ||||
| import { DeviceSetupItem } from "../Models/DeviceSetupItem"; | ||||
| import { Input, Matrix, Output } from "../Models/Matrix"; | ||||
| import { IDevice } from './IDevice'; | ||||
| import { RemoteKey } from '../Accessories/ControlUnit'; | ||||
| import { sleep } from '../Util/Sleep'; | ||||
| import { hub } from ".."; | ||||
| import fetch from 'node-fetch'; | ||||
|  | ||||
| let Characteristic: HAPNodeJS.Characteristic; | ||||
|  | ||||
| const Harmony = require("harmony-websocket"); | ||||
|  | ||||
| interface IActivityState { | ||||
|     currentActivity: Activity | ||||
| } | ||||
|  | ||||
| interface IHubDataProviderProps { | ||||
|     harmonyHubAddress: string, | ||||
|     neeoHubAddress: string, | ||||
|     log: any, | ||||
|     matrix: Matrix | ||||
| } | ||||
|  | ||||
| interface INeeoMacro { | ||||
|     roomKey: string, | ||||
|     macroKey: string, | ||||
|     deviceKey: string, | ||||
| } | ||||
|  | ||||
| class HubDataProvider { | ||||
|     //Harmony Fields | ||||
|     private harmonyHubAddress: string; | ||||
|     private harmonyConnected: boolean = false; | ||||
|  | ||||
|     //Neeo Fields | ||||
|     private neeoHubAddress: string; | ||||
|  | ||||
|     //Common Fields | ||||
|     private log: any; | ||||
|  | ||||
|     private devices: { [name: string]: IDevice; } = {}; | ||||
|     private states: { [controlUnitName: string]: (IActivityState | undefined) } = {}; | ||||
|  | ||||
|     private matrix: Matrix; | ||||
|  | ||||
|     constructor(props: IHubDataProviderProps) { | ||||
|         this.log = props.log; | ||||
|         this.harmonyHubAddress = props.harmonyHubAddress; | ||||
|         this.neeoHubAddress = props.neeoHubAddress; | ||||
|         this.matrix = props.matrix; | ||||
|  | ||||
|         if (this.harmonyHubAddress) { | ||||
|             try { | ||||
|                 Promise.resolve(this.connectHarmony(this.harmonyHubAddress)); | ||||
|             } catch (err) { | ||||
|                 this.log(`Error connecting to harmony hub at ${this.harmonyHubAddress}: ${err}`); | ||||
|             } | ||||
|         } | ||||
|         if (this.neeoHubAddress) { | ||||
|             try { | ||||
|                 Promise.resolve(this.connectNeeo(this.neeoHubAddress)); | ||||
|             } catch (err) { | ||||
|                 this.log(`Error connecting to neeo hub at ${this.neeoHubAddress}: ${err}`) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Power on all devices in an activity. | ||||
|      */ | ||||
|     public powerOn = async (controlUnitName: string, activity: Activity) => { | ||||
|         //Only power on if not alread on | ||||
|         let currentActivity = this.states[controlUnitName] ? this.states[controlUnitName]!.currentActivity : undefined; | ||||
|         if (!currentActivity) { | ||||
|             await this.startActivity(controlUnitName, activity); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Power off all devices in an activity that aren't being used. | ||||
|      */ | ||||
|     public powerOff = async (controlUnitName: string) => { | ||||
|         if (!this.states[controlUnitName]) { | ||||
|             return; | ||||
|         } | ||||
|         //Build potential list of devices to turn off | ||||
|         let devicesToTurnOff: Array<IDevice> = this.states[controlUnitName]!.currentActivity.deviceSetupItems | ||||
|             .map((value: DeviceSetupItem): IDevice => { | ||||
|                 return this.getDeviceFromName(value.deviceName, value.hubType); | ||||
|             }); | ||||
|  | ||||
|         //Resolve device conflicts with other controlUnits | ||||
|         devicesToTurnOff = this.sanitizeDeviceList(devicesToTurnOff, controlUnitName); | ||||
|  | ||||
|         await Promise.all( | ||||
|             //Turn off devices | ||||
|             devicesToTurnOff.map(async (device: IDevice) => { | ||||
|                 if (device) { | ||||
|                     await device.powerOff(); | ||||
|                 } | ||||
|             }) | ||||
|         ); | ||||
|  | ||||
|  | ||||
|         this.states[controlUnitName] = undefined; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Start an activity | ||||
|      */ | ||||
|     public startActivity = async (controlUnitName: string, activity: Activity) => { | ||||
|         this.log(`Starting activity ${activity.displayName} for controlUnit: ${controlUnitName}`) | ||||
|         let lastActivity: Activity | undefined = undefined; | ||||
|         if (this.states[controlUnitName]) { | ||||
|             lastActivity = this.states[controlUnitName]!.currentActivity; | ||||
|         } | ||||
|  | ||||
|         //Build potential list of devices to to turn on | ||||
|         let devicesToTurnOn: Array<IDevice> = activity.deviceSetupItems.map((value: DeviceSetupItem): IDevice => { | ||||
|             return this.getDeviceFromName(value.deviceName, value.hubType); | ||||
|         }); | ||||
|  | ||||
|         //Resolve device conflicts with other controlUnits | ||||
|         devicesToTurnOn = this.sanitizeDeviceList(devicesToTurnOn, controlUnitName); | ||||
|  | ||||
|         //Turn on devices | ||||
|         await Promise.all(devicesToTurnOn.map(async (device: IDevice) => { | ||||
|             if (device && device.name) { | ||||
|                 if (!device.on) { | ||||
|                     this.log(`Turning on device ${device.name}`) | ||||
|                     await device.powerOn(); | ||||
|                 } | ||||
|             } | ||||
|         })); | ||||
|  | ||||
|         //Assign correct input | ||||
|         await Promise.all( | ||||
|             activity.deviceSetupItems.map(async (value: DeviceSetupItem) => { | ||||
|                 let device: IDevice = this.getDeviceFromName(value.deviceName, value.hubType); | ||||
|  | ||||
|                 if (device && device.supportsCommand(`Input ${value.input}`)) { | ||||
|                     let command: string = device.getCommand(`Input ${value.input}`); | ||||
|                     await device.sendCommand(command); | ||||
|                 } | ||||
|             }) | ||||
|         ); | ||||
|  | ||||
|         if (activity.useMatrix) { | ||||
|             //get input and output | ||||
|             let input: Input = this.matrix.inputs.filter(e => e.inputDevice === activity.controlDeviceId)[0]; | ||||
|             let output: Output = this.matrix.outputs.filter(e => e.outputDevice === activity.outputDeviceId)[0]; | ||||
|  | ||||
|             let inputCommandName: string = `In ${input.inputNumber}`; | ||||
|             let outputCommandName: string = `Out ${output.outputLetter}`; | ||||
|  | ||||
|             let matrixDevice: IDevice = this.getDeviceFromName(this.matrix.deviceName, this.matrix.hubType); | ||||
|  | ||||
|             //Route hdmi | ||||
|             if (matrixDevice.supportsCommand(inputCommandName) && matrixDevice.supportsCommand(outputCommandName)) { | ||||
|                 await matrixDevice.sendCommand(matrixDevice.getCommand(inputCommandName)); | ||||
|                 await matrixDevice.sendCommand(matrixDevice.getCommand(outputCommandName)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         //Build potential list of devices to turn off | ||||
|         if (lastActivity) { | ||||
|             let devicesToTurnOff: Array<IDevice> = lastActivity.deviceSetupItems.map((value: DeviceSetupItem): IDevice => { | ||||
|                 return this.getDeviceFromName(value.deviceName, value.hubType); | ||||
|             }); | ||||
|  | ||||
|             //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 | ||||
|             devicesToTurnOff.forEach((device: IDevice, index: number) => { | ||||
|                 if (device && device.name && activity.deviceSetupItems.some(e => { | ||||
|                     return (e && e.deviceName === device.name) | ||||
|                 })) { | ||||
|                     delete devicesToTurnOff[index]; | ||||
|                 } | ||||
|             }) | ||||
|  | ||||
|             //Resolve device conflicts with other controlUnits | ||||
|             devicesToTurnOff = this.sanitizeDeviceList(devicesToTurnOff, controlUnitName); | ||||
|  | ||||
|             this.log(`Sanatized devices to turn off: ${JSON.stringify(devicesToTurnOff.map(e => e ? e.name : ""))}`); | ||||
|  | ||||
|             await Promise.all( | ||||
|                 //Turn off devices | ||||
|                 devicesToTurnOff.map(async (device: IDevice) => { | ||||
|                     if (device) { | ||||
|                         if (device.on) { | ||||
|                             this.log(`Turning off device ${device.name}`) | ||||
|                             await device.powerOff(); | ||||
|                         } | ||||
|                     } | ||||
|                 }) | ||||
|             ); | ||||
|  | ||||
|         } | ||||
|  | ||||
|         //Assign current activity | ||||
|         this.states[controlUnitName] = { currentActivity: activity }; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Turn the volume up for the current running activity. | ||||
|      */ | ||||
|     public volumeUp = async (controlUnitName: string) => { | ||||
|         let volumeUpCommand: string = "Volume Up" | ||||
|         let currentActivity: Activity = this.states[controlUnitName]!.currentActivity; | ||||
|         let deviceSetupItem: DeviceSetupItem = currentActivity.deviceSetupItems.filter(e => e.deviceName == currentActivity.volumeDeviceId)[0]; | ||||
|  | ||||
|         if (this.states[controlUnitName]) { | ||||
|             let volumeDevice: IDevice = this.getDeviceFromName(currentActivity.volumeDeviceId, deviceSetupItem.hubType); | ||||
|             if (volumeDevice.supportsCommand(volumeUpCommand)) { | ||||
|                 volumeDevice.sendCommand(volumeDevice.getCommand(volumeUpCommand)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Volume down for current running activity. | ||||
|      */ | ||||
|     public volumeDown = async (controlUnitName: string) => { | ||||
|         let volumeDownCommand: string = "Volume Down" | ||||
|         let currentActivity: Activity = this.states[controlUnitName]!.currentActivity; | ||||
|         let deviceSetupItem: DeviceSetupItem = currentActivity.deviceSetupItems.filter(e => e.deviceName == currentActivity.volumeDeviceId)[0]; | ||||
|  | ||||
|         if (this.states[controlUnitName]) { | ||||
|             let volumeDevice: IDevice = this.getDeviceFromName(currentActivity.volumeDeviceId, deviceSetupItem.hubType); | ||||
|             if (volumeDevice.supportsCommand(volumeDownCommand)) { | ||||
|                 volumeDevice.sendCommand(volumeDevice.getCommand(volumeDownCommand)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Return if a control unit is active | ||||
|      * @param controlUnitName  | ||||
|      */ | ||||
|     public getIsActive(controlUnitName: string): Activity | undefined { | ||||
|         return this.states[controlUnitName] ? this.states[controlUnitName]!.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<IDevice>, controlUnitName: string): Array<IDevice> { | ||||
|         for (let controlUnitKey in this.states) { | ||||
|             //Skip self | ||||
|             if (controlUnitKey === controlUnitName) { | ||||
|                 continue; | ||||
|             } | ||||
|             let currentOtherState: IActivityState = this.states[controlUnitKey]!; | ||||
|  | ||||
|             if (currentOtherState) { | ||||
|                 currentOtherState.currentActivity.deviceSetupItems.forEach((value: DeviceSetupItem) => { | ||||
|                     //there are devices to remove | ||||
|                     if (devicesToTurnOn.some(e => e && e.name === value.deviceName)) { | ||||
|                         let deviceToRemove: IDevice = devicesToTurnOn.filter(i => i.name === value.deviceName)[0]; | ||||
|                         delete devicesToTurnOn[devicesToTurnOn.indexOf(deviceToRemove)]; | ||||
|                     } | ||||
|                 }); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return devicesToTurnOn; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Send key press for current activity. | ||||
|      *  | ||||
|      * @param controlUnitName The name of the control unit to act on. | ||||
|      * @param key The key to send. | ||||
|      */ | ||||
|     public sendKeyPress = async (controlUnitName: string, key: any) => { | ||||
|         if (this.states[controlUnitName]) { | ||||
|             let commandName: string = ""; | ||||
|  | ||||
|             let currentActivity: Activity = this.states[controlUnitName]!.currentActivity; | ||||
|             let deviceSetupItem: DeviceSetupItem = currentActivity.deviceSetupItems.filter(e => e.deviceName == currentActivity.volumeDeviceId)[0]; | ||||
|  | ||||
|             let device: IDevice = this.getDeviceFromName(this.states[controlUnitName]!.currentActivity.controlDeviceId, deviceSetupItem.hubType); | ||||
|             switch (key) { | ||||
|                 case RemoteKey.ARROW_UP: { | ||||
|                     commandName = "Direction Up"; | ||||
|                     break; | ||||
|                 } | ||||
|                 case RemoteKey.ARROW_DOWN: { | ||||
|                     commandName = "Direction Down"; | ||||
|                     break; | ||||
|                 } | ||||
|                 case RemoteKey.ARROW_LEFT: { | ||||
|                     commandName = "Direction Left"; | ||||
|                     break; | ||||
|                 } | ||||
|                 case RemoteKey.ARROW_RIGHT: { | ||||
|                     commandName = "Direction Right"; | ||||
|                     break; | ||||
|                 } | ||||
|                 case RemoteKey.SELECT: { | ||||
|                     commandName = "Select"; | ||||
|                     break; | ||||
|                 } | ||||
|                 case RemoteKey.PLAY_PAUSE: { | ||||
|                     commandName = "Pause"; | ||||
|                     break; | ||||
|                 } | ||||
|                 case RemoteKey.INFORMATION: { | ||||
|                     commandName = "Menu"; | ||||
|                     break; | ||||
|                 } | ||||
|                 case RemoteKey.BACK: { | ||||
|                     commandName = "Back"; | ||||
|                     break; | ||||
|                 } | ||||
|                 case RemoteKey.EXIT: { | ||||
|                     commandName = "Back"; | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             if (device && device.supportsCommand(commandName)) { | ||||
|                 device.sendCommand(device.getCommand(commandName)); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Get the IDevice by name. | ||||
|      * @param deviceName The device to retrieve. | ||||
|      */ | ||||
|     private getDeviceFromName(deviceName: string, hubType: hub): IDevice { | ||||
|         return this.devices[`${hubType}-${deviceName}`]; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Connect to harmony hub and receive device info. | ||||
|      */ | ||||
|     private connectHarmony = async (address: string): Promise<void> => { | ||||
|         let harmony: any = new Harmony(); | ||||
|         //Listeners | ||||
|         harmony.on('open', () => { | ||||
|             this.harmonyConnected = true; | ||||
|         }); | ||||
|         harmony.on('close', () => { | ||||
|             this.harmonyConnected = false; | ||||
|         }); | ||||
|         await harmony.connect(address); | ||||
|         let self = this; | ||||
|  | ||||
|         setTimeout(async function () { | ||||
|             if (self.harmonyConnected) { | ||||
|                 let devices: any = await harmony.getDevices(); | ||||
|                 try { | ||||
|                     await Promise.all( | ||||
|                         //Add each to dictionary | ||||
|                         devices.map(async (dev: any) => { | ||||
|                             //get commands | ||||
|                             let commands: { [name: string]: string } = {}; | ||||
|                             let deviceCommands: any = await harmony.getDeviceCommands(dev.id); | ||||
|                             deviceCommands.forEach((command: any) => { | ||||
|                                 commands[command.label] = command.action; | ||||
|                             }); | ||||
|                             self.devices[`Harmony-${dev.label}`] = { | ||||
|                                 id: dev.id, | ||||
|                                 name: dev.label, | ||||
|                                 commands: commands, | ||||
|                                 harmony: harmony, | ||||
|                                 hubType: "Harmony", | ||||
|                                 log: self.log, | ||||
|                                 on: false, | ||||
|                                 //Define device methods | ||||
|                                 supportsCommand(commandName: string): boolean { | ||||
|                                     let command = commands[commandName]; | ||||
|                                     return (command) ? true : false; | ||||
|                                 }, | ||||
|                                 getCommand(commandName: string): string { | ||||
|                                     return commands[commandName]; | ||||
|                                 }, | ||||
|                                 async powerOn(): Promise<void> { | ||||
|                                     let powerOnCommand: string = "Power On"; | ||||
|                                     let powerToggleCommand: string = "Power Toggle"; | ||||
|                                     if (this.supportsCommand(powerOnCommand)) { | ||||
|                                         await this.sendCommand(this.getCommand(powerOnCommand)); | ||||
|                                         this.on = true; | ||||
|                                     } else if (this.supportsCommand(powerToggleCommand)) { | ||||
|                                         await this.sendCommand(this.getCommand(powerToggleCommand)); | ||||
|                                         this.on = true; | ||||
|                                     } | ||||
|                                 }, | ||||
|                                 async powerOff(): Promise<void> { | ||||
|                                     let powerOffCommand: string = "Power Off"; | ||||
|                                     let powerToggleCommand: string = "Power Toggle"; | ||||
|                                     if (this.supportsCommand(powerOffCommand)) { | ||||
|                                         await this.sendCommand(this.getCommand(powerOffCommand)); | ||||
|                                         this.on = false; | ||||
|                                     } else if (this.supportsCommand(powerToggleCommand)) { | ||||
|                                         await this.sendCommand(this.getCommand(powerToggleCommand)); | ||||
|                                         this.on = false; | ||||
|                                     } | ||||
|                                 }, | ||||
|                                 async sendCommand(command: string): Promise<void> { | ||||
|                                     try { | ||||
|                                         //Execute command | ||||
|                                         let response = await this.harmony.sendCommand(JSON.stringify(command)); | ||||
|  | ||||
|                                         //Sleep | ||||
|                                         await sleep(800); | ||||
|                                     } catch (err) { | ||||
|                                         this.log(`ERROR - error sending command to harmony: ${err}`); | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         })); | ||||
|                     self.log(`Harmony data provider ready`); | ||||
|  | ||||
|                 } catch (err) { | ||||
|                     self.log(`ERROR - error connecting to harmony: ${err}`); | ||||
|                 } | ||||
|             } | ||||
|         }, 1000); | ||||
|     } | ||||
|  | ||||
|     private connectNeeo = async (address: string): Promise<void> => { | ||||
|  | ||||
|         let baseUrl = `http://${address}:3000/v1/projects/home`; | ||||
|  | ||||
|         let response: any = await fetch(`${baseUrl}/rooms`); | ||||
|         let rooms: { [key: string]: any } = await response.json(); | ||||
|  | ||||
|         function regularizeString(str: string) { | ||||
|             str = str.toLowerCase(); | ||||
|             return str.replace(/(?:^|\s)\S/g, function (a) { return a.toUpperCase(); }); | ||||
|         } | ||||
|  | ||||
|         for (let key in rooms) { | ||||
|             let devices: { [key: string]: any } = rooms[key].devices; | ||||
|             for (let deviceKey in devices) { | ||||
|                 let device = devices[deviceKey]; | ||||
|  | ||||
|                 //shape commands from macros | ||||
|                 let commands: { [item: string]: any } = {}; | ||||
|                 for (let macroKey in device.macros) { | ||||
|                     let macro: any = device.macros[macroKey]; | ||||
|                     commands[regularizeString(macro.name)] = { | ||||
|                         deviceKey: macro.deviceKey, | ||||
|                         macroKey: macro.key, | ||||
|                         roomKey: macro.roomKey, | ||||
|                     }; | ||||
|                 } | ||||
|  | ||||
|                 this.devices[`Neeo-${deviceKey}`] = { | ||||
|                     id: device.key, | ||||
|                     name: device.name, | ||||
|                     log: this.log, | ||||
|                     hubType: "Neeo", | ||||
|                     commands: commands, | ||||
|                     on: false, | ||||
|                     supportsCommand(commandName: string): boolean { | ||||
|                         let command = this.commands[commandName]; | ||||
|                         return (command) ? true : false; | ||||
|                     }, | ||||
|  | ||||
|                     getCommand(commandName: string): string { | ||||
|                         return this.commands[commandName]; | ||||
|                     }, | ||||
|  | ||||
|                     async sendCommand(command: string): Promise<void> { | ||||
|                         if (this.supportsCommand(command)) { | ||||
|                             let macro: INeeoMacro = this.commands[command]; | ||||
|                             let url: string = `${baseUrl}/rooms/${macro.roomKey}/devices/${macro.deviceKey}/macros/${macro.macroKey}/trigger`; | ||||
|                             this.log(`Sending command ${url}`); | ||||
|                             fetch(`${baseUrl}/rooms/${macro.roomKey}/devices/${macro.deviceKey}/macros/${macro.macroKey}/trigger`); | ||||
|                         } | ||||
|                     }, | ||||
|  | ||||
|                     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; | ||||
|                         } | ||||
|                     }, | ||||
|                     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; | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default HubDataProvider; | ||||
							
								
								
									
										16
									
								
								src/DataProviders/IDevice.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/DataProviders/IDevice.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import { hub } from ".."; | ||||
|  | ||||
| export interface IDevice { | ||||
|     id: string, | ||||
|     name: string, | ||||
|     harmony?: any, | ||||
|     log: any, | ||||
|     hubType: hub | ||||
|     supportsCommand(commandName: string): boolean, | ||||
|     getCommand(commandName: string): string, | ||||
|     sendCommand(command: string): void, | ||||
|     powerOn(): Promise<void>, | ||||
|     powerOff(): Promise<void>, | ||||
|     commands: { [name: string]: any }; | ||||
|     on: boolean; | ||||
| } | ||||
							
								
								
									
										69
									
								
								src/Models/Activity.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								src/Models/Activity.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| import { DeviceSetupItem } from './DeviceSetupItem'; | ||||
| /** | ||||
|  * Input properties. | ||||
|  */ | ||||
| export interface IActivityProps { | ||||
|     deviceList: Array<DeviceSetupItem>, | ||||
|     controlDeviceId: string, | ||||
|     volumeDeviceId: string, | ||||
|     outputDeviceId: string, | ||||
|     displayName: string | ||||
|     useMatrix: boolean, | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Data model class to hold activity related information. | ||||
|  */ | ||||
| export class Activity { | ||||
|     private _volumeDeviceId: string = ""; | ||||
|     private _outputDeviceId: string = ""; | ||||
|     private _controlDeviceId: string = ""; | ||||
|     private _displayName: string = ""; | ||||
|     private _deviceSetupItems: Array<DeviceSetupItem>; | ||||
|     private _useMatrix: boolean = false; | ||||
|  | ||||
|     constructor(props: IActivityProps) { | ||||
|         this._controlDeviceId = props.controlDeviceId; | ||||
|         this._outputDeviceId = props.outputDeviceId; | ||||
|         this._volumeDeviceId = props.volumeDeviceId; | ||||
|         this._displayName = props.displayName; | ||||
|         this._deviceSetupItems = props.deviceList; | ||||
|         this._useMatrix = props.useMatrix | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * The device associated with main control. | ||||
|      */ | ||||
|     public get controlDeviceId(): string { | ||||
|         return this._controlDeviceId; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The device associated with the volume control. | ||||
|      */ | ||||
|     public get volumeDeviceId(): string { | ||||
|         return this._volumeDeviceId | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The device associated with output. | ||||
|      */ | ||||
|     public get outputDeviceId(): string { | ||||
|         return this._outputDeviceId; | ||||
|     }; | ||||
|  | ||||
|     /** | ||||
|      * The display name of the activity. | ||||
|      */ | ||||
|     public get displayName(): string { | ||||
|         return this._displayName; | ||||
|     } | ||||
|  | ||||
|     public get deviceSetupItems(): Array<DeviceSetupItem> { | ||||
|         return this._deviceSetupItems | ||||
|     } | ||||
|  | ||||
|     public get useMatrix(): boolean { | ||||
|         return this._useMatrix; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										33
									
								
								src/Models/DeviceSetupItem.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/Models/DeviceSetupItem.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| import { hub } from ".."; | ||||
|  | ||||
| export interface IDeviceSetupItemProps { | ||||
|     deviceName: string, | ||||
|     input: string, | ||||
|     hub: hub | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Data model to hold device setup items. | ||||
|  */ | ||||
| export class DeviceSetupItem { | ||||
|     private _deviceId: string = ""; | ||||
|     private _input: string = ""; | ||||
|     private _hubType: hub; | ||||
|     constructor(props: IDeviceSetupItemProps) { | ||||
|         this._deviceId = props.deviceName; | ||||
|         this._input = props.input; | ||||
|         this._hubType = props.hub; | ||||
|     } | ||||
|  | ||||
|     public get deviceName() { | ||||
|         return this._deviceId; | ||||
|     } | ||||
|  | ||||
|     public get input() { | ||||
|         return this._input; | ||||
|     } | ||||
|  | ||||
|     public get hubType() { | ||||
|         return this._hubType; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										51
									
								
								src/Models/Matrix.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/Models/Matrix.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| import { hub } from ".."; | ||||
|  | ||||
| export interface IMatrixProps { | ||||
|     inputs: Array<Input>, | ||||
|     outputs: Array<Output>, | ||||
|     deviceName: string, | ||||
|     hubType: hub | ||||
| } | ||||
|  | ||||
| export interface Input { | ||||
|     inputNumber: string, | ||||
|     inputDevice: string, | ||||
| } | ||||
|  | ||||
| export interface Output { | ||||
|     outputLetter: string, | ||||
|     outputDevice: string, | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * Data model to hold matrix information. | ||||
|  */ | ||||
| export class Matrix { | ||||
|     private _inputs: Array<Input> = []; | ||||
|     private _outputs: Array<Output> = []; | ||||
|     private _deviceName: string; | ||||
|     private _hubType: hub = "Harmony"; | ||||
|  | ||||
|     constructor(props: IMatrixProps) { | ||||
|         this._inputs = props.inputs; | ||||
|         this._outputs = props.outputs; | ||||
|         this._deviceName = props.deviceName; | ||||
|         this._hubType = props.hubType; | ||||
|     } | ||||
|  | ||||
|     public get inputs(): Array<Input> { | ||||
|         return this._inputs | ||||
|     } | ||||
|  | ||||
|     public get outputs(): Array<Output> { | ||||
|         return this._outputs; | ||||
|     } | ||||
|  | ||||
|     public get deviceName(): string { | ||||
|         return this._deviceName; | ||||
|     } | ||||
|  | ||||
|     public get hubType(): hub { | ||||
|         return this._hubType; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										456
									
								
								src/Types/HAPNodeJS.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										456
									
								
								src/Types/HAPNodeJS.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,456 @@ | ||||
| declare namespace HAPNodeJS { | ||||
|  | ||||
|     export interface uuid { | ||||
|         generate(data: string): string; | ||||
|         isValid(UUID: string): boolean; | ||||
|         unparse(bug: string, offset: number): string; | ||||
|     } | ||||
|  | ||||
|     type EventService = "characteristic-change" | "service-configurationChange" | ||||
|  | ||||
|     export interface IEventEmitterAccessory { | ||||
|         addListener(event: EventService, listener: Function): this; | ||||
|         on(event: EventService, listener: Function): this; | ||||
|         once(event: EventService, listener: Function): this; | ||||
|         removeListener(event: EventService, listener: Function): this; | ||||
|         removeAllListeners(event?: EventService): this; | ||||
|         setMaxListeners(n: number): this; | ||||
|         getMaxListeners(): number; | ||||
|         listeners(event: EventService): Function[]; | ||||
|         emit(event: EventService, ...args: any[]): boolean; | ||||
|         listenerCount(type: string): number; | ||||
|     } | ||||
|  | ||||
|     export interface Service extends IEventEmitterAccessory { | ||||
|         new(displayName: string, UUID: string, subtype: string): Service; | ||||
|  | ||||
|         displayName: string; | ||||
|         UUID: string; | ||||
|         subtype: string; | ||||
|         iid: string; | ||||
|         characteristics: Characteristic[]; | ||||
|         optionalCharacteristics: Characteristic[]; | ||||
|  | ||||
|         addCharacteristic(characteristic: Characteristic | Function): Characteristic; | ||||
|         removeCharacteristic(characteristic: Characteristic): void; | ||||
|         getCharacteristic(name: string | Function): Characteristic; | ||||
|         testCharacteristic(name: string | Function): boolean; | ||||
|         setCharacteristic(name: string | Function, value: CharacteristicValue): Service; | ||||
|         updateCharacteristic(name: string | Function, value: CharacteristicValue): Service; | ||||
|         addOptionalCharacteristic(characteristic: Characteristic | Function): void; | ||||
|         getCharacteristicByIID(iid: string): Characteristic; | ||||
|  | ||||
|         toHAP(opt: any): JSON; | ||||
|  | ||||
|         AccessoryInformation: PredefinedService; | ||||
|         AirPurifier: PredefinedService; | ||||
|         AirQualitySensor: PredefinedService; | ||||
|         BatteryService: PredefinedService; | ||||
|         BridgeConfiguration: PredefinedService; | ||||
|         BridgingState: PredefinedService; | ||||
|         CameraControl: PredefinedService; | ||||
|         CameraRTPStreamManagement: PredefinedService; | ||||
|         CarbonDioxideSensor: PredefinedService; | ||||
|         CarbonMonoxideSensor: PredefinedService; | ||||
|         ContactSensor: PredefinedService; | ||||
|         Door: PredefinedService; | ||||
|         Doorbell: PredefinedService; | ||||
|         Fan: PredefinedService; | ||||
|         Fanv2: PredefinedService; | ||||
|         Faucet: PredefinedService; | ||||
|         FilterMaintenance: PredefinedService; | ||||
|         GarageDoorOpener: PredefinedService; | ||||
|         HeaterCooler: PredefinedService; | ||||
|         HumidifierDehumidifier: PredefinedService; | ||||
|         HumiditySensor: PredefinedService; | ||||
|         InputSource: PredefinedService; | ||||
|         IrrigationSystem: PredefinedService; | ||||
|         LeakSensor: PredefinedService; | ||||
|         LightSensor: PredefinedService; | ||||
|         Lightbulb: PredefinedService; | ||||
|         LockManagement: PredefinedService; | ||||
|         LockMechanism: PredefinedService; | ||||
|         Microphone: PredefinedService; | ||||
|         MotionSensor: PredefinedService; | ||||
|         OccupancySensor: PredefinedService; | ||||
|         Outlet: PredefinedService; | ||||
|         Pairing: PredefinedService; | ||||
|         ProtocolInformation: PredefinedService; | ||||
|         Relay: PredefinedService; | ||||
|         SecuritySystem: PredefinedService; | ||||
|         ServiceLabel: PredefinedService; | ||||
|         Slat: PredefinedService; | ||||
|         SmokeSensor: PredefinedService; | ||||
|         Speaker: PredefinedService; | ||||
|         StatefulProgrammableSwitch: PredefinedService; | ||||
|         StatelessProgrammableSwitch: PredefinedService; | ||||
|         Switch: PredefinedService; | ||||
|         Television: PredefinedService; | ||||
|         TelevisionSpeaker: PredefinedService; | ||||
|         TemperatureSensor: PredefinedService; | ||||
|         Thermostat: PredefinedService; | ||||
|         TimeInformation: PredefinedService; | ||||
|         TunneledBTLEAccessoryService: PredefinedService; | ||||
|         Valve: PredefinedService; | ||||
|         Window: PredefinedService; | ||||
|         WindowCovering: PredefinedService; | ||||
|     } | ||||
|  | ||||
|     export interface PredefinedService { | ||||
|         new(displayName: string, subtype: string): Service; | ||||
|     } | ||||
|  | ||||
|     export interface CameraSource { | ||||
|  | ||||
|     } | ||||
|  | ||||
|     type EventAccessory = "service-configurationChange" | "service-characteristic-change" | "identify" | ||||
|  | ||||
|     export interface IEventEmitterAccessory { | ||||
|         addListener(event: EventAccessory, listener: Function): this; | ||||
|         on(event: EventAccessory, listener: Function): this; | ||||
|         once(event: EventAccessory, listener: Function): this; | ||||
|         removeListener(event: EventAccessory, listener: Function): this; | ||||
|         removeAllListeners(event?: EventAccessory): this; | ||||
|         setMaxListeners(n: number): this; | ||||
|         getMaxListeners(): number; | ||||
|         listeners(event: EventAccessory): Function[]; | ||||
|         emit(event: EventAccessory, ...args: any[]): boolean; | ||||
|         listenerCount(type: string): number; | ||||
|     } | ||||
|  | ||||
|     export interface CharacteristicProps { | ||||
|         format: Characteristic.Formats; | ||||
|         unit: Characteristic.Units, | ||||
|         minValue: number, | ||||
|         maxValue: number, | ||||
|         minStep: number, | ||||
|         perms: Characteristic.Perms[] | ||||
|     } | ||||
|  | ||||
|     type EventCharacteristic = "get" | "set" | ||||
|     type CharacteristicValue = boolean | string | number | ||||
|  | ||||
|     export type CharacteristicGetCallback<T = CharacteristicValue> = (error: Error | null, value: T) => void | ||||
|     export type CharacteristicSetCallback = (error?: Error | null) => void | ||||
|     export type CharacteristicCallback = CharacteristicGetCallback | CharacteristicSetCallback | ||||
|  | ||||
|     export interface IEventEmitterCharacteristic { | ||||
|         addListener(event: EventCharacteristic, listener: CharacteristicCallback): this; | ||||
|         on(event: EventCharacteristic, listener: CharacteristicCallback): this; | ||||
|         once(event: EventCharacteristic, listener: CharacteristicCallback): this; | ||||
|         removeListener(event: EventCharacteristic, listener: CharacteristicCallback): this; | ||||
|         removeAllListeners(event?: EventCharacteristic): this; | ||||
|         setMaxListeners(n: number): this; | ||||
|         getMaxListeners(): number; | ||||
|         listeners(event: EventCharacteristic): CharacteristicCallback[]; | ||||
|         emit(event: EventCharacteristic, ...args: any[]): boolean; | ||||
|         listenerCount(type: string): number; | ||||
|     } | ||||
|  | ||||
|     export interface Characteristic extends IEventEmitterCharacteristic { | ||||
|         new(displayName: string, UUID: string, props?: CharacteristicProps): Characteristic; | ||||
|  | ||||
|         Formats: typeof Characteristic.Formats; | ||||
|         Units: typeof Characteristic.Units; | ||||
|         Perms: typeof Characteristic.Perms; | ||||
|  | ||||
|         setProps(props: CharacteristicProps): Characteristic | ||||
|         getValue(callback?: CharacteristicGetCallback, context?: any, connectionID?: string): void; | ||||
|         setValue(newValue: CharacteristicValue, callback?: CharacteristicSetCallback, context?: any, connectionID?: string): Characteristic; | ||||
|         updateValue(newValue: CharacteristicValue, callback?: () => void, context?: any): Characteristic; | ||||
|         getDefaultValue(): CharacteristicValue; | ||||
|         toHAP(opt: any): JSON; | ||||
|  | ||||
|         AccessoryFlags: Characteristic; | ||||
|         AccessoryIdentifier: Characteristic; | ||||
|         Active: Characteristic; | ||||
|         ActiveIdentifier: Characteristic; | ||||
|         AdministratorOnlyAccess: Characteristic; | ||||
|         AirParticulateDensity: Characteristic; | ||||
|         AirParticulateSize: Characteristic; | ||||
|         AirQuality: Characteristic; | ||||
|         AppMatchingIdentifier: Characteristic; | ||||
|         AudioFeedback: Characteristic; | ||||
|         BatteryLevel: Characteristic; | ||||
|         Brightness: Characteristic; | ||||
|         CarbonDioxideDetected: Characteristic; | ||||
|         CarbonDioxideLevel: Characteristic; | ||||
|         CarbonDioxidePeakLevel: Characteristic; | ||||
|         CarbonMonoxideDetected: Characteristic; | ||||
|         CarbonMonoxideLevel: Characteristic; | ||||
|         CarbonMonoxidePeakLevel: Characteristic; | ||||
|         Category: Characteristic; | ||||
|         ChargingState: Characteristic; | ||||
|         ClosedCaptions: Characteristic; | ||||
|         ColorTemperature: Characteristic; | ||||
|         ConfigureBridgedAccessory: Characteristic; | ||||
|         ConfigureBridgedAccessoryStatus: Characteristic; | ||||
|         ConfiguredName: Characteristic; | ||||
|         ContactSensorState: Characteristic; | ||||
|         CoolingThresholdTemperature: Characteristic; | ||||
|         CurrentAirPurifierState: Characteristic; | ||||
|         CurrentAmbientLightLevel: Characteristic; | ||||
|         CurrentDoorState: Characteristic; | ||||
|         CurrentFanState: Characteristic; | ||||
|         CurrentHeaterCoolerState: Characteristic; | ||||
|         CurrentHeatingCoolingState: Characteristic; | ||||
|         CurrentHorizontalTiltAngle: Characteristic; | ||||
|         CurrentHumidifierDehumidifierState: Characteristic; | ||||
|         CurrentMediaState: Characteristic; | ||||
|         CurrentPosition: Characteristic; | ||||
|         CurrentRelativeHumidity: Characteristic; | ||||
|         CurrentSlatState: Characteristic; | ||||
|         CurrentTemperature: Characteristic; | ||||
|         CurrentTiltAngle: Characteristic; | ||||
|         CurrentTime: Characteristic; | ||||
|         CurrentVerticalTiltAngle: Characteristic; | ||||
|         CurrentVisibilityState: Characteristic; | ||||
|         DayoftheWeek: Characteristic; | ||||
|         DigitalZoom: Characteristic; | ||||
|         DiscoverBridgedAccessories: Characteristic; | ||||
|         DiscoveredBridgedAccessories: Characteristic; | ||||
|         DisplayOrder: Characteristic; | ||||
|         FilterChangeIndication: Characteristic; | ||||
|         FilterLifeLevel: Characteristic; | ||||
|         FirmwareRevision: Characteristic; | ||||
|         HardwareRevision: Characteristic; | ||||
|         HeatingThresholdTemperature: Characteristic; | ||||
|         HoldPosition: Characteristic; | ||||
|         Hue: Characteristic; | ||||
|         Identifier: Characteristic; | ||||
|         Identify: Characteristic; | ||||
|         ImageMirroring: Characteristic; | ||||
|         ImageRotation: Characteristic; | ||||
|         InUse: Characteristic; | ||||
|         InputDeviceType: Characteristic; | ||||
|         InputSourceType: Characteristic; | ||||
|         IsConfigured: Characteristic; | ||||
|         LeakDetected: Characteristic; | ||||
|         LinkQuality: Characteristic; | ||||
|         LockControlPoint: Characteristic; | ||||
|         LockCurrentState: Characteristic; | ||||
|         LockLastKnownAction: Characteristic; | ||||
|         LockManagementAutoSecurityTimeout: Characteristic; | ||||
|         LockPhysicalControls: Characteristic; | ||||
|         LockTargetState: Characteristic; | ||||
|         Logs: Characteristic; | ||||
|         Manufacturer: Characteristic; | ||||
|         Model: Characteristic; | ||||
|         MotionDetected: Characteristic; | ||||
|         Mute: Characteristic; | ||||
|         Name: Characteristic; | ||||
|         NightVision: Characteristic; | ||||
|         NitrogenDioxideDensity: Characteristic; | ||||
|         ObstructionDetected: Characteristic; | ||||
|         OccupancyDetected: Characteristic; | ||||
|         On: Characteristic; | ||||
|         OpticalZoom: Characteristic; | ||||
|         OutletInUse: Characteristic; | ||||
|         OzoneDensity: Characteristic; | ||||
|         PM10Density: Characteristic; | ||||
|         PM2_5Density: Characteristic; | ||||
|         PairSetup: Characteristic; | ||||
|         PairVerify: Characteristic; | ||||
|         PairingFeatures: Characteristic; | ||||
|         PairingPairings: Characteristic; | ||||
|         PictureMode: Characteristic; | ||||
|         PositionState: Characteristic; | ||||
|         PowerModeSelection: Characteristic; | ||||
|         ProgramMode: Characteristic; | ||||
|         ProgrammableSwitchEvent: Characteristic; | ||||
|         ProgrammableSwitchOutputState: Characteristic; | ||||
|         Reachable: Characteristic; | ||||
|         RelativeHumidityDehumidifierThreshold: Characteristic; | ||||
|         RelativeHumidityHumidifierThreshold: Characteristic; | ||||
|         RelayControlPoint: Characteristic; | ||||
|         RelayEnabled: Characteristic; | ||||
|         RelayState: Characteristic; | ||||
|         RemainingDuration: Characteristic; | ||||
|         RemoteKey: Characteristic; | ||||
|         ResetFilterIndication: Characteristic; | ||||
|         RotationDirection: Characteristic; | ||||
|         RotationSpeed: Characteristic; | ||||
|         Saturation: Characteristic; | ||||
|         SecuritySystemAlarmType: Characteristic; | ||||
|         SecuritySystemCurrentState: Characteristic; | ||||
|         SecuritySystemTargetState: Characteristic; | ||||
|         SelectedRTPStreamConfiguration: Characteristic; | ||||
|         SerialNumber: Characteristic; | ||||
|         ServiceLabelIndex: Characteristic; | ||||
|         ServiceLabelNamespace: Characteristic; | ||||
|         SetDuration: Characteristic; | ||||
|         SetupEndpoints: Characteristic; | ||||
|         SlatType: Characteristic; | ||||
|         SleepDiscoveryMode: Characteristic; | ||||
|         SmokeDetected: Characteristic; | ||||
|         SoftwareRevision: Characteristic; | ||||
|         StatusActive: Characteristic; | ||||
|         StatusFault: Characteristic; | ||||
|         StatusJammed: Characteristic; | ||||
|         StatusLowBattery: Characteristic; | ||||
|         StatusTampered: Characteristic; | ||||
|         StreamingStatus: Characteristic; | ||||
|         SulphurDioxideDensity: Characteristic; | ||||
|         SupportedAudioStreamConfiguration: Characteristic; | ||||
|         SupportedRTPConfiguration: Characteristic; | ||||
|         SupportedVideoStreamConfiguration: Characteristic; | ||||
|         SwingMode: Characteristic; | ||||
|         TargetAirPurifierState: Characteristic; | ||||
|         TargetAirQuality: Characteristic; | ||||
|         TargetDoorState: Characteristic; | ||||
|         TargetFanState: Characteristic; | ||||
|         TargetHeaterCoolerState: Characteristic; | ||||
|         TargetHeatingCoolingState: Characteristic; | ||||
|         TargetHorizontalTiltAngle: Characteristic; | ||||
|         TargetHumidifierDehumidifierState: Characteristic; | ||||
|         TargetMediaState: Characteristic; | ||||
|         TargetPosition: Characteristic; | ||||
|         TargetRelativeHumidity: Characteristic; | ||||
|         TargetSlatState: Characteristic; | ||||
|         TargetTemperature: Characteristic; | ||||
|         TargetTiltAngle: Characteristic; | ||||
|         TargetVerticalTiltAngle: Characteristic; | ||||
|         TargetVisibilityState: Characteristic; | ||||
|         TemperatureDisplayUnits: Characteristic; | ||||
|         TimeUpdate: Characteristic; | ||||
|         TunnelConnectionTimeout: Characteristic; | ||||
|         TunneledAccessoryAdvertising: Characteristic; | ||||
|         TunneledAccessoryConnected: Characteristic; | ||||
|         TunneledAccessoryStateNumber: Characteristic; | ||||
|         VOCDensity: Characteristic; | ||||
|         ValveType: Characteristic; | ||||
|         Version: Characteristic; | ||||
|         Volume: Characteristic; | ||||
|         VolumeControlType: Characteristic; | ||||
|         VolumeSelector: Characteristic; | ||||
|         WaterLevel: Characteristic; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     module Characteristic { | ||||
|         export enum Formats { | ||||
|             BOOL, | ||||
|             INT, | ||||
|             FLOAT, | ||||
|             STRING, | ||||
|             ARRAY, // unconfirmed | ||||
|             DICTIONARY, // unconfirmed | ||||
|             UINT8, | ||||
|             UINT16, | ||||
|             UINT32, | ||||
|             UINT64, | ||||
|             DATA, // unconfirmed | ||||
|             TLV8 | ||||
|         } | ||||
|  | ||||
|         export enum Units { | ||||
|             // HomeKit only defines Celsius, for Fahrenheit, it requires iOS app to do the conversion. | ||||
|             CELSIUS, | ||||
|             PERCENTAGE, | ||||
|             ARC_DEGREE, | ||||
|             LUX, | ||||
|             SECONDS | ||||
|         } | ||||
|  | ||||
|         export enum Perms { | ||||
|             READ, | ||||
|             WRITE, | ||||
|             NOTIFY, | ||||
|             HIDDEN | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     export interface PublishInfo { | ||||
|         port: number; | ||||
|         username: string; | ||||
|         pincode: string; | ||||
|         category: number; | ||||
|     } | ||||
|  | ||||
|     export interface Accessory extends IEventEmitterAccessory { | ||||
|         new(displayName: string, UUID: string): Accessory; | ||||
|         displayName: string; | ||||
|         username: string; | ||||
|         pincode: string; | ||||
|         UUID: string; | ||||
|         aid: string; | ||||
|         bridged: boolean; | ||||
|         bridgedAccessories: Accessory[]; | ||||
|         reachable: boolean; | ||||
|         category: Accessory.Categories; | ||||
|         services: Service[]; | ||||
|         cameraSource: CameraSource; | ||||
|         Categories: typeof Accessory.Categories | ||||
|         addService(service: Service | Function): Service; | ||||
|         removeService(service: Service): void; | ||||
|         getService(name: string | Function): Service; | ||||
|         updateReachability(reachable: boolean): void; | ||||
|         addBridgedAccessory(accessory: Accessory, deferUpdate: boolean): Accessory; | ||||
|         addBridgedAccessories(accessories: Accessory[]): void | ||||
|         removeBridgedAccessory(accessory: Accessory, deferUpdate: boolean): void; | ||||
|         removeBridgedAccessories(accessories: Accessory[]): void; | ||||
|         getCharacteristicByIID(iid: string): Characteristic; | ||||
|         getBridgedAccessoryByAID(aid: string): Accessory; | ||||
|         findCharacteristic(aid: string, iid: string): Accessory; | ||||
|         configureCameraSource(cameraSource: CameraSource): void; | ||||
|         toHAP(opt: any): JSON; | ||||
|         publish(info: PublishInfo, allowInsecureRequest: boolean): void; | ||||
|         destroy(): void; | ||||
|         setupURI(): string; | ||||
|     } | ||||
|  | ||||
|     module Accessory { | ||||
|         export enum Categories { | ||||
|             OTHER = 1, | ||||
|             BRIDGE = 2, | ||||
|             FAN = 3, | ||||
|             GARAGE_DOOR_OPENER = 4, | ||||
|             LIGHTBULB = 5, | ||||
|             DOOR_LOCK = 6, | ||||
|             OUTLET = 7, | ||||
|             SWITCH = 8, | ||||
|             THERMOSTAT = 9, | ||||
|             SENSOR = 10, | ||||
|             ALARM_SYSTEM = 11, | ||||
|             SECURITY_SYSTEM = 11, | ||||
|             DOOR = 12, | ||||
|             WINDOW = 13, | ||||
|             WINDOW_COVERING = 14, | ||||
|             PROGRAMMABLE_SWITCH = 15, | ||||
|             RANGE_EXTENDER = 16, | ||||
|             CAMERA = 17, | ||||
|             IP_CAMERA = 17, | ||||
|             VIDEO_DOORBELL = 18, | ||||
|             AIR_PURIFIER = 19, | ||||
|             AIR_HEATER = 20, | ||||
|             AIR_CONDITIONER = 21, | ||||
|             AIR_HUMIDIFIER = 22, | ||||
|             AIR_DEHUMIDIFIER = 23, | ||||
|             APPLE_TV = 24, | ||||
|             SPEAKER = 26, | ||||
|             AIRPORT = 27, | ||||
|             SPRINKLER = 28, | ||||
|             FAUCET = 29, | ||||
|             SHOWER_HEAD = 30, | ||||
|             TELEVISION = 31, | ||||
|             TARGET_CONTROLLER = 32 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     export interface HAPNodeJS { | ||||
|         init(storagePath?: string): void, | ||||
|         uuid: uuid, | ||||
|         Accessory: Accessory, | ||||
|         Service: Service, | ||||
|         Characteristic: Characteristic | ||||
|     } | ||||
|  | ||||
|  | ||||
| } | ||||
|  | ||||
| declare var hapNodeJS: HAPNodeJS.HAPNodeJS; | ||||
|  | ||||
| declare module "hap-nodejs" { | ||||
|     export = hapNodeJS; | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/Util/Callbackify.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Util/Callbackify.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
|  | ||||
| /** | ||||
|  * Helper function to convert callbacks into promises | ||||
|  * @param func  | ||||
|  */ | ||||
| export default function callbackify(func: (...args: any[]) => Promise<any>): Function { | ||||
|     return (...args: any[]) => { | ||||
|         const onlyArgs: any[] = []; | ||||
|         let maybeCallback: Function | null = null; | ||||
|  | ||||
|         for (const arg of args) { | ||||
|             if (typeof arg === 'function') { | ||||
|                 maybeCallback = arg; | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             onlyArgs.push(arg); | ||||
|         } | ||||
|  | ||||
|         if (!maybeCallback) { | ||||
|             throw new Error("Missing callback parameter!"); | ||||
|         } | ||||
|  | ||||
|         const callback = maybeCallback; | ||||
|  | ||||
|         func(...onlyArgs) | ||||
|             .then((data: any) => callback(null, data)) | ||||
|             .catch((err: any) => callback(err)) | ||||
|     } | ||||
| } | ||||
| @@ -1,294 +0,0 @@ | ||||
| import { PlatformAccessory, Service } from "homebridge"; | ||||
| import { IActivity } from "../models/config"; | ||||
| import { Platform } from "../platform"; | ||||
| import { ActivityService } from "../services/activityService"; | ||||
| import { CommandService } from "../services/commandService"; | ||||
| import { VolumeService } from "../services/volumeService"; | ||||
| import callbackify from "../util/callbackify"; | ||||
|  | ||||
| /** | ||||
|  * Enum describing remote key presses from homebridge. | ||||
|  */ | ||||
| export enum RemoteKey { | ||||
|   REWIND = 0, | ||||
|   FAST_FORWARD = 1, | ||||
|   NEXT_TRACK = 2, | ||||
|   PREVIOUS_TRACK = 3, | ||||
|   ARROW_UP = 4, | ||||
|   ARROW_DOWN = 5, | ||||
|   ARROW_LEFT = 6, | ||||
|   ARROW_RIGHT = 7, | ||||
|   SELECT = 8, | ||||
|   BACK = 9, | ||||
|   EXIT = 10, | ||||
|   PLAY_PAUSE = 11, | ||||
|   INFORMATION = 15, | ||||
| } | ||||
|  | ||||
| export class ControlUnit { | ||||
|   constructor( | ||||
|     private readonly _platform: Platform, | ||||
|     private readonly _accessory: PlatformAccessory, | ||||
|     private _activityService: ActivityService, | ||||
|     private _commandService: CommandService, | ||||
|     private _volumeService: VolumeService, | ||||
|     private _activities: Array<IActivity> | ||||
|   ) { | ||||
|     this._accessory | ||||
|       .getService(this._platform.Service.AccessoryInformation)! | ||||
|       .setCharacteristic( | ||||
|         this._platform.Characteristic.Manufacturer, | ||||
|         "Brandon Watson" | ||||
|       ) | ||||
|       .setCharacteristic( | ||||
|         this._platform.Characteristic.Model, | ||||
|         "Matrix Output Television" | ||||
|       ) | ||||
|       .setCharacteristic( | ||||
|         this._platform.Characteristic.SerialNumber, | ||||
|         "123-456-789" | ||||
|       ); | ||||
|  | ||||
|     const televisionUUID = this._platform.api.hap.uuid.generate( | ||||
|       `${this._accessory.displayName} Television` | ||||
|     ); | ||||
|     //Init services | ||||
|     const televisionService = | ||||
|       this._accessory.getService(this._platform.Service.Television) || | ||||
|       this._accessory.addService( | ||||
|         this._platform.Service.Television, | ||||
|         "Television", | ||||
|         televisionUUID | ||||
|       ); | ||||
|     this.configureTvService(televisionService); | ||||
|  | ||||
|     const televisionSpeakerUUID = this._platform.api.hap.uuid.generate( | ||||
|       `${this._accessory.displayName} Television Speaker` | ||||
|     ); | ||||
|     const televisionSpeakerService = | ||||
|       this._accessory.getService(this._platform.Service.TelevisionSpeaker) || | ||||
|       this._accessory.addService( | ||||
|         this._platform.Service.TelevisionSpeaker, | ||||
|         "Television Speaker", | ||||
|         televisionSpeakerUUID | ||||
|       ); | ||||
|     this.configureTvSpeakerService(televisionService, televisionSpeakerService); | ||||
|  | ||||
|     this.configureInputSourceService(televisionService); | ||||
|   } | ||||
|  | ||||
|   /************* | ||||
|    * | ||||
|    * Tv Service | ||||
|    * | ||||
|    *************/ | ||||
|  | ||||
|   /** | ||||
|    * Configure television service | ||||
|    */ | ||||
|   private configureTvService(televisionService: Service): void { | ||||
|     televisionService.setCharacteristic( | ||||
|       this._platform.Characteristic.ConfiguredName, | ||||
|       this._accessory.displayName | ||||
|     ); | ||||
|     //@ts-ignore | ||||
|     televisionService.setCharacteristic( | ||||
|       this._platform.Characteristic.SleepDiscoveryMode, | ||||
|       this._platform.Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE | ||||
|     ); | ||||
|     televisionService.setCharacteristic( | ||||
|       this._platform.Characteristic.ActiveIdentifier, | ||||
|       1 | ||||
|     ); | ||||
|     televisionService.setCharacteristic( | ||||
|       this._platform.Characteristic.Active, | ||||
|       false | ||||
|     ); | ||||
|  | ||||
|     //setup listeners | ||||
|     televisionService | ||||
|       .getCharacteristic(this._platform.Characteristic.Active) | ||||
|       //@ts-ignore | ||||
|       .on("set", callbackify(this.onSetAccessoryActive)) | ||||
|       //@ts-ignore | ||||
|       .on("get", callbackify(this.onGetAccessoryActive)); | ||||
|  | ||||
|     //Set remote characteristics if is external | ||||
|     televisionService | ||||
|       .getCharacteristic(this._platform.Characteristic.RemoteKey) | ||||
|       //@ts-ignore | ||||
|       .on("set", callbackify(this.onSetRemoteKey)); | ||||
|  | ||||
|     televisionService | ||||
|       .getCharacteristic(this._platform.Characteristic.ActiveIdentifier) | ||||
|       //@ts-ignore | ||||
|       .on("set", callbackify(this.onSetActiveIdentifier)) | ||||
|       //@ts-ignore | ||||
|       .on("get", callbackify(this.onGetActiveIdentifier)); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Event handler for SET active characteristic | ||||
|    */ | ||||
|   private onSetAccessoryActive = async (value: any) => { | ||||
|     switch (value) { | ||||
|       case 0: | ||||
|         this._activityService.stopCurrentActivity(this._accessory.displayName); | ||||
|         break; | ||||
|       //Turn on with first activity | ||||
|       case 1: | ||||
|         if (this._activityService.getIsActive(this._accessory.displayName)) { | ||||
|           break; | ||||
|         } | ||||
|         this._activityService.startLastOrDefaultActivity( | ||||
|           this._accessory.displayName, | ||||
|           this._activities[0] | ||||
|         ); | ||||
|         break; | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * Event handler for GET active characteristic | ||||
|    */ | ||||
|   private onGetAccessoryActive = async () => { | ||||
|     //@ts-ignore | ||||
|     return this._activityService.getCurrentActivity(this._accessory.displayName) | ||||
|       ?.DisplayName | ||||
|       ? this._platform.Characteristic.Active.ACTIVE | ||||
|       : this._platform.Characteristic.Active.INACTIVE; | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * Event handler for SET remote key | ||||
|    */ | ||||
|   private onSetRemoteKey = async (key: any) => { | ||||
|     this._commandService.sendKeyPress(this._accessory.displayName, key); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * Event handler for SET active identifier characteristic | ||||
|    */ | ||||
|   private onSetActiveIdentifier = async (identifier: any) => { | ||||
|     this._activityService.startActivity( | ||||
|       this._accessory.displayName, | ||||
|       this._activities[identifier] | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * Event handler for GET active identifier characteristic | ||||
|    */ | ||||
|   private onGetActiveIdentifier = async () => { | ||||
|     let currentActivity: IActivity = this._activityService.getCurrentActivity( | ||||
|       this._accessory.displayName | ||||
|     )!; | ||||
|     let identifier: number = 0; | ||||
|     if (currentActivity) { | ||||
|       identifier = this._activities.findIndex( | ||||
|         (e) => e.DisplayName === currentActivity.DisplayName | ||||
|       ); | ||||
|     } | ||||
|     return identifier; | ||||
|   }; | ||||
|  | ||||
|   /****************** | ||||
|    * | ||||
|    * Speaker Service | ||||
|    * | ||||
|    *****************/ | ||||
|  | ||||
|   /** | ||||
|    * Configure Speaker Service | ||||
|    */ | ||||
|   private configureTvSpeakerService( | ||||
|     televisionService: Service, | ||||
|     televisionSpeakerService: Service | ||||
|   ): void { | ||||
|     televisionSpeakerService.setCharacteristic( | ||||
|       this._platform.Characteristic.Name, | ||||
|       this._accessory.displayName | ||||
|     ); | ||||
|     //@ts-ignore | ||||
|     televisionSpeakerService.setCharacteristic( | ||||
|       this._platform.Characteristic.Active, | ||||
|       this._platform.Characteristic.Active.ACTIVE | ||||
|     ); | ||||
|     //@ts-ignore | ||||
|     televisionSpeakerService.setCharacteristic( | ||||
|       this._platform.Characteristic.VolumeControlType, | ||||
|       this._platform.Characteristic.VolumeControlType.ABSOLUTE | ||||
|     ); | ||||
|     televisionSpeakerService.subtype = this._accessory.displayName + "Volume"; | ||||
|     if (televisionService) { | ||||
|       //@ts-ignore | ||||
|       televisionService.addLinkedService(televisionSpeakerService); | ||||
|     } | ||||
|  | ||||
|     //Setup listeners | ||||
|     televisionSpeakerService | ||||
|       .getCharacteristic(this._platform.Characteristic.VolumeSelector) | ||||
|       //@ts-ignore | ||||
|       .on("set", callbackify(this.onSetVolumeSelector)); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Event handler for SET volume characteristic | ||||
|    */ | ||||
|   private onSetVolumeSelector = async (value: any) => { | ||||
|     switch (value) { | ||||
|       case 0: | ||||
|         this._volumeService.volumeUp(this._accessory.displayName); | ||||
|         break; | ||||
|       case 1: | ||||
|         this._volumeService.volumeDown(this._accessory.displayName); | ||||
|         break; | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   /***************** | ||||
|    * | ||||
|    * Input services | ||||
|    * | ||||
|    *****************/ | ||||
|  | ||||
|   /** | ||||
|    * Configure input service | ||||
|    */ | ||||
|   private configureInputSourceService(televisionService: Service): void { | ||||
|     let inputs: Array<Service> = []; | ||||
|     this._activities.forEach((activity: IActivity, index: number) => { | ||||
|       const inputUUID = this._platform.api.hap.uuid.generate( | ||||
|         `${activity.DisplayName} Input` | ||||
|       ); | ||||
|       let inputService = | ||||
|         this._accessory.getService(activity.DisplayName) || | ||||
|         this._accessory.addService( | ||||
|           this._platform.Service.InputSource, | ||||
|           activity.DisplayName, | ||||
|           inputUUID | ||||
|         ); | ||||
|  | ||||
|       inputService | ||||
|         .setCharacteristic(this._platform.Characteristic.Identifier, index) | ||||
|         .setCharacteristic( | ||||
|           this._platform.Characteristic.ConfiguredName, | ||||
|           activity.DisplayName | ||||
|         ) | ||||
|         .setCharacteristic( | ||||
|           this._platform.Characteristic.IsConfigured, | ||||
|           //@ts-ignore | ||||
|           this._platform.Characteristic.IsConfigured.CONFIGURED | ||||
|         ) | ||||
|         //@ts-ignore | ||||
|         .setCharacteristic( | ||||
|           this._platform.Characteristic.InputSourceType, | ||||
|           this._platform.Characteristic.InputSourceType.HDMI | ||||
|         ); | ||||
|  | ||||
|       //@ts-ignore | ||||
|       televisionService!.addLinkedService(inputService); | ||||
|       inputs.push(inputService); | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| @@ -1,122 +0,0 @@ | ||||
| import { PlatformAccessory, Service } from "homebridge"; | ||||
| import { HarmonyDataProvider } from "../dataProviders/harmonyDataProvider"; | ||||
| import { IDeviceButton } from "../models/config"; | ||||
| import { HarmonyDevice } from "../models/harmonyDevice"; | ||||
| import { Platform } from "../platform"; | ||||
|  | ||||
| export class DeviceButton { | ||||
|   private _buttonState: boolean; | ||||
|   private _device!: HarmonyDevice; | ||||
|   private _switchService: Service; | ||||
|  | ||||
|   constructor( | ||||
|     private readonly _platform: Platform, | ||||
|     private readonly _accessory: PlatformAccessory, | ||||
|     private _dataProvider: HarmonyDataProvider, | ||||
|     private _deviceInfo: IDeviceButton | ||||
|   ) { | ||||
|     this._buttonState = false; | ||||
|  | ||||
|     if (this._deviceInfo.NumberOfKeyPresses && this._deviceInfo.IsStateful) { | ||||
|       throw new Error( | ||||
|         "A button cannot be stateful and be pressed more than once" | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     this._accessory | ||||
|       .getService(this._platform.Service.AccessoryInformation)! | ||||
|       .setCharacteristic( | ||||
|         this._platform.Characteristic.Manufacturer, | ||||
|         "Brandon Watson" | ||||
|       ) | ||||
|       .setCharacteristic(this._platform.Characteristic.Model, "Device 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) | ||||
|       //@ts-ignore | ||||
|       .on("set", this.onSwitchSet) | ||||
|       .updateValue(this._buttonState) | ||||
|       .on("get", this.onSwitchGet); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Handler for switch set event | ||||
|    * @param callback The callback function to call when complete | ||||
|    */ | ||||
|   private onSwitchSet = async ( | ||||
|     newState: boolean, | ||||
|     callback: (error?: Error | null | undefined) => void | ||||
|   ) => { | ||||
|     if (!this._deviceInfo.IsStateful && newState === this._buttonState) { | ||||
|       return callback(); | ||||
|     } | ||||
|  | ||||
|     //Get device command if we don't have it | ||||
|     if (!this._device) { | ||||
|       this._device = this._dataProvider.getDeviceByName( | ||||
|         this._deviceInfo.DeviceName | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     //Execute command | ||||
|     if (!this._device) { | ||||
|       return callback(); | ||||
|     } | ||||
|  | ||||
|     //change state if stateful | ||||
|     if (this._deviceInfo.IsStateful && this._buttonState != newState) { | ||||
|       this._buttonState = newState; | ||||
|       await this._dataProvider.sendCommand( | ||||
|         this._deviceInfo.ButtonName, | ||||
|         this._device | ||||
|       ); | ||||
|       return callback(); | ||||
|     } else if (!this._deviceInfo.IsStateful) { | ||||
|       //Send the number of configured key presses | ||||
|       for (let i = 0; i < this._deviceInfo.NumberOfKeyPresses; i++) { | ||||
|         await this._dataProvider.sendCommand( | ||||
|           this._deviceInfo.ButtonName, | ||||
|           this._device | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       this._switchService | ||||
|         .getCharacteristic(this._platform.Characteristic.On) | ||||
|         .updateValue(false); | ||||
|  | ||||
|       this._buttonState = false; | ||||
|       return callback(); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * Handler for switch get event | ||||
|    * @param callback The callback function to call when complete | ||||
|    */ | ||||
|   private onSwitchGet = ( | ||||
|     callback: (error: Error | null, value: boolean) => void | ||||
|   ) => { | ||||
|     //Only return state if button is stateful | ||||
|     if (this._deviceInfo.IsStateful) { | ||||
|       return callback(null, this._buttonState); | ||||
|     } else { | ||||
|       return callback(null, false); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| @@ -1,2 +0,0 @@ | ||||
| export { ControlUnit } from "./controlUnit"; | ||||
| export { DeviceButton } from "./deviceButton"; | ||||
| @@ -1,105 +0,0 @@ | ||||
| import { | ||||
|   CharacteristicGetCallback, | ||||
|   CharacteristicSetCallback, | ||||
|   CharacteristicValue, | ||||
|   PlatformAccessory, | ||||
|   Service, | ||||
| } from "homebridge"; | ||||
| import { HarmonyDataProvider } from "../dataProviders/harmonyDataProvider"; | ||||
| import { ISequence } from "../models/config/sequence"; | ||||
| 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.getDeviceByName(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 onSwitchSet = async ( | ||||
|     _value: CharacteristicValue, | ||||
|     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 this._dataProvider.sendCommand(step.DeviceCommand, device); | ||||
|       } 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,166 +0,0 @@ | ||||
| import { Logging } from "homebridge"; | ||||
| import { inject, injectable } from "tsyringe"; | ||||
| import { ICommand } from "../models"; | ||||
| import { IConfig } from "../models/config"; | ||||
| import { IDeviceConfig } from "../models/config/deviceConfig"; | ||||
| import { IHub } from "../models/config/hub"; | ||||
| import { HarmonyDevice } from "../models/harmonyDevice"; | ||||
| import { HarmonyHub } from "../models/harmonyHub"; | ||||
|  | ||||
| @injectable() | ||||
| export class HarmonyDataProvider { | ||||
|   private _hubs: { [hubName: string]: HarmonyHub } = {}; | ||||
|   private _deviceConfigByName: { [deviceName: string]: IDeviceConfig } = {}; | ||||
|  | ||||
|   constructor( | ||||
|     @inject("IConfig") private _config: IConfig, | ||||
|     @inject("log") private _log: Logging | ||||
|   ) { | ||||
|     _config.Devices.forEach((deviceConfig: IDeviceConfig) => { | ||||
|       this._deviceConfigByName[deviceConfig.Name] = deviceConfig; | ||||
|     }); | ||||
|  | ||||
|     this.connect(_config.Hubs); | ||||
|     this.emitInfo(); | ||||
|   } | ||||
|  | ||||
|   public async powerOnDevices(devices: Array<HarmonyDevice>): Promise<void> { | ||||
|     await Promise.all( | ||||
|       devices.map(async (device: HarmonyDevice) => { | ||||
|         if (device && device.name) { | ||||
|           if (!device.on) { | ||||
|             this._log.info(`Turning on device ${device.name}`); | ||||
|             await this.powerOnDevice(device); | ||||
|           } | ||||
|         } | ||||
|       }) | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   public async powerOffDevices(devices: Array<HarmonyDevice>) { | ||||
|     await Promise.all( | ||||
|       //Turn off devices | ||||
|       devices.map(async (device: HarmonyDevice) => { | ||||
|         if (device) { | ||||
|           if (device.on) { | ||||
|             this._log.info(`Turning off device ${device.name}`); | ||||
|             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. | ||||
|    * @param deviceName The device to retrieve. | ||||
|    */ | ||||
|   public getDeviceByName(deviceName: string): HarmonyDevice { | ||||
|     let device: HarmonyDevice | undefined; | ||||
|     try { | ||||
|       device = | ||||
|         this._hubs[this._deviceConfigByName[deviceName].Hub].getDeviceByName( | ||||
|           deviceName | ||||
|         ); | ||||
|     } catch (err) { | ||||
|       this._log.info(`Error retrieving device from hub: ${err}`); | ||||
|     } | ||||
|  | ||||
|     return device!; | ||||
|   } | ||||
|  | ||||
|   private connect = async (hubs: Array<IHub>) => { | ||||
|     let readyCount = 0; | ||||
|     await Promise.all( | ||||
|       hubs.map(async (hub: IHub): Promise<void> => { | ||||
|         const newHarmonyHub = new HarmonyHub(hub.Name, hub.Ip, this._log.info); | ||||
|         this._hubs[hub.Name] = newHarmonyHub; | ||||
|         newHarmonyHub.on("Ready", () => { | ||||
|           readyCount++; | ||||
|           if (readyCount === Object.keys(this._hubs).length) { | ||||
|             // this.emit("Ready"); | ||||
|           } | ||||
|         }); | ||||
|         await newHarmonyHub.initialize(); | ||||
|       }) | ||||
|     ); | ||||
|   }; | ||||
|  | ||||
|   private emitInfo(): void { | ||||
|     //Emit devices if requested | ||||
|     this._log.info("All hubs connected"); | ||||
|     if (this._config.EmitDevicesOnStartup) { | ||||
|       const hubs = this._hubs; | ||||
|       Object.values(hubs).forEach((hub: HarmonyHub) => { | ||||
|         const deviceDictionary = hub.devices; | ||||
|         this._log.info(`${hub.hubName}`); | ||||
|         Object.values(deviceDictionary).forEach((device: HarmonyDevice) => { | ||||
|           this._log.info(`    ${device.name} : ${device.id}`); | ||||
|           Object.keys(device.commands).forEach((command: string) => { | ||||
|             this._log.info(`        ${command}`); | ||||
|           }); | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   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; | ||||
|   } | ||||
| } | ||||
| @@ -1,43 +0,0 @@ | ||||
| import { injectable } from "tsyringe"; | ||||
| import { IActivityState } from "../models/activityState"; | ||||
| import { IActivity } from "../models/config"; | ||||
|  | ||||
| @injectable() | ||||
| export class StateDataProvider { | ||||
|   private _states: { | ||||
|     [controlUnitName: string]: IActivityState | undefined; | ||||
|   } = {}; | ||||
|  | ||||
|   public updateState(activity: IActivity, controlUnitName: string): void { | ||||
|     const oldState = this._states[controlUnitName]; | ||||
|     this._states[controlUnitName] = { | ||||
|       currentActivity: activity, | ||||
|       lastActivity: oldState?.lastActivity, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public deactivateState(controlUnitName: string): void { | ||||
|     const oldState = this._states[controlUnitName]; | ||||
|     this._states[controlUnitName] = { | ||||
|       currentActivity: undefined, | ||||
|       lastActivity: oldState?.currentActivity, | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   public removeState(controlUnitName: string): void { | ||||
|     this._states[controlUnitName] = undefined; | ||||
|   } | ||||
|  | ||||
|   public getState(controlUnitName: string): IActivityState | undefined { | ||||
|     if (!this._states[controlUnitName]) { | ||||
|       return undefined; | ||||
|     } | ||||
|     return this._states[controlUnitName]; | ||||
|   } | ||||
|  | ||||
|   public get states(): { | ||||
|     [controlUnitName: string]: IActivityState | undefined; | ||||
|   } { | ||||
|     return this._states; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										184
									
								
								src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										184
									
								
								src/index.ts
									
									
									
									
									
								
							| @@ -1,11 +1,183 @@ | ||||
| import { API } from "homebridge"; | ||||
| import { ControlUnit } from "./Accessories/ControlUnit"; | ||||
| import { Activity } from "./Models/Activity"; | ||||
| import { DeviceSetupItem } from "./Models/DeviceSetupItem"; | ||||
| import { Input, Output, Matrix } from "./Models/Matrix"; | ||||
| import HubDataProvider from './DataProviders/HubDataProvider'; | ||||
|  | ||||
| import { PLATFORM_NAME } from "./settings"; | ||||
| import { Platform } from "./platform"; | ||||
| let Accessory: any; | ||||
| let Homebridge: any; | ||||
|  | ||||
| export type hub = "Harmony" | "Neeo"; | ||||
|  | ||||
| /** | ||||
|  * This method registers the platform with Homebridge | ||||
|  * Main entry. | ||||
|  * @param homebridge  | ||||
|  */ | ||||
| export = (api: API) => { | ||||
|   api.registerPlatform(PLATFORM_NAME, Platform); | ||||
| export default function (homebridge: any) { | ||||
|     Homebridge = homebridge; | ||||
|     Accessory = homebridge.platformAccessory; | ||||
|     homebridge.registerPlatform( | ||||
|         'homebridge-harmony-watson', | ||||
|         'HarmonyHubMatrix', | ||||
|         HarmonyMatrixPlatform, | ||||
|         true | ||||
|     ); | ||||
| }; | ||||
|  | ||||
| class HarmonyMatrixPlatform { | ||||
|     log: any = {}; | ||||
|     config: any = {}; | ||||
|     api: any; | ||||
|     externalAccessories: Array<any> = []; | ||||
|     dataProvider: HubDataProvider | null; | ||||
|  | ||||
|     constructor(log: any, config: any, api: any) { | ||||
|         this.log = log; | ||||
|         this.config = config; | ||||
|         this.api = api; | ||||
|         this.log('INFO - Registering Harmony Matrix Platform'); | ||||
|         this.api.on('didFinishLaunching', this.didFinishLaunching.bind(this)); | ||||
|  | ||||
|         this.dataProvider = null; | ||||
|         this.log("This is new"); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Handler for didFinishLaunching | ||||
|      * Happens after constructor | ||||
|      */ | ||||
|     didFinishLaunching() { | ||||
|         this.log(`Publishing external accessories`); | ||||
|  | ||||
|         //This is required in order to have multiple tv remotes on one platform | ||||
|         this.externalAccessories.forEach((accessory: ControlUnit) => { | ||||
|             this.api.publishExternalAccessories("HarmonyMatrixPlatform", [accessory.platformAccessory]); | ||||
|         }) | ||||
|  | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Called by homebridge to gather accessories. | ||||
|      * @param callback  | ||||
|      */ | ||||
|     accessories(callback: (accessories: Array<ControlUnit>) => void) { | ||||
|         //Parse ip | ||||
|         let harmonyIp: string = this.config["harmonyHubIp"]; | ||||
|         let neeoIp: string = this.config["neeoHubIp"]; | ||||
|  | ||||
|         //Parse matrix | ||||
|         let configInputs: any = this.config["Matrix"]["Inputs"]; | ||||
|         let configOutputs: any = this.config["Matrix"]["Outputs"]; | ||||
|         let matrixName: string = this.config["Matrix"]["DeviceName"]; | ||||
|         let hub: hub = this.config["Matrix"]["Hub"]; | ||||
|  | ||||
|         let inputs: Array<Input> = []; | ||||
|         let outputs: Array<Output> = []; | ||||
|  | ||||
|         configInputs.forEach((configInput: any) => { | ||||
|             let inputDevice: string = configInput["InputDevice"]; | ||||
|             let inputNumber: string = configInput["InputNumber"]; | ||||
|  | ||||
|             this.log(`INFO - Added input to matrix '${inputDevice}'`); | ||||
|  | ||||
|             inputs.push({ | ||||
|                 inputDevice: inputDevice, | ||||
|                 inputNumber: inputNumber | ||||
|             }); | ||||
|         }); | ||||
|         configOutputs.forEach((configOutput: any) => { | ||||
|             let outputDevice: string = configOutput["OutputDevice"]; | ||||
|             let outputLetter: string = configOutput["OutputLetter"]; | ||||
|  | ||||
|             this.log(`INFO - Added output to matrix '${outputDevice}'`); | ||||
|  | ||||
|             outputs.push({ | ||||
|                 outputDevice: outputDevice, | ||||
|                 outputLetter: outputLetter | ||||
|             }); | ||||
|  | ||||
|         }); | ||||
|  | ||||
|         let matrix = new Matrix({ | ||||
|             inputs: inputs, | ||||
|             outputs: outputs, | ||||
|             deviceName: matrixName, | ||||
|             hubType: hub | ||||
|         }); | ||||
|  | ||||
|         //construct data provider | ||||
|         this.dataProvider = new HubDataProvider({ | ||||
|             harmonyHubAddress: harmonyIp, | ||||
|             neeoHubAddress: neeoIp, | ||||
|             matrix: matrix, | ||||
|             log: this.log | ||||
|         }); | ||||
|  | ||||
|         //Parse control units | ||||
|         let configControlUnits: any = this.config["ControlUnits"]; | ||||
|         let controlUnits: Array<ControlUnit> = []; | ||||
|         configControlUnits.forEach((configControlUnit: any) => { | ||||
|             //Parse activities list | ||||
|             let configActivities: any = configControlUnit["Activities"]; | ||||
|             let activities: Array<Activity> = []; | ||||
|             configActivities.forEach((configActivity: any) => { | ||||
|                 //parse devices | ||||
|                 let configDevices: any = configActivity["DeviceSetupList"]; | ||||
|                 let devices: Array<DeviceSetupItem> = []; | ||||
|                 configDevices.forEach((configDevice: any) => { | ||||
|                     //Add device | ||||
|                     devices.push(new DeviceSetupItem({ | ||||
|                         deviceName: configDevice["DeviceName"], | ||||
|                         input: configDevice["Input"], | ||||
|                         hub: configDevice["Hub"] | ||||
|                     })); | ||||
|                     this.log(`INFO - Added device '${configDevice["DeviceName"]}' for activity '${configActivity["DisplayName"]}'`); | ||||
|                 }); | ||||
|  | ||||
|                 //Add activity | ||||
|                 activities.push(new Activity({ | ||||
|                     volumeDeviceId: configActivity["VolumeDevice"], | ||||
|                     controlDeviceId: configActivity["ControlDevice"], | ||||
|                     outputDeviceId: configActivity["OutputDevice"], | ||||
|                     displayName: configActivity["DisplayName"], | ||||
|                     useMatrix: configActivity["UseMatrix"], | ||||
|                     deviceList: devices | ||||
|                 })); | ||||
|                 this.log(`INFO - Added activity '${configActivity["DisplayName"]}'`); | ||||
|             }); | ||||
|  | ||||
|             let controlUnit: ControlUnit = new ControlUnit({ | ||||
|                 dataProvider: this.dataProvider!, | ||||
|                 displayName: configControlUnit["DisplayName"], | ||||
|                 api: this.api, | ||||
|                 log: this.log, | ||||
|                 activities: activities, | ||||
|                 homebridge: Homebridge, | ||||
|                 isExternal: false | ||||
|             }); | ||||
|  | ||||
|             let controlUnitExternal: ControlUnit = new ControlUnit({ | ||||
|                 dataProvider: this.dataProvider!, | ||||
|                 displayName: `${configControlUnit["DisplayName"]}`, | ||||
|                 api: this.api, | ||||
|                 log: this.log, | ||||
|                 activities: activities, | ||||
|                 homebridge: Homebridge, | ||||
|                 isExternal: true | ||||
|             }); | ||||
|  | ||||
|             //@ts-ignore | ||||
|             let accessory = controlUnit as homebridge.platformAccessory; | ||||
|             //@ts-ignore | ||||
|             let externalAccessory = controlUnitExternal as homebridge.platformAccessory; | ||||
|  | ||||
|             //Add control unit | ||||
|             controlUnits.push(accessory); | ||||
|             //Add to list of remotes | ||||
|             this.externalAccessories.push(externalAccessory); | ||||
|  | ||||
|             this.log(`INFO - Added ControlUnit`); | ||||
|         }); | ||||
|         callback(controlUnits); | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| import { IActivity } from "./config"; | ||||
|  | ||||
| export interface IActivityState { | ||||
|   currentActivity?: IActivity; | ||||
|   lastActivity?: IActivity; | ||||
| } | ||||
| @@ -1,10 +0,0 @@ | ||||
| import { IDeviceSetupItem } from "./deviceSetupItem"; | ||||
|  | ||||
| export interface IActivity { | ||||
|   OutputDevice: string; | ||||
|   VolumeDevice: string; | ||||
|   ControlDevice: string; | ||||
|   DisplayName: string; | ||||
|   DeviceSetupList: Array<IDeviceSetupItem>; | ||||
|   UseMatrix: boolean; | ||||
| } | ||||
| @@ -1,22 +0,0 @@ | ||||
| import { IMatrix } from "./matrix"; | ||||
| import { IActivity } from "./activity"; | ||||
| import { IDeviceButton } from "./deviceButton"; | ||||
| import { IDeviceConfig } from "./deviceConfig"; | ||||
| import { IHub } from "./hub"; | ||||
| import { ISequence } from "./sequence"; | ||||
|  | ||||
| export interface IControlUnit { | ||||
|   DisplayName: string; | ||||
|   Activities: Array<IActivity>; | ||||
| } | ||||
|  | ||||
| export interface IConfig { | ||||
|   hubIp: string; | ||||
|   EmitDevicesOnStartup: boolean; | ||||
|   Matrix: IMatrix; | ||||
|   ControlUnits: Array<IControlUnit>; | ||||
|   DeviceButtons: Array<IDeviceButton>; | ||||
|   Sequences: Array<ISequence>; | ||||
|   Devices: Array<IDeviceConfig>; | ||||
|   Hubs: Array<IHub>; | ||||
| } | ||||
| @@ -1,7 +0,0 @@ | ||||
| export interface IDeviceButton { | ||||
|     DeviceName: string; | ||||
|     ButtonName: string; | ||||
|     DisplayName: string; | ||||
|     NumberOfKeyPresses: number; | ||||
|     IsStateful: boolean; | ||||
| } | ||||
| @@ -1,5 +0,0 @@ | ||||
| export interface IDeviceConfig { | ||||
|   Name: string; | ||||
|   Hub: string; | ||||
|   Overrides: Array<{ Command: string; Override: string }>; | ||||
| } | ||||
| @@ -1,5 +0,0 @@ | ||||
|  | ||||
| export interface IDeviceSetupItem { | ||||
|     DeviceName: string; | ||||
|     Input: string; | ||||
| } | ||||
| @@ -1,5 +0,0 @@ | ||||
| export interface IHub { | ||||
|     Name: string; | ||||
|     Ip: string; | ||||
|     Harmony: any; | ||||
| } | ||||
| @@ -1,5 +0,0 @@ | ||||
| export * from "./activity"; | ||||
| export * from "./config"; | ||||
| export * from "./deviceButton"; | ||||
| export * from "./deviceSetupItem"; | ||||
| export * from "./matrix"; | ||||
| @@ -1,15 +0,0 @@ | ||||
| export interface IInput { | ||||
|     InputNumber: string, | ||||
|     InputDevice: string, | ||||
| } | ||||
|  | ||||
| export interface IOutput { | ||||
|     OutputLetter: string, | ||||
|     OutputDevice: string, | ||||
| } | ||||
|  | ||||
| export interface IMatrix { | ||||
|     Inputs: Array<IInput>; | ||||
|     Outputs: Array<IOutput>; | ||||
|     DeviceName: string; | ||||
| } | ||||
| @@ -1,10 +0,0 @@ | ||||
| export interface ISequence { | ||||
|   DisplayName: string; | ||||
|   Steps: Array<IStep>; | ||||
| } | ||||
|  | ||||
| export interface IStep { | ||||
|   DeviceName?: string; | ||||
|   DeviceCommand?: string; | ||||
|   Delay: number; | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| export interface ICommand { | ||||
|     command?: string, | ||||
|     deviceId?: string, | ||||
|     type?: string | ||||
| } | ||||
|  | ||||
| export interface IDevice { | ||||
|     id: string; | ||||
|     name: string; | ||||
|     harmony: any; | ||||
|     log: any | ||||
|     supportsCommand(commandName: string): boolean, | ||||
|     getCommand(commandName: string): ICommand, | ||||
|     powerOn(): Promise<void>; | ||||
|     powerOff(): Promise<void>; | ||||
|     sendCommand(commandName: string): Promise<void>; | ||||
|     commands: { [name: string]: ICommand }; | ||||
|     on: boolean; | ||||
| } | ||||
| @@ -1,47 +0,0 @@ | ||||
| import { ICommand } from "./device"; | ||||
| import { sleep } from "../util/sleep"; | ||||
|  | ||||
| export interface IHarmonyDeviceProps { | ||||
|   id: string; | ||||
|   name: string; | ||||
|   harmony: any; | ||||
|   log: any; | ||||
|   commands: { [name: string]: ICommand }; | ||||
| } | ||||
|  | ||||
| export class HarmonyDevice { | ||||
|   private _commands: { [name: string]: ICommand } = {}; | ||||
|   private _on: boolean; | ||||
|  | ||||
|   constructor(props: IHarmonyDeviceProps) { | ||||
|     this.id = props.id; | ||||
|     this.name = props.name; | ||||
|     this._on = false; | ||||
|     this._commands = props.commands; | ||||
|   } | ||||
|  | ||||
|   public id: string; | ||||
|   public name: string; | ||||
|  | ||||
|   public get on(): boolean { | ||||
|     return this._on; | ||||
|   } | ||||
|  | ||||
|   public set on(value: boolean) { | ||||
|     this._on = value; | ||||
|   } | ||||
|  | ||||
|   public get commands(): { [name: string]: ICommand } { | ||||
|     return this._commands; | ||||
|   } | ||||
|  | ||||
|   //Define device methods | ||||
|   public supportsCommand(commandName: string): boolean { | ||||
|     let command = this._commands[commandName]; | ||||
|     return command ? true : false; | ||||
|   } | ||||
|  | ||||
|   public getCommand(commandName: string): ICommand { | ||||
|     return this._commands[commandName]; | ||||
|   } | ||||
| } | ||||
| @@ -1,94 +0,0 @@ | ||||
| import { HarmonyDevice } from "./harmonyDevice"; | ||||
| const Harmony = require("harmony-websocket"); | ||||
| import { ICommand } from "./device"; | ||||
| import { EventEmitter } from "events"; | ||||
| import { sleep } from "../util"; | ||||
|  | ||||
| export class HarmonyHub extends EventEmitter { | ||||
|   private _devices: { [deviceName: string]: HarmonyDevice } = {}; | ||||
|   private _ip: string; | ||||
|   private _harmony: any; | ||||
|   private _log: any; | ||||
|   private _name: string; | ||||
|  | ||||
|   constructor(hubName: string, ipAddress: string, log: any) { | ||||
|     super(); | ||||
|     this._ip = ipAddress; | ||||
|     this._log = log; | ||||
|     this._name = hubName; | ||||
|   } | ||||
|  | ||||
|   public get devices(): { [deviceName: string]: HarmonyDevice } { | ||||
|     return this._devices; | ||||
|   } | ||||
|  | ||||
|   public get hubName(): string { | ||||
|     return this._name; | ||||
|   } | ||||
|  | ||||
|   public getDeviceByName = (deviceName: string): HarmonyDevice => { | ||||
|     return this._devices[deviceName]; | ||||
|   }; | ||||
|  | ||||
|   public initialize = async () => { | ||||
|     this._harmony = new Harmony(); | ||||
|     await this._harmony.connect(this._ip); | ||||
|     this._harmony.on("stateDigest", (data: any) => { | ||||
|       console.log(data); | ||||
|     }); | ||||
|  | ||||
|     this._harmony.on("automationState", (data: any) => { | ||||
|       console.log(data); | ||||
|     }); | ||||
|  | ||||
|     //Gather devices | ||||
|     let devices: any = await this._harmony.getDevices(); | ||||
|     try { | ||||
|       await Promise.all( | ||||
|         //Add each to dictionary | ||||
|         devices.map(async (dev: any) => { | ||||
|           //get commands | ||||
|           let commands: { [name: string]: ICommand } = {}; | ||||
|           let deviceCommands: any = await this._harmony.getDeviceCommands( | ||||
|             dev.id | ||||
|           ); | ||||
|           deviceCommands.forEach((command: any) => { | ||||
|             commands[command.label] = command.action; | ||||
|           }); | ||||
|           this._devices[dev.label] = new HarmonyDevice({ | ||||
|             id: dev.id, | ||||
|             name: dev.label, | ||||
|             commands: commands, | ||||
|             log: this._log, | ||||
|             harmony: this._harmony, | ||||
|           }); | ||||
|         }) | ||||
|       ); | ||||
|       this.emit("Ready"); | ||||
|     } catch (err) { | ||||
|       this._log(`ERROR - error connecting to harmony: ${err}`); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   private connect = async (): Promise<void> => { | ||||
|     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}`); | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1 +0,0 @@ | ||||
| export * from "./device"; | ||||
							
								
								
									
										228
									
								
								src/platform.ts
									
									
									
									
									
								
							
							
						
						
									
										228
									
								
								src/platform.ts
									
									
									
									
									
								
							| @@ -1,228 +0,0 @@ | ||||
| import "reflect-metadata"; | ||||
| import { | ||||
|   API, | ||||
|   Characteristic, | ||||
|   DynamicPlatformPlugin, | ||||
|   Logger, | ||||
|   PlatformAccessory, | ||||
|   PlatformConfig, | ||||
|   Service, | ||||
| } from "homebridge"; | ||||
| import { ControlUnit, DeviceButton } from "./accessories"; | ||||
| import { Sequence } from "./accessories/sequence"; | ||||
| import { IConfig, IControlUnit, IDeviceButton } from "./models/config"; | ||||
| import { ISequence } from "./models/config/sequence"; | ||||
| import { PLATFORM_NAME, PLUGIN_NAME } from "./settings"; | ||||
| import { container } from "tsyringe"; | ||||
| import { HarmonyDataProvider } from "./dataProviders/harmonyDataProvider"; | ||||
| import { StateDataProvider } from "./dataProviders/stateDataProvider"; | ||||
| import { CommandService } from "./services/commandService"; | ||||
| import { ActivityService } from "./services/activityService"; | ||||
| import { VolumeService } from "./services/volumeService"; | ||||
|  | ||||
| export class Platform implements DynamicPlatformPlugin { | ||||
|   constructor( | ||||
|     public readonly log: Logger, | ||||
|     config: PlatformConfig, | ||||
|     public readonly api: API | ||||
|   ) { | ||||
|     this.log.debug("Finished initializing platform:", config.name); | ||||
|     this.config = config as unknown as IConfig; | ||||
|     this.register(); | ||||
|  | ||||
|     let didFinishLaunching = false; | ||||
|     this.api.on("didFinishLaunching", async () => { | ||||
|       log.debug("Executed didFinishLaunching callback"); | ||||
|       this.discoverControlUnitAccessories(); | ||||
|       this.discoverDeviceButtonAccessories(); | ||||
|       this.discoverSequenceAccessories(); | ||||
|       this.pruneAccessories(); | ||||
|       didFinishLaunching = true; | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   public readonly Service: typeof Service = this.api.hap.Service; | ||||
|   public readonly Characteristic: typeof Characteristic = | ||||
|     this.api.hap.Characteristic; | ||||
|  | ||||
|   // this is used to track restored cached accessories | ||||
|   public readonly accessories: PlatformAccessory[] = []; | ||||
|   public config: IConfig; | ||||
|  | ||||
|   /** | ||||
|    * Discover new control unit accessories | ||||
|    * @param dataProvider | ||||
|    */ | ||||
|   private discoverControlUnitAccessories(): 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: " + | ||||
|             existingAccessory.displayName | ||||
|         ); | ||||
|  | ||||
|         new ControlUnit( | ||||
|           this, | ||||
|           existingAccessory, | ||||
|           container.resolve(ActivityService), | ||||
|           container.resolve(CommandService), | ||||
|           container.resolve(VolumeService), | ||||
|           unit.Activities | ||||
|         ); | ||||
|  | ||||
|         this.api.publishExternalAccessories(PLUGIN_NAME, [existingAccessory]); | ||||
|         console.log("Publishing external accessory: " + uuid); | ||||
|       } else { | ||||
|         this.log.info("Adding new accessory: " + unit.DisplayName); | ||||
|         const accessory = new this.api.platformAccessory( | ||||
|           unit.DisplayName, | ||||
|           uuid | ||||
|         ); | ||||
|         accessory.context["DeviceName"] = unit.DisplayName; | ||||
|         accessory.context["Type"] = typeof ControlUnit.name; | ||||
|  | ||||
|         new ControlUnit( | ||||
|           this, | ||||
|           accessory, | ||||
|           container.resolve(ActivityService), | ||||
|           container.resolve(CommandService), | ||||
|           container.resolve(VolumeService), | ||||
|           unit.Activities | ||||
|         ); | ||||
|  | ||||
|         this.api.publishExternalAccessories(PLUGIN_NAME, [accessory]); | ||||
|         console.log("Publishing external accessory: " + uuid); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Discover new device button accessories | ||||
|    * @param dataProvider | ||||
|    */ | ||||
|   private discoverDeviceButtonAccessories(): 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); | ||||
|       if (existingAccessory) { | ||||
|         this.log.info( | ||||
|           "Restoring existing accessory from cache: " + | ||||
|             existingAccessory.displayName | ||||
|         ); | ||||
|  | ||||
|         new DeviceButton( | ||||
|           this, | ||||
|           existingAccessory, | ||||
|           container.resolve(HarmonyDataProvider), | ||||
|           button | ||||
|         ); | ||||
|         this.api.updatePlatformAccessories([existingAccessory]); | ||||
|       } else { | ||||
|         this.log.info("Adding new accessory: " + button.DisplayName); | ||||
|         const accessory = new this.api.platformAccessory( | ||||
|           button.DisplayName, | ||||
|           uuid | ||||
|         ); | ||||
|         accessory.context["DeviceName"] = button.DisplayName; | ||||
|         accessory.context["Type"] = typeof DeviceButton; | ||||
|  | ||||
|         new DeviceButton( | ||||
|           this, | ||||
|           accessory, | ||||
|           container.resolve(HarmonyDataProvider), | ||||
|           button | ||||
|         ); | ||||
|  | ||||
|         this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ | ||||
|           accessory, | ||||
|         ]); | ||||
|         this.accessories.push(accessory); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Discover new sequence accessories | ||||
|    * @param dataProvider | ||||
|    */ | ||||
|   private discoverSequenceAccessories(): 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, | ||||
|           container.resolve(HarmonyDataProvider), | ||||
|           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; | ||||
|         accessory.context["Type"] = typeof "Sequence"; | ||||
|  | ||||
|         new Sequence( | ||||
|           this, | ||||
|           accessory, | ||||
|           container.resolve(HarmonyDataProvider), | ||||
|           sequence | ||||
|         ); | ||||
|  | ||||
|         this.api.registerPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ | ||||
|           accessory, | ||||
|         ]); | ||||
|         this.accessories.push(accessory); | ||||
|       } | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   private pruneAccessories(): void { | ||||
|     // Remove devices not discovered | ||||
|     const listOfConfiguredAccessories = [ | ||||
|       ...this.config.DeviceButtons.map((e) => e.DisplayName), | ||||
|       ...this.config.Sequences.map((e) => e.DisplayName), | ||||
|       ...this.config.ControlUnits.map((e) => e.DisplayName), | ||||
|     ]; | ||||
|     for (const accessory of this.accessories) { | ||||
|       if ( | ||||
|         listOfConfiguredAccessories.filter( | ||||
|           (displayName) => displayName === accessory.displayName | ||||
|         ).length === 0 | ||||
|       ) { | ||||
|         this.api.unregisterPlatformAccessories(PLUGIN_NAME, PLATFORM_NAME, [ | ||||
|           accessory, | ||||
|         ]); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   configureAccessory(accessory: PlatformAccessory<Record<string, any>>): void { | ||||
|     this.log.info("Loading accessory from cache:", accessory.displayName); | ||||
|  | ||||
|     // add the restored accessory to the accessories cache so we can track if it has already been registered | ||||
|     this.accessories.push(accessory); | ||||
|   } | ||||
|  | ||||
|   private register(): void { | ||||
|     container.register<Platform>(Platform, { useValue: this }); | ||||
|     container.register<IConfig>("IConfig", { useValue: this.config }); | ||||
|     container.register<Logger>("log", { useValue: this.log }); | ||||
|     container.registerSingleton<HarmonyDataProvider>(HarmonyDataProvider); | ||||
|     container.registerSingleton<ActivityService>(ActivityService); | ||||
|     container.registerSingleton<StateDataProvider>(StateDataProvider); | ||||
|     container.registerSingleton<CommandService>(CommandService); | ||||
|   } | ||||
| } | ||||
| @@ -1,243 +0,0 @@ | ||||
| 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<HarmonyDevice> = 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<HarmonyDevice> = this.getDevicesToTurnOff( | ||||
|       controlUnitName, | ||||
|       lastActivity, | ||||
|       activity | ||||
|     ); | ||||
|     await this._harmonyDataProvider.powerOffDevices(devicesToTurnOff); | ||||
|  | ||||
|     this._stateDataProvider.updateState(activity, controlUnitName); | ||||
|   }; | ||||
|  | ||||
|   public stopCurrentActivity = async ( | ||||
|     controlUnitName: string | ||||
|   ): Promise<void> => { | ||||
|     if (!this.getCurrentActivity(controlUnitName)) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     let lastActivity: IActivity | undefined = | ||||
|       this.getCurrentActivity(controlUnitName); | ||||
|  | ||||
|     let devicesToTurnOff: Array<HarmonyDevice> = 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<HarmonyDevice>, | ||||
|     controlUnitName: string | ||||
|   ): Array<HarmonyDevice> { | ||||
|     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<HarmonyDevice> { | ||||
|     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<void> { | ||||
|     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 | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @@ -1,71 +0,0 @@ | ||||
| import { inject, injectable } from "tsyringe"; | ||||
| import { RemoteKey } from "../accessories/controlUnit"; | ||||
| import { HarmonyDataProvider } from "../dataProviders/harmonyDataProvider"; | ||||
| import { StateDataProvider } from "../dataProviders/stateDataProvider"; | ||||
| import { HarmonyDevice } from "../models/harmonyDevice"; | ||||
|  | ||||
| @injectable() | ||||
| export class CommandService { | ||||
|   constructor( | ||||
|     @inject(StateDataProvider) private _stateDataProvider: StateDataProvider, | ||||
|     @inject(HarmonyDataProvider) | ||||
|     private _harmonyDataProvider: HarmonyDataProvider | ||||
|   ) {} | ||||
|  | ||||
|   /** | ||||
|    * Send key press for current activity. | ||||
|    * | ||||
|    * @param controlUnitName The name of the control unit to act on. | ||||
|    * @param key The key to send. | ||||
|    */ | ||||
|   public sendKeyPress = async (controlUnitName: string, key: any) => { | ||||
|     let currentActivity = this._stateDataProvider.getState(controlUnitName); | ||||
|     if (currentActivity) { | ||||
|       let commandName: string = ""; | ||||
|  | ||||
|       let device: HarmonyDevice = this._harmonyDataProvider.getDeviceByName( | ||||
|         currentActivity.currentActivity!.ControlDevice | ||||
|       ); | ||||
|       switch (key) { | ||||
|         case RemoteKey.ARROW_UP: { | ||||
|           commandName = "Direction Up"; | ||||
|           break; | ||||
|         } | ||||
|         case RemoteKey.ARROW_DOWN: { | ||||
|           commandName = "Direction Down"; | ||||
|           break; | ||||
|         } | ||||
|         case RemoteKey.ARROW_LEFT: { | ||||
|           commandName = "Direction Left"; | ||||
|           break; | ||||
|         } | ||||
|         case RemoteKey.ARROW_RIGHT: { | ||||
|           commandName = "Direction Right"; | ||||
|           break; | ||||
|         } | ||||
|         case RemoteKey.SELECT: { | ||||
|           commandName = "Select"; | ||||
|           break; | ||||
|         } | ||||
|         case RemoteKey.PLAY_PAUSE: { | ||||
|           commandName = "Pause"; | ||||
|           break; | ||||
|         } | ||||
|         case RemoteKey.INFORMATION: { | ||||
|           commandName = "Menu"; | ||||
|           break; | ||||
|         } | ||||
|         case RemoteKey.BACK: { | ||||
|           commandName = "Back"; | ||||
|           break; | ||||
|         } | ||||
|         case RemoteKey.EXIT: { | ||||
|           commandName = "Back"; | ||||
|           break; | ||||
|         } | ||||
|       } | ||||
|  | ||||
|       await this._harmonyDataProvider.sendCommand(commandName, device); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| @@ -1,49 +0,0 @@ | ||||
| import { inject, injectable } from "tsyringe"; | ||||
| import { HarmonyDataProvider } from "../dataProviders/harmonyDataProvider"; | ||||
| import { StateDataProvider } from "../dataProviders/stateDataProvider"; | ||||
| import { HarmonyDevice } from "../models/harmonyDevice"; | ||||
|  | ||||
| @injectable() | ||||
| export class VolumeService { | ||||
|   constructor( | ||||
|     @inject(StateDataProvider) private _stateDataProvider: StateDataProvider, | ||||
|     @inject(HarmonyDataProvider) | ||||
|     private _harmonyDataProvider: HarmonyDataProvider | ||||
|   ) {} | ||||
|  | ||||
|   /** | ||||
|    * Turn the volume up for the current running activity. | ||||
|    */ | ||||
|   public volumeUp = async (controlUnitName: string) => { | ||||
|     let volumeUpCommand: string = "Volume Up"; | ||||
|     let currentState = this._stateDataProvider.getState(controlUnitName); | ||||
|     if (currentState) { | ||||
|       let volumeDevice: HarmonyDevice = | ||||
|         this._harmonyDataProvider.getDeviceByName( | ||||
|           currentState.currentActivity!.VolumeDevice | ||||
|         ); | ||||
|       await this._harmonyDataProvider.sendCommand( | ||||
|         volumeUpCommand, | ||||
|         volumeDevice | ||||
|       ); | ||||
|     } | ||||
|   }; | ||||
|  | ||||
|   /** | ||||
|    * Volume down for current running activity. | ||||
|    */ | ||||
|   public volumeDown = async (controlUnitName: string) => { | ||||
|     let volumeDownCommand: string = "Volume Down"; | ||||
|     let currentState = this._stateDataProvider.getState(controlUnitName); | ||||
|     if (currentState) { | ||||
|       let volumeDevice: HarmonyDevice = | ||||
|         this._harmonyDataProvider.getDeviceByName( | ||||
|           currentState.currentActivity!.VolumeDevice | ||||
|         ); | ||||
|       await this._harmonyDataProvider.sendCommand( | ||||
|         volumeDownCommand, | ||||
|         volumeDevice | ||||
|       ); | ||||
|     } | ||||
|   }; | ||||
| } | ||||
| @@ -1,9 +0,0 @@ | ||||
| /** | ||||
|  * This is the name of the platform that users will use to register the plugin in the Homebridge config.json | ||||
|  */ | ||||
| export const PLATFORM_NAME = "HarmonyHubMatrix"; | ||||
|  | ||||
| /** | ||||
|  * This must match the name of your plugin as defined the package.json | ||||
|  */ | ||||
| export const PLUGIN_NAME = "@watsonb8/homebridge-harmony-control"; | ||||
| @@ -1,32 +0,0 @@ | ||||
| /** | ||||
|  * Helper function to convert callbacks into promises | ||||
|  * @param func | ||||
|  */ | ||||
|  | ||||
| export default function callbackify( | ||||
|   func: (...args: any[]) => Promise<any> | ||||
| ): Function { | ||||
|   return (...args: any[]) => { | ||||
|     const onlyArgs: any[] = []; | ||||
|     let maybeCallback: Function | null = null; | ||||
|  | ||||
|     for (const arg of args) { | ||||
|       if (typeof arg === "function") { | ||||
|         maybeCallback = arg; | ||||
|         break; | ||||
|       } | ||||
|  | ||||
|       onlyArgs.push(arg); | ||||
|     } | ||||
|  | ||||
|     if (!maybeCallback) { | ||||
|       throw new Error("Missing callback parameter!"); | ||||
|     } | ||||
|  | ||||
|     const callback = maybeCallback; | ||||
|  | ||||
|     func(...onlyArgs) | ||||
|       .then((data: any) => callback(null, data)) | ||||
|       .catch((err: any) => callback(err)); | ||||
|   }; | ||||
| } | ||||
| @@ -1,2 +0,0 @@ | ||||
| export * from "./callbackify"; | ||||
| export * from "./sleep"; | ||||
| @@ -1,15 +1,17 @@ | ||||
| { | ||||
|   "compilerOptions": { | ||||
|     "target": "es6", | ||||
|     "module": "commonjs", | ||||
|     "declaration": true, | ||||
|     "outDir": "./bin", | ||||
|     "strict": true, | ||||
|     "sourceMap": true, | ||||
|     "skipLibCheck": true, | ||||
|     "experimentalDecorators": true, | ||||
|     "emitDecoratorMetadata": true | ||||
|   }, | ||||
|   "include": ["src"], | ||||
|   "exclude": ["**/node_modules", "**/__tests__/*"] | ||||
| } | ||||
|     "compilerOptions": { | ||||
|         "target": "es6", | ||||
|         "module": "commonjs", | ||||
|         "declaration": true, | ||||
|         "outDir": "./bin", | ||||
|         "strict": true, | ||||
|         "sourceMap": true, | ||||
|     }, | ||||
|     "include": [ | ||||
|         "src" | ||||
|     ], | ||||
|     "exclude": [ | ||||
|         "node_modules", | ||||
|         "**/__tests__/*" | ||||
|     ] | ||||
| } | ||||
		Reference in New Issue
	
	Block a user