This repository has been archived by the owner on Oct 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial commit with support for receiving alert status from your Datadog monitors and flash your Philips Hue lights to let you know. Lights will flash red for critical alerts, or orange for warnings and then green when all monitors are resolved. Has the option to only flash Philips Hue Light Bulbs during active hours so you don't get woken up :)
- Loading branch information
Chris Board
committed
Jan 18, 2020
0 parents
commit 86f1eb0
Showing
3 changed files
with
348 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,241 @@ | ||
from calendar import calendar | ||
from datetime import datetime | ||
import calendar | ||
import time | ||
|
||
import requests | ||
from datadog import initialize, api | ||
import json | ||
from phue import Bridge | ||
from enum import Enum | ||
|
||
|
||
class BulbColour(Enum): | ||
RED = 1 | ||
ORANGE = 2 | ||
GREEN = 3 | ||
|
||
|
||
def discover_ip(): | ||
response = requests.get('https://discovery.meethue.com') | ||
|
||
# process response | ||
|
||
if response and response.status_code == 200: | ||
data = response.json() | ||
|
||
if 'internalipaddress' in data[0]: | ||
return data[0]['internalipaddress'] | ||
return None | ||
|
||
|
||
def is_during_active_hours(options): | ||
|
||
# Check if the flag for alerting only during active hours is set. If its set to 0, just return True | ||
# as user wants alerted 24/7 | ||
|
||
if options['alert_active_hours_only'] == 0: | ||
return True | ||
|
||
# Get the current epoch time | ||
current_epoch = time.time() | ||
current_date = str(datetime.date(datetime.now())) | ||
|
||
start_active_hours_epoch = calendar.timegm(time.strptime(current_date + ' ' + options['start_active_hours'], | ||
'%Y-%m-%d %H:%M')) | ||
end_active_hours_epoch = calendar.timegm(time.strptime(current_date + ' ' + options['end_active_hours'], | ||
'%Y-%m-%d %H:%M')) | ||
|
||
if current_epoch >= start_active_hours_epoch and current_epoch <= end_active_hours_epoch: | ||
return True | ||
else: | ||
return False | ||
|
||
|
||
def flash_lights(bulb_colour): | ||
options = { | ||
'alert_active_hours_only': 1, | ||
'start_active_hours': '09:00', | ||
'end_active_hours': '22:00' | ||
} | ||
|
||
if not is_during_active_hours(options): | ||
print("Not during active hours so not flashing lights") | ||
# Return False to tell the main function there was no alert done so the config file | ||
# doesn't get updated. This allows that if the monitor is still active when it runs again | ||
# during active hours the bulbs are flashed | ||
return False | ||
|
||
print("Flashing lights as during active hours or active hours only alerting is disabled") | ||
|
||
lights = b.lights | ||
|
||
if bulb_colour == BulbColour.RED: | ||
hue = 64015 | ||
saturation = 254 | ||
elif bulb_colour == BulbColour.ORANGE: | ||
hue = 8382 | ||
saturation = 252 | ||
elif bulb_colour == BulbColour.GREEN: | ||
hue = 25652 | ||
saturation = 254 | ||
|
||
# Need to store the existing light settings so we can revert the lights back after flashing | ||
original_light_states = [] | ||
# Get the colour light indexes so can update the hue and sat without receiving an error setting a non colour bulb | ||
colour_light_ids = [] | ||
|
||
# Need a list of all light id so can control all lights on/off state together | ||
all_light_ids = [] | ||
|
||
for light in lights: | ||
light_state = {} | ||
light_state['id'] = light.light_id | ||
light_state['name'] = light.name | ||
light_state['on'] = light.on | ||
light_state['brightness'] = light.brightness | ||
if light.type == 'Extended color light': | ||
colour_light_ids.append(light.light_id) | ||
light_state['hue'] = light.hue | ||
light_state['sat'] = light.saturation | ||
light_state['xy'] = light.xy | ||
|
||
original_light_states.append(light_state) | ||
all_light_ids.append(light.light_id) | ||
|
||
# Flash each light on an off every 1 second for 3 seconds | ||
bulb_on_state = 40 | ||
b.set_light(all_light_ids, 'on', True) | ||
# Set the bulbs to the alert colour | ||
b.set_light(colour_light_ids, 'hue', hue) | ||
b.set_light(colour_light_ids, 'sat', saturation) | ||
time.sleep(0.3) | ||
for i in range(8): | ||
b.set_light(all_light_ids, 'bri', bulb_on_state) | ||
# Set bulb state to opposite of current state | ||
if bulb_on_state == 40: | ||
bulb_on_state = 254 | ||
else: | ||
bulb_on_state = 40 | ||
|
||
time.sleep(0.3) | ||
|
||
# Now restore the states of the bulbs | ||
# Because each bulb might and most likely different settings each bulb has to be individually updated | ||
for light in lights: | ||
light_id = light.light_id | ||
|
||
# Loop over the original states looking for this id and if found update the buld to the correct settings | ||
for state in original_light_states: | ||
if state['id'] == light_id: | ||
light.on = state['on'] | ||
if state['on']: | ||
light.brightness = state['brightness'] | ||
if light.type == 'Extended color light': | ||
light.hue = state['hue'] | ||
light.saturation = state['sat'] | ||
light.xy = state['xy'] | ||
break | ||
# Return true so the main method knows the lights were flashed so user was updated | ||
# therefore update the config file so they don't get alerted again | ||
return True | ||
|
||
|
||
hue_bridge_ip = discover_ip() | ||
print("Hue Bridge IP: " + hue_bridge_ip) | ||
if hue_bridge_ip is None: | ||
print("The IP address of Philips Hue Bridge could not be found. Please provide the IP address manually") | ||
exit(0) | ||
|
||
b = Bridge(hue_bridge_ip) | ||
b.connect() | ||
|
||
dd_options = { | ||
'api_key': '', | ||
'app_key': '' | ||
} | ||
|
||
# Check if the count file exists if not create a default one | ||
try: | ||
f = open("alert_count.json") | ||
except IOError: | ||
counts_json = '{"warn_count": 0, "alert_count": 0}' | ||
f = open("alert_count.json", "w") | ||
f.write(counts_json) | ||
f.close() | ||
|
||
initialize(**dd_options) | ||
|
||
monitors = api.Monitor.get_all() | ||
|
||
alert_count = 0 | ||
warn_count = 0 | ||
ok_count = 0 | ||
|
||
for monitor in monitors: | ||
monitorName = monitor['name'] | ||
monitorStatus = monitor['overall_state'] | ||
if monitorStatus == 'OK': | ||
ok_count += 1 | ||
elif monitorStatus == 'Alert': | ||
alert_count += 1 | ||
elif monitorStatus == 'Warn': | ||
warn_count += 1 | ||
|
||
print('Monitor Name: ' + monitorName + " Status: " + monitorStatus) | ||
|
||
print("OK Count: " + str(ok_count)) | ||
print("Warn Count: " + str(warn_count)) | ||
print("Alert Count: " + str(alert_count)) | ||
|
||
with open('alert_count.json') as json_file: | ||
counts_json = json.load(json_file) | ||
|
||
file_warn_counts = counts_json['warn_count'] | ||
file_alert_counts = counts_json['alert_count'] | ||
|
||
were_lights_updated = False | ||
|
||
if warn_count > 0 or alert_count > 0: | ||
# Check the alert_count.json file, if the counts in the file are 0, not previously alerted so flash the lights | ||
|
||
if file_warn_counts == 0 and file_alert_counts == 0: | ||
# Need to flash the lights - check what the wost alert level is | ||
if alert_count > 0: | ||
# Need to flash the lights red | ||
print("Flashing philips hue to be red") | ||
were_lights_updated = flash_lights(BulbColour.RED) | ||
elif warn_count > 0: | ||
# Need to flash the lights orange | ||
print("Flashing philips hue to be orange") | ||
were_lights_updated = flash_lights(BulbColour.ORANGE) | ||
elif file_warn_counts > 0 or file_alert_counts > 0: | ||
if file_alert_counts > 0 and alert_count == 0 and warn_count > 0: | ||
# If here, then previously alerted red but red alert recovered and now on warning | ||
# so flash philips hue orange instead | ||
print("alert recovered so flashing orange instead") | ||
were_lights_updated = flash_lights(BulbColour.ORANGE) | ||
elif (file_alert_counts > 0 or file_warn_counts) and (alert_count > 0 or warn_count > 0): | ||
# The file has some alerts and warning counts and there are still current alert and warning | ||
# counts so don't do anything with the lights | ||
print("no alerts and warnings recovered so not flashing the lights") | ||
else: | ||
print("Previously alerted and alerts are still happening so don't do anything with lights") | ||
|
||
else: | ||
# There are no warnings and no errors check the file counts, if any are over 0, the monitors are therefore | ||
# recovered so flash green | ||
if file_alert_counts > 0 or file_warn_counts > 0: | ||
# The file did have an alert count the last time it run, so flash the lights green | ||
print("Everything recovered so flash lights green") | ||
were_lights_updated = flash_lights(BulbColour.GREEN) | ||
|
||
if were_lights_updated: | ||
# Save a JSON file of the current counts. This will be used so that if the file already shows the counts for non OK | ||
# are > 1 so don't inadvertently keep flashing the users lights | ||
counts_json = '{"warn_count": ' + str(warn_count) + ', "alert_count":' + str(alert_count) + '}' | ||
f = open("alert_count.json", "w") | ||
f.write(counts_json) | ||
f.close() | ||
|
||
exit(0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
# DDHueAlert | ||
![image alt="logo" style="float: left"](logo.png) | ||
|
||
This is a small python script that you can run as a scheduled task using either the | ||
Windows Scheduler or as Linux Cron jobs and can flash your Philips Hue | ||
(https://www2.meethue.com/en-gb) based on the status of the monitors you | ||
have set up in your Datadog (https://datadoghq.com) account. | ||
|
||
Below is described how to get started. | ||
|
||
# Installing the Script | ||
You need to have Python 3.x installed and need to run the following dependencies | ||
``` | ||
pip install requests | ||
pip install datadog | ||
pip install phue | ||
``` | ||
|
||
*Details about the library are below* | ||
|
||
Once installed you need to press the button on your Philips Hue Bridge and then | ||
run your script once within 30 seconds (`python DDMonitorCheck.py`) to authorise DDHueAlert script with your | ||
Philips Hue Bridge - the script will automatically find your bridge IP - if it throws an | ||
exception when you run the script, then likely the python script wasn't executed within | ||
30 seconds of the button on top of the bridge being pressed. | ||
|
||
# Setting up | ||
Now that you have the script installed and authorised with your bridge now its | ||
time to set up your Datadog API key and app key and the options for when you want to | ||
be alerted. | ||
|
||
1. In the main method towards the bottom of the script | ||
you should see an object created called dd_options. This contains two keys | ||
api_key and app_key. Fill these in with your key, they can be retrieved | ||
from your account on the datadog website (https://app.datadoghq.com/account/settings#api). | ||
Its recommended to create a new API key and App Key that is used for this script. | ||
|
||
2. There's an option object in the method `flash_lights`. This contains | ||
some options on when you should be alerted, for example, you can have it set up | ||
to always flash your lights when a triggered Datadog monitor is found or only during | ||
active monitoring hours so you don't get woken up. By default active hours alerting is | ||
turned on and the active hours are between 09:00 and 22:00. You can amend these | ||
to your needs (times have to be in 24 hour format) or you can set `alert_active_hours_only` | ||
to '0' so that you always get an alert. | ||
|
||
# How does it work? | ||
The script runs once and finishes. It could be done as a loop and keeps going to | ||
sleep but thought run once and finish was the best option as you don't need | ||
to worry about creating start up scripts or worry about the script dying for some reason | ||
and not restarting. | ||
|
||
You can run it as often as you like, but bear in mind there may be some usage limits | ||
with the Datadog API. I've been making it run every 5 minutes on a Linux server | ||
in a cron job. | ||
|
||
When the script runs, it connects to the bridge to ensure its authenticated. | ||
It then checks all of the monitors under your datacount account and creates | ||
a count of warning and critical alerts. If there's warnings and critical alerts | ||
the lights will flash red and then revert to their original state. | ||
|
||
The script updates a file in the path of the script called alert_count.json. | ||
This provides a history of what was triggered the last time it ran. | ||
|
||
If the script runs again and previously the critical count > 0 and the warning count | ||
was greater than 0, but now the critical count = 0, then the lights flash orange | ||
to show that state has now changed to warning. | ||
|
||
When the script runs again, if one or more of the counts were greater than 0 on the | ||
last run, but now both counts retrieved are now 0, then the lights flash green so you | ||
know everythings been resolved. | ||
|
||
The file stops the lights flashing every single time it runs so please | ||
make sure that where the script runs from has read/write access to that directory | ||
to avoid flashing your Philips Hue lights unnecessarily. | ||
|
||
# What if I Don't Have Philips Hue Colour Bulbs? | ||
Its no problem, the script will still work, obviously won't show the colours though? | ||
|
||
The script goes through all bulbs, light strips ets and checks their type. If they are detected | ||
as a colour bulb then they will have their colour updated to match the datadog monitor severity. If the | ||
bulb is detected as a non colour bulb, e.g. Philips Hue Ambient White Light Bulb, then the bulb will just | ||
flash. | ||
|
||
# Thanks to the following libraries | ||
**Requests - https://pypi.org/project/requests/**: Used for sending API | ||
requests to the Datadog API. | ||
|
||
**Datadog - https://docs.datadoghq.com/integrations/python/:** Python | ||
library to access your Datadog account via Datadog's API | ||
|
||
**phue - https://github.com/studioimaginaire/phue**: Python library | ||
for interacting with the Philips Hue API. | ||
|
||
*This project is in no way affiliated with Datadog (https://datadoghq.com) or | ||
Philips Hue (https://meethue.com).* | ||
|
||
*Boardies IT Solutions can take no responsibility for any unexpected or unintended | ||
additional costs from your Datadog Account or any unexpected or unintended | ||
damage to your Philips Hue devices or property. This is provided as is and there are | ||
no guarantees or warranties associated with this project* | ||
|
||
If you need any help, then please feel free to contact me either via here or by raising | ||
a support ticket at https://support.boardiesitsolutions.com. | ||
|
||
Boardies IT Solutions - Copyright © 2020 | ||
|
||
<img src="https://boardiesitsolutions.com/images/logo.png"> |