mirror of
https://github.com/opelly27/streetmerchant.git
synced 2026-05-20 13:27:38 +00:00
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:
@@ -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=
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Generated
+20
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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'],
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user