diff --git a/.env-example b/.env-example
index 7cd2d33..c47b0cb 100644
--- a/.env-example
+++ b/.env-example
@@ -35,6 +35,15 @@ PAGE_SLEEP_MAX=
PAGE_TIMEOUT=
PAGERDUTY_INTEGRATION_KEY=
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_NUMBER=
PLAY_SOUND=
diff --git a/README.md b/README.md
index 3503f71..07e3e88 100644
--- a/README.md
+++ b/README.md
@@ -335,7 +335,23 @@ environment variables are **optional**._
| `PAGERDUTY_SEVERITY` | Severity of PagerDuty events | Default: `info` |
+
+Philips Hue
+| 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. |
+
+
+
Pushbullet
@@ -414,8 +430,15 @@ environment variables are **optional**._
+
+
+
+
+
+
+
## FAQ
**Q: What's Node.js and how do I install it?** Visit [their website](https://nodejs.org/en/) and download and install
diff --git a/package-lock.json b/package-lock.json
index b8490e2..2ddad4e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1624,6 +1624,11 @@
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"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": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz",
@@ -4148,6 +4153,11 @@
"integrity": "sha1-mYR1wXhEVobQsyJG2l3428++jqM=",
"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": {
"version": "8.0.0",
"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",
"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": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz",
diff --git a/package.json b/package.json
index 6ac8eda..b4fbb54 100644
--- a/package.json
+++ b/package.json
@@ -32,6 +32,7 @@
"messaging-api-telegram": "^1.0.1",
"mqtt": "^4.2.4",
"node-fetch": "^2.6.1",
+ "node-hue-api": "^4.0.9",
"node-notifier": "^8.0.0",
"node-pagerduty": "^1.3.5",
"nodemailer": "^6.4.14",
diff --git a/src/config.ts b/src/config.ts
index c8e4855..e28f01d 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -151,6 +151,18 @@ const notifications = {
integrationKey: envOrString(process.env.PAGERDUTY_INTEGRATION_KEY),
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: {
availableCarriers: new Map([
['att', 'txt.att.net'],
diff --git a/src/notification/notification.ts b/src/notification/notification.ts
index 8cf9dd8..ebb017a 100644
--- a/src/notification/notification.ts
+++ b/src/notification/notification.ts
@@ -1,4 +1,5 @@
import {Link, Store} from '../store/model';
+import {adjustPhilipsHueLights} from './philips-hue';
import {playSound} from './sound';
import {sendDesktopNotification} from './desktop';
import {sendDiscordMessage} from './discord';
@@ -21,6 +22,7 @@ export function sendNotification(link: Link, store: Store) {
sendSms(link, store);
sendDesktopNotification(link, store);
// Non-priority
+ adjustPhilipsHueLights();
sendDiscordMessage(link, store);
sendMqttMessage(link, store);
sendPagerDutyNotification(link, store);
diff --git a/src/notification/philips-hue.ts b/src/notification/philips-hue.ts
new file mode 100644
index 0000000..b85d40f
--- /dev/null
+++ b/src/notification/philips-hue.ts
@@ -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).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).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');
+ }
+}