Last active
September 13, 2021 04:55
-
-
Save aeneaswiener/ca2680096992f58fb836095a55a83896 to your computer and use it in GitHub Desktop.
Philipps Hue Home Security Alarm System -- Send Hue motion sensor readings to Datadog every 5 second for alerting. Blog post https://aeneaswiener.com/smart-home-security-system-with-philipps-hue-motion-sensors-c6d461887bdb
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
""" | |
1. Create app on Philipps Hue developer portal: | |
https://developers.meethue.com/my-apps/ | |
2. Create Datadog account and get both an API key and an APP key | |
3. Export environment variables: | |
Prerequisites: | |
- Create a new separate GCP project, or use use an existing one | |
- Create a GCS bucket named GCS_AUTH_BUCKET | |
export HUE_APP_ID= | |
export HUE_CLIENT_ID= | |
export HUE_CLIENT_SECRET= | |
export DATADOG_API_KEY= | |
export DATADOG_APP_KEY= | |
export GCP_PROJECT_ID= | |
export GCS_AUTH_BUCKET= | |
4. Expose below code Google Cloud Functions | |
https://console.cloud.google.com/functions | |
- Set max timeout to 120 seconds | |
- Set HTTP endpoint to be public | |
- When pasting the code, set Entry point to 'root_handler' | |
5. Initialise oauth2 credentials by connecting to your Hue Bridge | |
echo "https://api.meethue.com/oauth2/auth?clientid=$HUE_CLIENT_ID&appid=$HUE_APP_ID&deviceid=my-device-id&response_type=code" | |
- Run in browser to authorise access to your Hue Bridge | |
- The redirect will save Hue oauth2 credentials to GCS_AUTH_BUCKET | |
6. Schedule for your function to be called every minute by Google Cloud Scheduler | |
https://console.cloud.google.com/cloudscheduler | |
- https://<YOUR-CLOUD-FUNCTION_ENDPOINT>/?report_to_datadog=True | |
8. Go to the Datadog Metrics Explorer to view your sensor data | |
https://app.datadoghq.eu/metric/explorer | |
- Search for 'temperature' or 'presence' | |
- All metrics are tagged by 'sensor_name' | |
Project blog post: | |
https://aeneaswiener.com/smart-home-security-system-with-philipps-hue-motion-sensors-c6d461887bdb | |
""" | |
from flask import Flask, request | |
from slugify import slugify | |
from datadog import initialize, threadstats | |
from google.cloud import storage | |
import requests | |
import json | |
import os | |
import time | |
DATADOG_API_KEY = os.environ.get('DATADOG_API_KEY') | |
DATADOG_APP_KEY = os.environ.get('DATADOG_APP_KEY') | |
HUE_CLIENT_ID = os.environ.get('HUE_CLIENT_ID') | |
HUE_CLIENT_SECRET = os.environ.get('HUE_CLIENT_SECRET') | |
GCP_PROJECT_ID = os.environ.get('GCP_PROJECT_ID') | |
GCS_AUTH_BUCKET = os.environ.get('GCS_AUTH_BUCKET') | |
# We are triggering GET /?action=report_to_datadog with the Google Cloud Scheduler which can trigger at most once per | |
# minute. To overcome this we are making every request last 60 seconds, sending 12 readings (one every 5 seconds) | |
NUM_HUE_POLLS_PER_REQUEST = 12 | |
TARGET_REQUEST_DURATION_SECONDS = 60 | |
client = storage.Client(project=GCP_PROJECT_ID) | |
auth_bucket = client.get_bucket(GCS_AUTH_BUCKET) | |
def handle_hue_oauth2_callback(request): | |
auth_url = 'https://api.meethue.com/oauth2/token?code=' + request.args.get( | |
'code') + '&grant_type=authorization_code' | |
auth_resp = requests.post(auth_url, data={}, auth=(HUE_CLIENT_ID, HUE_CLIENT_SECRET)).json() | |
access_token = auth_resp.get('access_token') | |
refresh_token = auth_resp.get('refresh_token') | |
whitelist_url = 'https://api.meethue.com/bridge/0/config' | |
_ = requests.put(whitelist_url, json={"linkbutton": True}, | |
headers={"Authorization": f'Bearer {access_token}'}).json() | |
user_url = 'https://api.meethue.com/bridge/' | |
user_resp = requests.post(user_url, json={"devicetype": "my-helios-backend"}, | |
headers={"Authorization": f'Bearer {access_token}'}).json() | |
username = user_resp[0].get('success').get('username') | |
auth_data = { | |
"access_token": access_token, | |
"refresh_token": refresh_token, | |
"username": username | |
} | |
blob = auth_bucket.blob('auth_data.json') | |
blob.upload_from_string(json.dumps(auth_data), content_type='application/json') | |
return {'success': True} | |
def get_hue_oauth2_token_and_refresh(): | |
blob = auth_bucket.blob('auth_data.json') | |
auth_data = json.loads(blob.download_as_string()) | |
refresh_url = 'https://api.meethue.com/oauth2/refresh?grant_type=refresh_token' | |
refresh_resp = requests.post(refresh_url, data={"refresh_token": auth_data['refresh_token']}, | |
auth=(HUE_CLIENT_ID, HUE_CLIENT_SECRET)).json() | |
auth_data['access_token'] = refresh_resp['access_token'] | |
auth_data['refresh_token'] = refresh_resp['refresh_token'] | |
blob = auth_bucket.blob('auth_data.json') | |
blob.upload_from_string(json.dumps(auth_data), content_type='application/json') | |
return auth_data | |
def send_hue_readings_to_datadog(threadstats_agent, hue_auth_data): | |
sensors_url = f'https://api.meethue.com/bridge/{hue_auth_data["username"]}/sensors' | |
auth_header = {"Authorization": f'Bearer {hue_auth_data["access_token"]}'} | |
resp_sensors = requests.get(sensors_url, headers=auth_header).json() | |
for sensor in resp_sensors.values(): | |
sensor_name = slugify(sensor['name']) | |
for state_key, state_value in sensor['state'].items(): | |
try: | |
state_value_numeric = float(state_value) | |
sensor_type = slugify(sensor['type']) | |
threadstats_agent.gauge(f'hue.sensor.{sensor_type}.{state_key}', | |
state_value_numeric, | |
tags=[f"sensor_name:{sensor_name}"]) | |
except ValueError: | |
pass | |
def init_threadstats_agent(): | |
datadog_options = { | |
'api_key': DATADOG_API_KEY, | |
'app_key': DATADOG_APP_KEY, | |
'api_host': 'https://api.datadoghq.eu' | |
} | |
initialize(**datadog_options) | |
threadstats_agent = threadstats.base.ThreadStats() | |
threadstats_agent.start() | |
return threadstats_agent | |
def root_handler(request): | |
if request.args.get('action') == 'hue_oauth2_callback': | |
status = handle_hue_oauth2_callback(request=request) | |
return status | |
elif request.args.get('action') == 'report_to_datadog': | |
threadstats_agent = init_threadstats_agent() | |
hue_auth_data = get_hue_oauth2_token_and_refresh() | |
for i in range(NUM_HUE_POLLS_PER_REQUEST): | |
send_hue_readings_to_datadog(threadstats_agent, hue_auth_data) | |
time.sleep(TARGET_REQUEST_DURATION_SECONDS/NUM_HUE_POLLS_PER_REQUEST) | |
return {'success': True} | |
# For local debugging, this can be left in when uploading to Google Cloud Functions | |
app = Flask(__name__) | |
@app.route('/') | |
def root_handler_flask(): | |
return root_handler(request) | |
if __name__ == '__main__': | |
app.run(debug=True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment