feat(notification): add philips hue (#681)

Co-authored-by: Jef LeCompte <jeffreylec@gmail.com>
Co-authored-by: Nathan Banks <nathantb3@gmail.com>
This commit is contained in:
Matthew
2020-11-05 23:23:27 +00:00
committed by GitHub
parent 2955ea70ab
commit c8a9b0ba3e
7 changed files with 175 additions and 0 deletions
+9
View File
@@ -35,6 +35,15 @@ PAGE_SLEEP_MAX=
PAGE_TIMEOUT= PAGE_TIMEOUT=
PAGERDUTY_INTEGRATION_KEY= PAGERDUTY_INTEGRATION_KEY=
PAGERDUTY_SEVERITY= PAGERDUTY_SEVERITY=
PHILIPS_HUE_API_KEY=
PHILIPS_HUE_CLOUD_ACCESS_TOKEN=
PHILIPS_HUE_CLOUD_CLIENT_ID=
PHILIPS_HUE_CLOUD_CLIENT_SECRET=
PHILIPS_HUE_CLOUD_REFRESH_TOKEN=
PHILIPS_HUE_LAN_BRIDGE_IP=
PHILIPS_HUE_LIGHT_COLOR=
PHILIPS_HUE_LIGHT_IDS=
PHILIPS_HUE_LIGHT_PATTERN=
PHONE_CARRIER= PHONE_CARRIER=
PHONE_NUMBER= PHONE_NUMBER=
PLAY_SOUND= PLAY_SOUND=
+23
View File
@@ -335,7 +335,23 @@ environment variables are **optional**._
| `PAGERDUTY_SEVERITY` | Severity of PagerDuty events | Default: `info` | | `PAGERDUTY_SEVERITY` | Severity of PagerDuty events | Default: `info` |
</details> </details>
<details>
<summary>Philips Hue</summary>
| Environment variable | Description | Notes |
|:---:|---|---|
| `PHILIPS_HUE_API_KEY` | Hue Bridge API Key | **Required**, generate key using instructions [here](https://developers.meethue.com/develop/get-started-2/). This will be used for both LAN and cloud access over the official Remote Hue API. |
| `PHILIPS_HUE_LAN_BRIDGE_IP` | LAN IP Address of your Hue Bridge | LAN only, e.g. `192.168.x.x`|
| `PHILIPS_HUE_LIGHT_IDS` | Light IDs | Optional (all if not supplied). Comma seperated, e.g.: `1`, `2` |See Hue App → About for IDs |
| `PHILIPS_HUE_LIGHT_COLOR` | Color in RGB Format | Optional (NVIDIA green if not supplied). Comma separated, e.g.: `255`, `255`, `255`|
| `PHILIPS_HUE_LIGHT_PATTERN` | `blink` or empty | Optional - lights will flash for 30 seconds if `blink` is supplied. |
| `PHILIPS_HUE_CLOUD_ACCESS_TOKEN` | Remote Access Token | Cloud only, the access token obtained from Philips's Remote Hue API. Instructions to generate [here](https://developers.meethue.com/develop/hue-api/remote-authentication/). |
| `PHILIPS_HUE_CLOUD_REFRESH_TOKEN` | Remote Refresh Token | Cloud only, the refresh token obtained from Philips's Remote Hue API. |
| `PHILIPS_HUE_CLOUD_CLIENT_ID` | Remote Client ID | Cloud only, the client ID to use when accessing the Remote Hue API. |
| `PHILIPS_HUE_CLOUD_CLIENT_SECRET` | Remote Client Secret | Cloud only, the client secret to use when accessing the Remote Hue API. |
</details>
<details> <details>
<summary>Pushbullet</summary> <summary>Pushbullet</summary>
@@ -414,8 +430,15 @@ environment variables are **optional**._
</details> </details>
</details> </details>
## FAQ ## FAQ
**Q: What's Node.js and how do I install it?** Visit [their website](https://nodejs.org/en/) and download and install **Q: What's Node.js and how do I install it?** Visit [their website](https://nodejs.org/en/) and download and install
+20
View File
@@ -1624,6 +1624,11 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
}, },
"bottleneck": {
"version": "2.19.5",
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
"integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw=="
},
"boxen": { "boxen": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz", "resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz",
@@ -4148,6 +4153,11 @@
"integrity": "sha1-mYR1wXhEVobQsyJG2l3428++jqM=", "integrity": "sha1-mYR1wXhEVobQsyJG2l3428++jqM=",
"dev": true "dev": true
}, },
"get-ssl-certificate": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/get-ssl-certificate/-/get-ssl-certificate-2.3.3.tgz",
"integrity": "sha512-aKYXS1S5+2IYw4W5+lKC/M+lvaNYPe0PhnQ144NWARcBg35H3ZvyVZ6y0LNGtiAxggFBHeO7LaVGO4bgHK4g1Q=="
},
"get-stdin": { "get-stdin": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
@@ -5911,6 +5921,16 @@
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
"integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA==" "integrity": "sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA=="
}, },
"node-hue-api": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/node-hue-api/-/node-hue-api-4.0.9.tgz",
"integrity": "sha512-xsMUGKDSeMtYsKHSKNCn5XFq4eEArbEaFRAAccGBIlQ+ysrVKjlg1So44wY32gMgYfm3S6sJQOw2jLyPxu3Dkw==",
"requires": {
"axios": "^0.19.0",
"bottleneck": "^2.19.5",
"get-ssl-certificate": "^2.3.3"
}
},
"node-libs-browser": { "node-libs-browser": {
"version": "2.2.1", "version": "2.2.1",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
+1
View File
@@ -32,6 +32,7 @@
"messaging-api-telegram": "^1.0.1", "messaging-api-telegram": "^1.0.1",
"mqtt": "^4.2.4", "mqtt": "^4.2.4",
"node-fetch": "^2.6.1", "node-fetch": "^2.6.1",
"node-hue-api": "^4.0.9",
"node-notifier": "^8.0.0", "node-notifier": "^8.0.0",
"node-pagerduty": "^1.3.5", "node-pagerduty": "^1.3.5",
"nodemailer": "^6.4.14", "nodemailer": "^6.4.14",
+12
View File
@@ -151,6 +151,18 @@ const notifications = {
integrationKey: envOrString(process.env.PAGERDUTY_INTEGRATION_KEY), integrationKey: envOrString(process.env.PAGERDUTY_INTEGRATION_KEY),
severity: envOrString(process.env.PAGERDUTY_SEVERITY, 'info') severity: envOrString(process.env.PAGERDUTY_SEVERITY, 'info')
}, },
philips_hue: {
accessToken: envOrString(process.env.PHILIPS_HUE_CLOUD_ACCESS_TOKEN),
apiKey: envOrString(process.env.PHILIPS_HUE_API_KEY),
bridgeIp: envOrString(process.env.PHILIPS_HUE_LAN_BRIDGE_IP),
clientId: envOrString(process.env.PHILIPS_HUE_CLOUD_CLIENT_ID),
clientSecret: envOrString(process.env.PHILIPS_HUE_CLOUD_CLIENT_SECRET),
lightColor: envOrString(process.env.PHILIPS_HUE_LIGHT_COLOR),
lightIds: envOrString(process.env.PHILIPS_HUE_LIGHT_IDS),
lightPattern: envOrString(process.env.PHILIPS_HUE_LIGHT_PATTERN),
refreshToken: envOrString(process.env.PHILIPS_HUE_CLOUD_REFRESH_TOKEN),
remoteApiUsername: envOrString(process.env.PHILIPS_HUE_API_KEY)
},
phone: { phone: {
availableCarriers: new Map([ availableCarriers: new Map([
['att', 'txt.att.net'], ['att', 'txt.att.net'],
+2
View File
@@ -1,4 +1,5 @@
import {Link, Store} from '../store/model'; import {Link, Store} from '../store/model';
import {adjustPhilipsHueLights} from './philips-hue';
import {playSound} from './sound'; import {playSound} from './sound';
import {sendDesktopNotification} from './desktop'; import {sendDesktopNotification} from './desktop';
import {sendDiscordMessage} from './discord'; import {sendDiscordMessage} from './discord';
@@ -21,6 +22,7 @@ export function sendNotification(link: Link, store: Store) {
sendSms(link, store); sendSms(link, store);
sendDesktopNotification(link, store); sendDesktopNotification(link, store);
// Non-priority // Non-priority
adjustPhilipsHueLights();
sendDiscordMessage(link, store); sendDiscordMessage(link, store);
sendMqttMessage(link, store); sendMqttMessage(link, store);
sendPagerDutyNotification(link, store); sendPagerDutyNotification(link, store);
+108
View File
@@ -0,0 +1,108 @@
import type Api from 'node-hue-api/lib/api/Api';
import {config} from '../config';
import {v3 as hueAPI} from 'node-hue-api';
import {logger} from '../logger';
const hue = config.notifications.philips_hue;
const apiKey = hue.apiKey;
const bridgeIp = hue.bridgeIp;
const lightIds = hue.lightIds;
const lightColor = hue.lightColor;
const lightPattern = hue.lightPattern;
const LightState = hueAPI.lightStates.LightState;
const clientId = hue.clientId;
const clientSecret = hue.clientSecret;
const accessToken = hue.accessToken;
const refreshToken = hue.refreshToken;
const remoteApiUsername = hue.remoteApiUsername;
// Default Light State
const lightState = new LightState()
.on(true)
.brightness(100)
.rgb(46.27, 72.55, 0);
const adjustLightsWithAPI = (hueBridge: Api) => {
logger.debug('Connected to Philips Hue bridge.');
// Set the custom light state (COLOR and METHOD here)
if (lightColor) {
const rgbArray = lightColor.split(',');
// If there's not three values, must not be RGB
if (rgbArray.length === 3) {
lightState.rgb(rgbArray[0], rgbArray[1], rgbArray[2]);
} else {
logger.debug('✖ Error assigning RGB Values');
}
}
// If blink is specified, then blink the lights
if (lightPattern === 'blink') {
lightState.alertLong();
}
// If we've been given light IDs, then only adjust those IDs
if (lightIds) {
const arrayOfIDs = lightIds.split(',');
arrayOfIDs.forEach(light => {
logger.debug('adjusting all hue lights');
(hueBridge.lights.setLightState(light, lightState) as Promise<any>).catch((error: Error) => {
logger.error('Failed to adjust all lights.');
logger.error(error);
throw error;
});
});
} else { // Adjust all light IDs
hueBridge.lights.getAll().then((allLights: any[]) => {
allLights.forEach((light: any) => {
logger.debug('adjusting specified lights');
(hueBridge.lights.setLightState(light, lightState) as Promise<any>).catch((error: Error) => {
logger.error('Failed to adjust specified lights.');
logger.error(error);
throw error;
});
});
}).catch((error: Error) => {
logger.error('Failed to get all lights.');
logger.error(error);
throw error;
});
}
};
export function adjustPhilipsHueLights() {
// Check if the required variables have been set
if (hue.apiKey && hue.bridgeIp) {
logger.info('↗ adjusting Philips Hue lights over LAN');
(async () => {
logger.debug('Attempting to connect to Philips Hue bridge at ' + bridgeIp);
hueAPI.api.createLocal(bridgeIp).connect(apiKey).then(
hueBridge => {
adjustLightsWithAPI(hueBridge);
logger.info('✔ adjusted Philips Hue lights over LAN');
},
(error: Error) => {
logger.error('✖ couldn\'t adjust hue lights.', error);
});
})();
} else if (hue.apiKey && hue.clientId && hue.clientSecret) {
logger.info('↗ adjusting Philips Hue lights over cloud');
(async () => {
logger.debug('Attempting to connect to Philips Hue bridge over cloud');
const remoteBootstrap = hueAPI.api.createRemote(clientId, clientSecret);
if (hue.accessToken && hue.refreshToken) {
remoteBootstrap.connectWithTokens(accessToken, refreshToken, remoteApiUsername)
.then(hueBridge => {
adjustLightsWithAPI(hueBridge);
logger.info('✔ adjusted Philips Hue lights over cloud');
},
(error: Error) => {
logger.error('Failed to get a remote Philips Hue connection using supplied tokens.');
logger.error(error);
throw error;
});
}
})();
} else {
logger.error('✖ couldn\'t adjust hue lights');
}
}