Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wake on Lan #199

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ homebridge config for your Roku accessory: `"infoButtonOverride": "HOME"`. The
list of possible keys can be found
[here](https://github.com/bschlenk/node-roku-client/blob/master/lib/keys.ts).

### requestTimeout

Wait for this value in milliseconds before considering the device unreachable. The default value is 1000 (1 second).

## Migrating Major Versions

### 2.x.x -> 3.x.x
Expand Down Expand Up @@ -116,6 +120,8 @@ overcome by sending 100 volume down requests before sending X amount of volume
up requests. I didn't feel like implementing this for obvious reasons, but pull
requests are welcome :)

Wake-on-LAN is supported, but your device must be connected via Ethernet.

## TODO

- Possibly fetch apps at homebridge start time or periodically so that the
Expand Down
16 changes: 14 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@
"dependencies": {
"deepmerge": "^4.2.2",
"lodash.map": "^4.6.0",
"roku-client": "^4.0.0"
"p-timeout": "^3.2.0",
"roku-client": "^4.0.0",
"wol": "^1.0.7"
},
"devDependencies": {
"@commitlint/cli": "^8.3.5",
Expand Down
97 changes: 69 additions & 28 deletions src/homebridge-roku.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
'use strict';

const { Client, keys } = require('roku-client');
const pTimeout = require('p-timeout');
const wol = require('wol');
const plugin = require('../package');

let hap;
Expand Down Expand Up @@ -30,6 +32,7 @@ class RokuAccessory {

this.volumeIncrement = config.volumeIncrement || DEFAULT_VOLUME_INCREMENT;
this.volumeDecrement = config.volumeDecrement || this.volumeIncrement;
this.requestTimeout = config.requestTimeout || 1000;

this.muted = false;

Expand Down Expand Up @@ -100,29 +103,58 @@ class RokuAccessory {
return accessoryInfo;
}

doesSupportWakeOnLan() {
return this.info.supportsWakeOnWlan === 'true';
}

setupTelevision() {
const television = new Service.Television(this.name);

television
.getCharacteristic(Characteristic.Active)
.on('get', (callback) => {
this.roku
.info()
.then((info) => {
const value =
info.powerMode === 'PowerOn'
? Characteristic.Active.ACTIVE
: Characteristic.Active.INACTIVE;
callback(null, value);
})
.catch(callback);
.on('get', async (callback) => {
try {
const info = await pTimeout(this.roku.info(), this.requestTimeout);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this again, will this ever work as expected? If the TV is off, this will fail immediately and never trigger the TimeoutError check below. I don't think we even need to use pTimeout at all. We can always try sending the WOL packet if this initially fails. Then you'd have to retry this.roku.info() to see if it actually turned on (the retry would need to be done after some timeout to give it time to turn on), then if it still isn't on, you call callback with the error, otherwise with null.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at this again, will this ever work as expected?

It's been working for me so far 😛.

If the TV is off, this will fail immediately and never trigger the TimeoutError check below.

Nope, this.roku.info() doesn't resolve until around 10 seconds later if the TV is offline; node-fetch which is used by the Roku package sorta has timeout support but I don't think the Roku package implements it.


const value =
info.powerMode === 'PowerOn'
? Characteristic.Active.ACTIVE
: Characteristic.Active.INACTIVE;
callback(null, value);
} catch (error) {
if (
error.constructor === pTimeout.TimeoutError &&
this.doesSupportWakeOnLan()
) {
callback(null, Characteristic.Active.INACTIVE);
return;
}

callback(error);
}
})
.on('set', (newValue, callback) => {
.on('set', async (newValue, callback) => {
if (newValue === Characteristic.Active.ACTIVE) {
this.roku
.keypress('PowerOn')
.then(() => callback(null))
.catch(callback);
try {
await pTimeout(this.roku.keypress('PowerOn'), this.requestTimeout);

callback(null);
} catch (error) {
if (
error.constructor === pTimeout.TimeoutError &&
this.doesSupportWakeOnLan()
) {
if (this.info.ethernetMac) {
// Send wake-on-lan packet
await wol.wake(this.info.ethernetMac);

callback(null);
return;
}
}

callback(error);
}
} else {
this.roku
.keypress('PowerOff')
Expand All @@ -133,18 +165,27 @@ class RokuAccessory {

television
.getCharacteristic(Characteristic.ActiveIdentifier)
.on('get', (callback) => {
this.roku
.active()
.then((app) => {
const index =
app !== null
? this.inputs.findIndex((input) => input.id === app.id)
: -1;
const hapId = index + 1;
callback(null, hapId);
})
.catch(callback);
.on('get', async (callback) => {
try {
const app = await pTimeout(this.roku.active(), this.requestTimeout);

const index =
app !== null
? this.inputs.findIndex((input) => input.id === app.id)
: -1;
const hapId = index + 1;
callback(null, hapId);
} catch (error) {
if (
error.constructor === pTimeout.TimeoutError &&
this.doesSupportWakeOnLan()
) {
callback(null, 1);
return;
}

callback(error);
}
})
.on('set', (index, callback) => {
const rokuId = this.inputs[index - 1].id;
Expand Down