Added first working version of the library; updated README, .gitignore and LICENSE
This commit is contained in:
parent
b9b1620e89
commit
46de665176
7 changed files with 208 additions and 2 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -158,3 +158,5 @@ cython_debug/
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
test.py
|
||||||
|
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2023 Julian Müller
|
Copyright (c) 2023 Julian Müller (ChaoticByte)
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
# YaHueLib
|
# YaHueLib
|
||||||
Yet Another Philips Hue API Library
|
|
||||||
|
Yet Another Philips Hue API Library for Python. This project only implements a subset of the API.
|
||||||
|
|
0
yahuelib/__init_.py
Normal file
0
yahuelib/__init_.py
Normal file
182
yahuelib/controller.py
Normal file
182
yahuelib/controller.py
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
# Copyright (c) 2023 Julian Müller (ChaoticByte)
|
||||||
|
|
||||||
|
|
||||||
|
from json import dumps as _json_dumps
|
||||||
|
from json import loads as _json_loads
|
||||||
|
from urllib import request as _request
|
||||||
|
|
||||||
|
|
||||||
|
from .exceptions import *
|
||||||
|
|
||||||
|
|
||||||
|
# Have to use the following SSL context because the
|
||||||
|
# TLS certificate of a Hue Bridge is self signed
|
||||||
|
ssl_context_unverified = _request.ssl._create_unverified_context()
|
||||||
|
|
||||||
|
|
||||||
|
class _BaseController:
|
||||||
|
|
||||||
|
_api_endpoint_all = ""
|
||||||
|
_api_endpoint_specific = ""
|
||||||
|
|
||||||
|
def __init__(self, number:int, bridge_ip_address:str, bridge_api_user:str):
|
||||||
|
assert type(number) == int
|
||||||
|
assert type(bridge_ip_address) == str
|
||||||
|
assert type(bridge_api_user) == str
|
||||||
|
self.number = number
|
||||||
|
self.bridge_ip_address = bridge_ip_address
|
||||||
|
self.bridge_api_user = bridge_api_user
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_name(cls, name:str, bridge_ip_address:str, bridge_api_user:str):
|
||||||
|
assert type(bridge_ip_address) == str
|
||||||
|
assert type(bridge_api_user) == str
|
||||||
|
assert type(name) == str
|
||||||
|
api_request = _request.Request(cls._api_endpoint_all.format(
|
||||||
|
bridge_ip_address=bridge_ip_address,
|
||||||
|
bridge_api_user=bridge_api_user))
|
||||||
|
with _request.urlopen(api_request, context=ssl_context_unverified) as r:
|
||||||
|
data = _json_loads(r.read())
|
||||||
|
for n in data:
|
||||||
|
if data[n]["name"] == name:
|
||||||
|
return cls(int(n), bridge_ip_address, bridge_api_user)
|
||||||
|
raise LightOrGroupNotFound()
|
||||||
|
|
||||||
|
def _api_request(self, method="GET", path:str="", data:dict={}):
|
||||||
|
assert type(method) == str
|
||||||
|
assert type(path) == str
|
||||||
|
assert type(data) == dict
|
||||||
|
api_request = _request.Request(
|
||||||
|
self._api_endpoint_specific.format(
|
||||||
|
bridge_ip_address=self.bridge_ip_address,
|
||||||
|
bridge_api_user=self.bridge_api_user,
|
||||||
|
number=self.number
|
||||||
|
) + path,
|
||||||
|
method=method,
|
||||||
|
data=_json_dumps(data).encode())
|
||||||
|
with _request.urlopen(api_request, context=ssl_context_unverified) as r:
|
||||||
|
response_data = _json_loads(r.read())
|
||||||
|
if type(response_data) == list and len(response_data) > 0:
|
||||||
|
if "error" in response_data[0]:
|
||||||
|
raise APIError(response_data)
|
||||||
|
return response_data
|
||||||
|
|
||||||
|
|
||||||
|
class LightController(_BaseController):
|
||||||
|
|
||||||
|
_api_endpoint_all = "https://{bridge_ip_address}/api/{bridge_api_user}/lights"
|
||||||
|
_api_endpoint_specific = "https://{bridge_ip_address}/api/{bridge_api_user}/lights/{number}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def reachable(self) -> bool:
|
||||||
|
data = self._api_request()
|
||||||
|
return data["state"]["reachable"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on(self) -> bool:
|
||||||
|
data = self._api_request()
|
||||||
|
return data["state"]["on"]
|
||||||
|
|
||||||
|
@on.setter
|
||||||
|
def on(self, on:bool):
|
||||||
|
assert type(on) == bool
|
||||||
|
self._api_request("PUT", "/state", {"on": on})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def brightness(self):
|
||||||
|
data = self._api_request()
|
||||||
|
return data["state"]["bri"]
|
||||||
|
|
||||||
|
@brightness.setter
|
||||||
|
def brightness(self, brightness:float):
|
||||||
|
assert type(brightness) == float or type(brightness) == int
|
||||||
|
bri_ = min(max(int(brightness * 254), 0), 254)
|
||||||
|
self._api_request("PUT", "/state", {"bri": bri_})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hue(self):
|
||||||
|
data = self._api_request()
|
||||||
|
return data["state"]["hue"]
|
||||||
|
|
||||||
|
@hue.setter
|
||||||
|
def hue(self, hue:float):
|
||||||
|
assert type(hue) == float or type(hue) == int
|
||||||
|
hue_ = min(max(int(hue * 65535), 0), 65535)
|
||||||
|
self._api_request("PUT", "/state", {"hue": hue_})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def saturation(self):
|
||||||
|
data = self._api_request()
|
||||||
|
return data["state"]["sat"]
|
||||||
|
|
||||||
|
@saturation.setter
|
||||||
|
def saturation(self, saturation:float):
|
||||||
|
assert type(saturation) == float or type(saturation) == int
|
||||||
|
sat_ = min(max(int(saturation * 254), 0), 254)
|
||||||
|
self._api_request("PUT", "/state", {"sat": sat_})
|
||||||
|
|
||||||
|
def alert(self):
|
||||||
|
self._api_request("PUT", "/state", {"alert": "select"})
|
||||||
|
|
||||||
|
def alert_long(self):
|
||||||
|
self._api_request("PUT", "/state", {"alert": "lselect"})
|
||||||
|
|
||||||
|
|
||||||
|
class GroupController(_BaseController):
|
||||||
|
|
||||||
|
_api_endpoint_all = "https://{bridge_ip_address}/api/{bridge_api_user}/groups"
|
||||||
|
_api_endpoint_specific = "https://{bridge_ip_address}/api/{bridge_api_user}/groups/{number}"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def any_on(self) -> bool:
|
||||||
|
data = self._api_request()
|
||||||
|
return data["state"]["any_on"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_on(self) -> bool:
|
||||||
|
data = self._api_request()
|
||||||
|
return data["state"]["all_on"]
|
||||||
|
|
||||||
|
@all_on.setter
|
||||||
|
def all_on(self, on:bool):
|
||||||
|
assert type(on) == bool
|
||||||
|
self._api_request("PUT", "/action", {"on": on})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def brightness(self):
|
||||||
|
data = self._api_request()
|
||||||
|
return data["action"]["bri"]
|
||||||
|
|
||||||
|
@brightness.setter
|
||||||
|
def brightness(self, brightness:float):
|
||||||
|
assert type(brightness) == float or type(brightness) == int
|
||||||
|
bri_ = min(max(int(brightness * 254), 0), 254)
|
||||||
|
self._api_request("PUT", "/action", {"bri": bri_})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hue(self):
|
||||||
|
data = self._api_request()
|
||||||
|
return data["action"]["hue"]
|
||||||
|
|
||||||
|
@hue.setter
|
||||||
|
def hue(self, hue:float):
|
||||||
|
assert type(hue) == float or type(hue) == int
|
||||||
|
hue_ = min(max(int(hue * 65535), 0), 65535)
|
||||||
|
self._api_request("PUT", "/action", {"hue": hue_})
|
||||||
|
|
||||||
|
@property
|
||||||
|
def saturation(self):
|
||||||
|
data = self._api_request()
|
||||||
|
return data["action"]["sat"]
|
||||||
|
|
||||||
|
@saturation.setter
|
||||||
|
def saturation(self, saturation:float):
|
||||||
|
assert type(saturation) == float or type(saturation) == int
|
||||||
|
sat_ = min(max(int(saturation * 254), 0), 254)
|
||||||
|
self._api_request("PUT", "/action", {"sat": sat_})
|
||||||
|
|
||||||
|
def alert(self):
|
||||||
|
self._api_request("PUT", "/action", {"alert": "select"})
|
||||||
|
|
||||||
|
def alert_long(self):
|
||||||
|
self._api_request("PUT", "/action", {"alert": "lselect"})
|
7
yahuelib/exceptions.py
Normal file
7
yahuelib/exceptions.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# Copyright (c) 2023 Julian Müller (ChaoticByte)
|
||||||
|
|
||||||
|
class LightOrGroupNotFound(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class APIError(Exception):
|
||||||
|
pass
|
14
yahuelib/utils.py
Normal file
14
yahuelib/utils.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
# Copyright (c) 2023 Julian Müller (ChaoticByte)
|
||||||
|
|
||||||
|
|
||||||
|
from colorsys import rgb_to_hsv as _rgb_to_hsv
|
||||||
|
|
||||||
|
|
||||||
|
def rgb_to_hsv(r:int, g:int, b:int) -> float:
|
||||||
|
assert type(r) == int
|
||||||
|
assert type(g) == int
|
||||||
|
assert type(b) == int
|
||||||
|
r_ = r / 255.0
|
||||||
|
g_ = g / 255.0
|
||||||
|
b_ = b / 255.0
|
||||||
|
return _rgb_to_hsv(r_, g_, b_)
|
Reference in a new issue