בעוונותי אני בעלים לא מאוד גאה של כמה נורות חכמות של Philips Hue, הנורות האלו שמתחברות ל-WiFi בבית ואז אפשר לשלוט עליהן דרך האפליקציה של פיליפס בטלפון.

בנוסף, לא מזמן יצא לי לבנות בעצמי Macropad למחשב (הפרויקט הזה). Macropad הוא מעין אוסף מקשי מקלדת שמתחבר למחשב בנוסף למקלדת רגילה, כאשר את המקשים ניתן לתכנת כרצוננו לבצע כל פעולה במחשב שנרצה. אני שמח לספר שהפרויקט יצא לי ממש טוב.

חשבתי שזה יכול להיות מגניב אם אצליח לתכנת את אחד הכפתורים לשלוט על התאורה החכמה בחדר. למזלי, פיליפס הואילו בטובם לשחרר את מוצרי ה-Hue שלהם עם API די נוח לשימוש ואפילו כתבו לו תיעוד די סבבה.

איך זה בעצם עובד?

בניגוד לסדרות תאורה חכמה של חברות אחרות (למשל Yeelight) שבהן כל נורה מתחברת באופן עצמאי לרשת האלחוטית הביתית, אצל פיליפס הנורות מגיעות עם מעין רכזת (Hub) – מה שפיליפס קוראים לו Hue Bridge. הארכיטקטורה של המוצר בנויה ככה שכל אחת מהנורות מתחברת אלחוטית אל ה-Bridge, כשהוא למעשה מתחבר קווית לנתב הביתי. ה-Bridge מנהל את כל הנורות ומילולית מגשר ביניהן לבין האפליקציה. באופן זה, התכנה מתנהלת מול רכיב יחיד עם כתובת IP יחידה, מה שהופך את הכל ליותר פשוט ומונע סרבול רב.

מבחינת אבטחה, לגשר יש כפתור עליו צריך ללחוץ כדי לאשר הוספת נורות חדשות לרשת וחיבור של משתמשים נוספים, דבר שמעט מזכיר כפתורי WPS בראוטרים.

חומר שיווקי של פיליפס

כאשר מנסים להתחבר לכתובת ה-IP המקומית של ה-Bridge דרך דפדפן אינטרנט, נפתח מעין ממשק אינטרנטי לשליחת בקשות HTTP אל הגשר. כלומר, למכשיר קיים ממשק תכנות אינטרנטי (REST API) בעזרתו ניתן לתכנת בעצמנו את המנורות.

בעזרת המדריך הרשמי הזה של פיליפס יצרתי סיסמה איתה אפשר לקבל גישה מאובטחת למנורות, ומהרגע הזה אפשר לשחק איתן כרצוננו. לצורך ההדגמה נניח שכתובת ה-IP של הגשר היא 192.168.1.100 והסיסמה היא XXXX. לכן הכתובת אליה נשלח בקשות HTTP היא

http://192.168.1.100/api/XXXX

נשלח בקשת GET כללית והתשובה שנקבל תראה כמו משהו כזה:

{
	"lights": {
		"6": {
			"state": {
				"on": true,
				"bri": 254,
				"hue": 46987,
				"sat": 254,
				"effect": "none",
				"xy": [
					0.3691,
					0.3719
				],
				"ct": 233,
				"alert": "select",
				"colormode": "ct",
				"mode": "homeautomation",
				"reachable": true
			}
			"type": "Extended color light",
			"manufacturername": "Signify Netherlands B.V.",
			"productname": "Hue color lamp",
		},
		"8": {
			"state": {
				"on": true,
				"bri": 254,
				"hue": 33090,
				"sat": 61,
				"effect": "none",
				"xy": [
					0.367,
					0.3707
				],
				"ct": 230,
				"alert": "select",
				"colormode": "ct",
				"mode": "homeautomation",
				"reachable": true
			},
			"type": "Extended color light",
			"manufacturername": "Signify Netherlands B.V.",
			"productname": "Hue color lamp",
		},
		"9": {
			"state": {
				"on": true,
				"bri": 254,
				"hue": 33090,
				"sat": 61,
				"effect": "none",
				"xy": [
					0.367,
					0.3707
				],
				"ct": 230,
				"alert": "select",
				"colormode": "ct",
				"mode": "homeautomation",
				"reachable": true
			},
			"type": "Extended color light",
			"manufacturername": "Signify Netherlands B.V.",
			"productname": "Hue color lamp",
		},
		"11": {
			"state": {
				"on": true,
				"bri": 254,
				"hue": 33090,
				"sat": 61,
				"effect": "none",
				"xy": [
					0.367,
					0.3707
				],
				"ct": 230,
				"alert": "select",
				"colormode": "ct",
				"mode": "homeautomation",
				"reachable": true
			},
			"type": "Extended color light",
			"manufacturername": "Signify Netherlands B.V.",
			"productname": "Hue color lamp",
		}
	}
}

