Skip to content

Instantly share code, notes, and snippets.

@aeneaswiener
Last active September 13, 2021 04:55
Show Gist options
  • Save aeneaswiener/ca2680096992f58fb836095a55a83896 to your computer and use it in GitHub Desktop.
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
"""
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