קיבלנו רשימה בפורמט JSON של כל הנורות שמוגדרות בגשר, ואת המידע שיש לגשר עליהן. בין היתר מה הצבע שהנורות מאירות בו ברגע שליחת הבקשה, מה עוצמת ההארה ואפילו פרטים על המפעל בו כל נורה יוצרה. השורות המודגשות מציינות את המספרים הסידוריים (ID) של הנורות, והאם הן דולקות. קל לראות שכל 4 הנורות דולקות בצבע לבן.

נגיד ונרצה לכבות את נורה 9. לשם כך נצטרך לגשת לאובייקט state שבתוך האובייקט 9 שבתוך האובייקט lights, ולשנות את ערך השדה on מ-true ל-false. לכן כתובת האובייקט אליו נשלח את הבקשה היא http://192.168.1.100/api/XXXX/lights/9/state. גוף ההודעה יהיה {"on": true} וסוג הבקשה יהיה PUT (כיוון שאנחנו רוצים לערוך מידע קיים).

עכשיו נרצה להדליק את הנורה בחזרה, אבל גם לשנות את הצבע שלה. נשלח את אותה בקשה כאשר הפעם גוף ההודעה יהיה {"on":true, "sat":254, "bri":254,"hue":0} וכאילו באורח פלא הנורה תידלק בחזרה, הפעם באור אדום.

המטרה שלי היא שכפתור ב-macropad יכבה או ידליק את כל הנורות מבלי לשנות את שאר המאפיינים שלהן. על מנת לבצע פעולה על מספר נורות, ניתן לגשת לקבוצות של נורות בכתובת http://192.168.1.100/api/XXXX/groups. הקבוצה שמספרה הסידורי 0 קיימת תמיד בגשר, ומכילה את כל הנורות. על מנת לכבות את כל הנורות נשלח לכתובת http://192.168.1.100/api/XXXX/groups/0/action את הבקשה {"on":false} ועל מנת להדליק נשלח לאותה כתובת את הבקשה {"on":true}, שתיהן כמובן מסוג PUT.

עכשיו כשאנחנו יודעים אילו בקשות נצטרך לשלוח, נצטרך להחליט איך נשלח אותן ואיך השליחה תקרה בעת לחיצה על כפתור ב-macropad. לא אכנס פה לדקויות של אופן הפעולה של ה-macropad, אבל בשורה התחתונה מבחינתנו כל מה שהוא יודע לעשות זה לדמות רצף של הקשות וירטואליות על מקשי המקלדת. כלומר, הוא לא יודע לשלוח בעצמו בקשות HTTP אבל הוא כן יודע לדמות הקלדה במקלדת.

אני יודע שב-Windows קל לקנפג קיצור מקשים מותאם-אישית כדי להפעיל קיצור-דרך לכל קובץ שנרצה במחשב. לכן לדעתי דרך טובה לפעול היא לתכנת סקריפט קצר בפייתון שישלח בשבילי בקשות HTTP למנורות, ליצור לו קיצור דרך ולקנפג קיצור-מקשים שמפעיל את קיצור הדרך. לבסוף, נתכנת את ה-macropad כך שלחיצה על הכפתור הרצוי תדמה את הקשת קיצור-המקשים שבחרנו.

נשמע קצת מסורבל אבל בפועל כל השלבים האלו אמורים לקרות מאחורי הקלעים במהירות גבוהה מאוד, ולכן הפעולה אמורה להתבצע מיידית.

ניצור קובץ פייתון באיזה מיקום שנרצה במחשב שלנו. אני קראתי לקובץ שלי hue.pyw. סיומת הקובץ היא .pyw במקום .py על מנת שהסקריפט ירוץ מאחורי הקלעים, ולא יפתח לנו טרמינל בפייתון בזמן הריצה שלו.

נתחיל בייבוא ספריות.

import requests, json

בעזרת הספריה requests נשלח את בקשות ה-HTTP והספרייה json תעזור לנו לקרוא את התשובות שהבקשות יחזירו.

כעת נגדיר קבועים שיחזיקו את כתובות ה-URL אליהן נצטרך לשלוח את הבקשות.

PUT_URL = "http://192.168.1.100/api/XXXX/groups/1/action"
GET_URL = "http://192.168.1.100/api/XXXX/groups/1/"

PUT_URL מכילה את הכתובת אליה נשלח את בקשות הכיבוי וההדלקה. GET_URL מכילה את הכתובת ממנה נשיג מידע על המצב הנוכחי של הנורות.

נגדיר קבועים שיחזיקו את גוף הבקשה, הן עבור הדלקה והן עבור כיבוי

ON_PARAMS = {"on": True}
OFF_PARAMS = {"on": False}

לפני שנשלח פקודות לגשר, אנחנו צריכים לדעת איזו פקודה לשלוח – אם להדליק את הנורות או לכבות אותן. בשביל זה נצטרך לדעת מה מצבן הנוכחי. החלטתי שכל הנורות יכובו אם לפחות נורה אחת דולקת, אחרת כל הנורות יידלקו. למזלנו, קבוצת הנורות 0 מכילה את האובייקט state אשר מחזיק בין היתר את המאפיין הבוליאני any_on, שבאופן אינטואיטיבי חיובי כאשר לפחות אחת מהנורות דולקת, ושלילי אחרת. כדי להשיג את המידע הזה נשלח בקשת GET לכתובת שהגדרנו ב-GET_URL ונשמור את התשובה במשתנה res. לאחר מכן נשתמש בספרייה json כדי לחלץ את הערך של any_on ונשמור אותו למשתנה בעל שם זהה.

res = requests.get(GET_URL)
response_dict = json.loads(res.text)
any_on = response_dict["state"]["any_on"]

לבסוף, נשלח את הבקשה המתאימה לערך של any_on.

if any_on:
    r = requests.put(url = PUT_URL, json = OFF_PARAMS)
else:
    r = requests.put(url = PUT_URL, json = ON_PARAMS)

הקוד השלם יראה כך:

import requests, json

PUT_URL = "http://192.168.1.100/api/XXXX/groups/1/action"
GET_URL = "http://192.168.1.100/api/XXXX/groups/1/"

ON_PARAMS = {"on": True}
OFF_PARAMS = {"on": False}

res = requests.get(GET_URL)
response_dict = json.loads(res.text)
any_on = response_dict["state"]["any_on"]

if any_on:
    r = requests.put(url = PUT_URL, json = OFF_PARAMS)
else:
    r = requests.put(url = PUT_URL, json = ON_PARAMS)

כשמריצים את הקוד הוא עובד מעולה והאורות נכבים ונדלקים כמצופה. עכשיו נדאג להפעלת הקוד בעת לחיצה על מקש macropad כרצוננו.

ניצור קיצור דרך לקובץ הפייתון שלנו ונמקם אותו בתוך התיקייה

C:\ProgramData\Microsoft\Windows\Start Menu\Programs

המיקום הספציפי הזה חשוב, וקיצור המקשים שנגדיר בהמשך לא יפעל בהכרח עבור מיקומים אחרים.

נלחץ מקש ימני על קיצור הדרך וניכנס למאפיינים. בשדה Shortcut key נבחר קיצור מקשים כרצוננו. אני בחרתי Ctrl+Alt+H. נאשר.

כל מה שנותר הוא לקנפג שלחיצה על מקש כלשהו ב-macropad תשלח למחשב את הקיצור Ctrl+Alt+H והאורות יגיבו כמו קסם! קול.


את התיעוד המלא ל-API של פיליפס אפשר למצוא פה.