pull/17/head
Micke Nordin 3 years ago
parent 00c6da8f0a
commit 273e111477
Signed by: micke
GPG Key ID: 014B273D614BE877

@ -1,9 +1,241 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tinge import Tinge
from typing import Union
import wx
import wx.lib.scrolledpanel as scrolled
from tinge import Tinge, HueBridge, HueGroup, HueLight
class Hui(wx.Frame):
"""This is the Hue GUI class
Args:
wx (Frame): Parent class
"""
def __init__(self, *args, **kw):
"""Constructor
"""
super().__init__(*args, **kw)
self.m_on_icon: str = ''
self.m_off_icon: str = ''
self.m_tinge: Tinge = Tinge()
self.m_bridges: list[HueBridge] = self.m_tinge.get_bridges()
self.cur_bridge: Union[None, HueBridge] = None
self.cur_group: Union[None, HueGroup] = None
# create a panel in the frame
self.pnl: scrolled.ScrolledPanel = scrolled.ScrolledPanel(self, -1, style=wx.VSCROLL)
self.pnl.SetupScrolling()
# and create a sizer to manage the layout of child widgets
self.sizer: wx.BoxSizer = wx.BoxSizer(wx.VERTICAL)
self.pnl.SetSizer(self.sizer)
self.add_bridges()
def add_bridges(self):
"""Add bridges to sizer, the entry point of the program
"""
self.sizer.Clear(delete_windows=True)
if self.m_bridges:
for bridge in self.m_bridges:
btn: wx.Button = wx.Button(self.pnl, label=str(bridge))
self.sizer.Add(btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mbridge=bridge: self.goto_bridge(mbridge), btn)
else:
btn: wx.Button = wx.Button(self.pnl, label="Discover bridges")
self.sizer.Add(btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event: self.discover_new_bridges(), btn)
self.sizer.Layout()
def add_groups(self, groups: list[HueGroup]):
"""This will add the groups to the sizer, when coming down from a bridge, or up from a light
Args:
groups (list[HueGroup]): The groups to display
"""
self.sizer.Clear(delete_windows=True)
bridge_btn: wx.Button = wx.Button(self.pnl, label=str(self.cur_bridge))
self.sizer.Add(bridge_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event: self.add_bridges(), bridge_btn)
for group in groups:
inner_sizer: wx.BoxSizer = wx.BoxSizer(orient=wx.HORIZONTAL)
groupid: int = group.get_id()
icon: str = self.m_off_icon
if group.is_any_on():
icon = self.m_on_icon
toggle_btn: wx.Button = wx.Button(self.pnl, label=icon)
inner_sizer.Add(toggle_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mgroupid=groupid: self.toggle_group(mgroupid), toggle_btn)
label: str = str(group)
group_btn: wx.Button = wx.Button(self.pnl, label=label)
inner_sizer.Add(group_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mgroupid=groupid: self.goto_group(mgroupid), group_btn)
self.sizer.Add(inner_sizer, 0, wx.EXPAND)
self.sizer.Layout()
# noinspection PyDefaultArgument
def add_lights(self, lights: list[HueLight]):
"""This will add the lights from a group to the sizer
Args:
lights (list[HueLight]): The lights to display
"""
self.sizer.Clear(delete_windows=True)
group_btn: wx.Button = wx.Button(self.pnl, label=str(self.cur_group))
self.sizer.Add(group_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event: self.add_groups(self.cur_bridge.get_groups()), group_btn)
for light in lights:
inner_sizer = wx.BoxSizer(orient=wx.HORIZONTAL)
lightid: int = light.get_id()
icon: str = self.m_off_icon
if light.is_on():
icon = self.m_on_icon
toggle_btn: wx.Button = wx.Button(self.pnl, label=icon)
inner_sizer.Add(toggle_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mlightid=lightid, mlights=lights: self.toggle_light_and_goto_group(mlightid,
mlights),
toggle_btn)
label: str = "{}".format(light)
light_btn: wx.Button = wx.Button(self.pnl, label=label)
inner_sizer.Add(light_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mlightid=lightid: self.goto_light(mlightid), light_btn)
self.sizer.Add(inner_sizer, 0, wx.EXPAND)
self.sizer.Layout()
def goto_bridge(self, bridge: HueBridge):
"""Call back for a bridge button
Args:
bridge (HueBridge): The bridge to display
"""
self.cur_bridge = bridge
groups: list[HueGroup] = bridge.get_groups()
if groups:
self.add_groups(groups)
else:
self.add_lights(bridge.get_lights())
def discover_new_bridges(self):
"""Call back for button that is displayed if no bridges were found
"""
self.m_tinge.discover_new_bridges()
self.add_bridges()
def toggle_group(self, groupid: int):
"""Toggle the lights of a group
Args:
groupid (int): The group id of the group to toggle
"""
self.cur_bridge.get_group_by_id(groupid).toggle()
self.add_groups(self.cur_bridge.get_groups())
def goto_group(self, groupid: int):
"""Call back for group button
Args:
groupid (int): The group id of the group to display
"""
group = self.cur_bridge.get_group_by_id(groupid)
self.cur_group = group
self.add_lights(group.get_lights())
def toggle_light_and_goto_group(self, lightid: int, lights: list[HueLight]):
"""Combo call back for toggle and goto group
Args:
lightid (int): The light id oof the light to toggle
lights (list[HueLight]): The lights to display after toggle
"""
self.cur_bridge.get_light_by_id(lightid).toggle()
self.add_lights(lights)
def goto_light(self, lightid: int):
"""Call back for light button
Args:
lightid (int): The light id of the light to display
"""
light: HueLight = self.cur_bridge.get_light_by_id(lightid)
is_on: bool = light.is_on()
self.sizer.Clear(delete_windows=True)
group: HueGroup = self.cur_group
group_btn: wx.Button = wx.Button(self.pnl, label=str(group))
self.sizer.Add(group_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event: self.goto_group(self.cur_group.get_id()), group_btn)
# Toggle
icon: str = self.m_off_icon
if is_on:
icon = self.m_on_icon
toggle_btn: wx.Button = wx.Button(self.pnl, label=icon)
self.sizer.Add(toggle_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mlightid=lightid: self.toggle_light_and_goto_light(mlightid),
toggle_btn)
# Slider for brightness
if is_on and light.get_brightness() > 0:
b_label: wx.StaticText = wx.StaticText(self.pnl, label="Brightness")
self.sizer.Add(b_label, 0, wx.EXPAND)
b_slider: wx.Slider = wx.Slider(self.pnl, value=light.get_state().get_brightness(), minValue=1,
maxValue=254)
self.sizer.Add(b_slider, 0, wx.EXPAND)
self.Bind(wx.EVT_SCROLL_THUMBRELEASE,
lambda event: self.set_brightness(event, light.get_id()), b_slider)
# Slider for colortemp
if is_on and light.can_set_ct():
c_label: wx.StaticText = wx.StaticText(self.pnl, label="Color Temperature")
self.sizer.Add(c_label, 0, wx.EXPAND)
c_slider: wx.Slider = wx.Slider(self.pnl, value=light.get_ct(), minValue=153, maxValue=500)
self.sizer.Add(c_slider, 0, wx.EXPAND)
self.Bind(wx.EVT_SCROLL_THUMBRELEASE,
lambda event: self.set_colortemp(event, light.get_id()), c_slider)
self.sizer.Layout()
def set_brightness(self, event: wx.ScrollEvent, lightid: int):
"""Call back for brightness slider
Args:
event (wx.ScrollEvent): The scroll event to react to
lightid (int): The light id of the light to adjust brightness of
"""
bri: int = event.GetPosition()
light: HueLight = self.cur_bridge.get_light_by_id(lightid)
light.set_brightness(bri)
def toggle_light_and_goto_light(self, lightid):
"""Combo call back to toggle a light and display that light again
Args:
lightid ([type]): The light id of the light to toggle/display
"""
self.cur_bridge.get_light_by_id(lightid).toggle()
self.goto_light(lightid)
def set_colortemp(self, event, lightid):
"""Call back for colortemp slider
Args:
event (wx.ScrollEvent): The scroll event to react to
lightid (int): The light id of the light to adjust colortemp of
"""
ct: int = event.GetPosition()
light: HueLight = self.cur_bridge.get_light_by_id(lightid)
light.set_ct(ct)
if __name__ == "__main__":
m_tinge = Tinge()
bridge = m_tinge.get_bridges()[0]
print(bridge.discover_new_lights())
print(bridge.append_new_lights())
app = wx.App()
frm = Hui(None, title="Tinge")
frm.Show()
app.MainLoop()

@ -1,6 +1,7 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
from typing import Union
from ..HueGroup import HueGroup
from ..HueLight import HueLight
@ -8,13 +9,40 @@ from ..HueUtils import make_request
class HueBridge:
def __init__(self, ipaddress: str, username: str):
"""This class represents a Hue Bridge
"""
def __init__(self, ipaddress: str, username: str, name: str = ""):
""" Constructor
Args:
ipaddress (str): The ip address of the bridge
username (str): The username for this app for this bridge
name (str, optional): A human readable name for this bridge. Is set to ipaddress, if not supplied.
"""
self.m_ipaddress: str = ipaddress
self.m_username: str = username
if name:
self.m_name: str = name
else:
self.m_name = self.m_ipaddress
self.m_lights: list[HueLight] = self.discover_lights()
self.m_groups: list[HueGroup] = self.discover_groups()
def __str__(self) -> str:
"""The string representation of this bridge
Returns:
str: Returns the name
"""
return self.m_name
def discover_groups(self) -> list[HueGroup]:
"""Get groups from Hue Api
Returns:
list[HueGroup]: discovered groups
"""
path: str = "{}/groups".format(self.m_username)
response = make_request(self.m_ipaddress, path)
groups: list[HueGroup] = list()
@ -27,6 +55,19 @@ class HueBridge:
def create_group(self, lights: list[HueLight], name: str, group_type: str = "LightGroup",
group_class: str = "Other") -> bool:
"""Create a group from a list of lights
Args:
lights (list[HueLight]): a list of lights to group
name (str): The name of the new group
group_type (str, optional): The group type can be LightGroup, Room or either Luminaire or
LightSource if a Multisource Luminaire is present in the system.
Defaults to "LightGroup".
group_class (str, optional): Category of Room Types. Defaults to "Other".
Returns:
bool: True if creation was a success, otherwise False
"""
path = "{}/groups".format(self.get_user())
method = "POST"
data: dict = {'lights': [], 'name': name, 'type': group_type, 'class': group_class}
@ -44,6 +85,11 @@ class HueBridge:
return False
def discover_lights(self) -> list[HueLight]:
"""Get lights from Hue API
Returns:
list[HueLight]: List of discovered lights
"""
path: str = "{}/lights".format(self.m_username)
response = make_request(self.m_ipaddress, path)
lights: list[HueLight] = list()
@ -51,11 +97,19 @@ class HueBridge:
lights.append(HueLight(int(key), value, self.get_ipaddress(), self.get_user()))
return lights
def discover_new_lights(self, device_id_list=None) -> bool:
def discover_new_lights(self, light_ids: Union[None, list[int]] = None) -> bool:
"""Makes bridge search for new lights
Args:
light_ids (Union[None, list[int]], optional): Either a list of light ids or None. Defaults to None.
Returns:
bool: True if bridge has started looking for new lights, otherwise False
"""
body: str = ""
if device_id_list is not None:
if light_ids is not None:
body_dict = {'deviceid': []}
for device in device_id_list:
for device in light_ids:
body_dict['deviceid'].append(device)
body = json.dumps(body_dict)
path: str = "{}/lights".format(self.m_username)
@ -64,6 +118,12 @@ class HueBridge:
return 'success' in response.json()[0].keys()
def append_new_lights(self) -> bool:
"""If any new lights were discovered in discover_new_lights(), they can be appended to this bridges
list of lights with this function
Returns:
bool: True if the request was ok, otherwise False
"""
path: str = "{}/lights/new".format(self.m_username)
response = make_request(self.m_ipaddress, path)
for key, value in json.loads(response.text).items():
@ -73,19 +133,60 @@ class HueBridge:
self.m_lights.append(HueLight(int(key), response.json(), self.get_ipaddress(), self.get_user()))
return response.ok
def get_groups(self):
def get_groups(self) -> list[HueGroup]:
"""Return all the groups of this bridge
Returns:
list[HueGroup]: A list of all groups owned by this bridge
"""
return self.m_groups
def get_lights(self):
def get_lights(self) -> list[HueLight]:
"""Get all the lights owned by this bridge
Returns:
list[HueLight]: A flat list of all lights owned by this bridge
"""
return self.m_lights
def get_light_by_id(self, id: int) -> HueLight:
def get_light_by_id(self, light_id: int) -> HueLight:
"""Get a specific light
Args:
light_id (int): The light id of the light to get
Returns:
HueLight: The light
"""
for light in self.m_lights:
if light.get_id() == id:
if light.get_id() == light_id:
return light
def get_ipaddress(self):
def get_group_by_id(self, group_id: int) -> HueGroup:
"""Get a specific group
Args:
group_id (int): The group id of the group to get
Returns:
HueGroup: The group
"""
for group in self.m_groups:
if group.get_id() == group_id:
return group
def get_ipaddress(self) -> str:
"""Get the ip address of this bridge
Returns:
str: The ip address
"""
return self.m_ipaddress
def get_user(self):
def get_user(self) -> str:
"""A user, or username, is more like a password and is needed to authenticate with the Hue API
Returns:
str: The username
"""
return self.m_username

@ -1,6 +1,5 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import requests
@ -9,8 +8,19 @@ from ..HueUtils import make_request
class HueGroup:
"""A class for groups
"""
class Action:
"""The light state of one of the lamps in the group.
"""
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the data structure that concerns this Action
"""
keys = data_slice.keys()
self.m_on: bool = data_slice['on']
self.m_bri: int = data_slice['bri']
@ -41,25 +51,40 @@ class HueGroup:
self.m_colormode = str()
class State:
"""A hueGroup.State represents the collective state of the group
"""
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the data structure that concerns this State
"""
self.m_all_on: bool = data_slice['all_on']
self.m_any_on: bool = data_slice['any_on']
def is_all_on(self) -> bool:
return self.m_all_on
def is_any_on(self) -> bool:
return self.m_any_on
def __init__(self, id: int, lights: list[HueLight], data: dict, parent_bridge_ip: str, parent_bridge_user: str):
self.m_id: int = id
def __init__(self, group_id: int, lights: list[HueLight], data: dict, parent_bridge_ip: str,
parent_bridge_user: str):
"""Constructor
Args:
group_id (int): The group id for this group
lights (list[HueLight]): The lights that is in this group
data (dict): The Hue API description of the group
parent_bridge_ip (str): ip address of the parent bridge
parent_bridge_user (str): username of the parent bridge
"""
self.m_id: int = group_id
self.m_parent_bridge_ip = parent_bridge_ip
self.m_parent_bridge_user = parent_bridge_user
self.m_name: str = data['name']
self.m_lights = lights
self.m_sensors: list[str] = data['sensors']
self.m_type: str = data['type']
self.m_state: HueGroup.State = HueGroup.State(data['state'])
# Don't believe the api, it can claim that a light is on even if it is unreachable
state: dict = {'all_on': self.is_any_on(), 'any_on': self.is_any_on()}
self.m_state: HueGroup.State = HueGroup.State(state)
self.m_recycle: bool = data['recycle']
if 'class' in data.keys():
self.m_class: str = data['class']
@ -67,32 +92,100 @@ class HueGroup:
self.m_class: str = str()
self.m_action: HueGroup.Action(data['action'])
def __str__(self):
def __str__(self) -> str:
"""String representation of the group
Returns:
str: The group name
"""
return self.m_name
def get_id(self) -> int:
"""Get the group id
Returns:
int: The group id
"""
return self.m_id
def get_lights(self) -> list[HueLight]:
"""Get the lights that belong to this group
Returns:
list[HueLight]: The lights
"""
return self.m_lights
def is_all_on(self) -> bool:
return self.m_state.is_all_on()
"""We really check to make sure
Returns:
bool: True if all lights are on and reachable, otherwise False
"""
# Dont believe the API, both on and reachable to be on
on = True
for light in self.m_lights:
if not light.is_on():
on = False
return on
def is_any_on(self) -> bool:
return self.m_state.is_any_on()
"""Check if any of the lights are on and reachable
Returns:
bool: True if any light in the group is on and reachable, otherwise False
"""
# Dont believe the API, both on and reachable to be on
on = False
for light in self.m_lights:
if light.is_on():
on = True
return on
def toggle(self):
"""Toggle all lights of the group
"""
for light in self.m_lights:
light.toggle()
self.update_state()
def turn_off(self):
"""turn off all lights in the group
"""
state: str = '{"on": false}'
self.set_state(state)
def turn_on(self):
"""Turn on all lights in the group
"""
state: str = '{"on": true}'
self.set_state(state)
def set_state(self, state: str) -> requests.Response:
"""Helper method to set the state of the group
Args:
state (str): The state of the group
Possible attributes are:
* on: True/False
* bri: 0-254
* hue: 0-65535
* sat: 0-254
* xy: [0.0-1.0,0.0-1.0]
* ct: 153-500
* alert: "none"/"select"/"lselect"
* effect: "none"/"colorloop"
* transitiontime: 0-65535 (multiple of 100ms, default is 4)
* bri_inc: -254-254
* sat_inc: -254-254
* hue_inc: -65534-65534
* ct_inc: -65534-65534
* xy_inc: -0.5-0.5
* scene: "Scene identifier"
Returns:
requests.Response: The API response
"""
path: str = "{}/groups/{}/action".format(self.m_parent_bridge_user, self.m_id)
method: str = "PUT"
response = make_request(self.m_parent_bridge_ip, path, method, state)
@ -100,6 +193,7 @@ class HueGroup:
return response
def update_state(self):
path: str = "{}/groups/{}".format(self.m_parent_bridge_user, self.m_id)
response = make_request(self.m_parent_bridge_ip, path)
self.m_state = HueGroup.State(json.loads(response.text)['state'])
"""Update the state after a possible change
"""
state: dict = {'all_on': self.is_any_on(), 'any_on': self.is_any_on()}
self.m_state = HueGroup.State(state)

@ -9,8 +9,19 @@ from ..HueUtils import make_request
class HueLight:
"""A class representing a light
"""
class State:
"""A class representing the state of a light
"""
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the data from the API response that concerns this state
"""
keys = data_slice.keys()
self.m_on: bool = data_slice['on']
if 'bri' in keys:
@ -29,21 +40,69 @@ class HueLight:
self.m_mode: str = data_slice['mode']
self.m_reachable: bool = data_slice['reachable']
def get_brightness(self) -> int:
"""Get current brightness of the light
Returns:
int: 0-254
"""
return self.m_bri
def get_ct(self) -> int:
"""Get current color temp of the light
Returns:
int: 0, 153-500, 0 means it cant do color temp
"""
return self.m_ct
def is_on(self) -> bool:
"""Is this thing on?
Returns:
bool: True if it is on, otherwise False
"""
return self.m_on
def is_reachable(self) -> bool:
"""Can we reach this light, if not it has most likely been turned off with physical switch
Returns:
bool: True if it is reachable, otherwise False
"""
return self.m_reachable
class SwUpdate:
"""A class to describe software updates
"""
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the data from the API that concerns this SwUpdate
"""
self.m_state: str = data_slice['state']
self.m_lastinstall: datetime = datetime.strptime(data_slice['lastinstall'], "%Y-%m-%dT%H:%M:%S")
class Capabilites:
"""A class for the light capabilities
"""
class Control:
"""A class for the control object
"""
class ColorTemp:
"""A class for the color temperature object
"""
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the data from the API that concerns this ColorTemp
"""
keys = data_slice.keys()
if 'min' in keys:
self.m_min: int = data_slice['min']
@ -54,7 +113,20 @@ class HueLight:
else:
self.m_max: int = 0
def get_max(self) -> int:
"""Get the max colortemp of this light
Returns:
int: Max colortemp of this light, 0 means you can't change colortemp
"""
return self.m_max
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the Hue API data that concerns this Control
"""
keys = data_slice.keys()
if 'mindimlevel' in keys:
self.m_mindimlevel: int = data_slice['mindimlevel']
@ -69,30 +141,82 @@ class HueLight:
else:
self.m_ct = HueLight.Capabilites.Control.ColorTemp({})
def get_colortemp(self):
"""Get the colortemp object
Returns:
HueLight.Capabilities.Control.ColorTemp: the colortemp object of this capability
"""
return self.m_ct
class Streaming:
"""A class for the streaming object
"""
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the Hue API data that concerns this streaming object
"""
self.m_renderer: bool = data_slice['renderer']
self.m_proxy: bool = data_slice['proxy']
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the Hue API data that concerns this capabilities object
"""
self.m_certified: bool = data_slice['certified']
self.m_control = HueLight.Capabilites.Control(data_slice['control'])
self.m_streaming = HueLight.Capabilites.Streaming(data_slice['streaming'])
def get_control(self):
"""Get the control object
Returns:
HueLight.Capabilities.Control: the control object of this capability
"""
class Config:
"""A class for the config object
"""
class Startup:
"""A class for the startup object
"""
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the Hue API data that concerns this startup
"""
self.m_mode: str = data_slice['mode']
self.m_configured: bool = data_slice['configured']
def __init__(self, data_slice: dict):
"""Constructor
Args:
data_slice (dict): The part of the Hue API response that concerns this config object
"""
self.m_archetype: str = data_slice['archetype']
self.m_function: str = data_slice['function']
self.m_direction: str = data_slice['direction']
self.m_startup = HueLight.Config.Startup(data_slice['startup'])
def __init__(self, id: int, data: dict, parent_bridge_ip: str, parent_bridge_user: str):
self.m_id: int = id
def __init__(self, light_id: int, data: dict, parent_bridge_ip: str, parent_bridge_user: str):
"""Constructor
Args:
light_id (int): The id of this light
data (dict): The response data from the Hue API
parent_bridge_ip (str): ip address of the parent bridge
parent_bridge_user (str): username of the parent bridge
"""
self.m_id: int = light_id
self.m_parent_bridge_ip = parent_bridge_ip
self.m_parent_bridge_user = parent_bridge_user
self.m_state = HueLight.State(data['state'])
@ -109,28 +233,91 @@ class HueLight:
self.m_productid: str = data['productid']
def __str__(self) -> str:
"""String representation of this light
Returns:
str: The name of the light
"""
return self.m_name
def can_set_ct(self) -> bool:
"""Check if we can set a color temp for this light
Returns:
bool: True if we can, otherwise False
"""
return self.get_ct() > 0
def get_state(self):
"""Get the state object for this light
Returns:
HueLight.State: The current state of affairs
"""
return self.m_state
def get_id(self):
def get_id(self) -> int:
"""Get the id of this light
Returns:
int: The light id
"""
return self.m_id
def is_on(self) -> bool:
return self.get_state().is_on()
"""Is it on though?
Returns:
bool: True if it is both on and reachable, otherwise False
"""
# A light has to be both on and reachable
return self.get_state().is_on() and self.is_reachable()
def is_reachable(self) -> bool:
"""Is it reachable
Returns:
bool: True if it is, False otherwise
"""
return self.get_state().is_reachable()
def set_state(self, state: str) -> requests.Response:
"""A helper method to set the state of the light
Args:
state (str): The state of the light
Returns:
requests.Response: The response from the Hue API
"""
path: str = "{}/lights/{}/state".format(self.m_parent_bridge_user, self.m_id)
method: str = "PUT"
response = make_request(self.m_parent_bridge_ip, path, method, state)
self.update_state()
return response
def set_brightness(self, bri: int):
"""Set the brightness of the light
Args:
bri (int): 0-254
"""
state = '{{"bri":{0}}}'.format(bri)
self.set_state(state)
def set_ct(self, colortemp: int):
"""Set the colortemp of the light, if possible
Args:
colortemp (int): 153-500
"""
if self.can_set_ct():
state = '{{"ct":{0}}}'.format(colortemp)
self.set_state(state)
def toggle(self):
"""Toggle light
"""
if self.is_reachable():
state: str = '{"on":true}'
if self.is_on():
@ -138,6 +325,24 @@ class HueLight:
self.set_state(state)
def update_state(self):
"""See if anything has changed
"""
path: str = "{}/lights/{}".format(self.m_parent_bridge_user, self.m_id)
response = make_request(self.m_parent_bridge_ip, path)
self.m_state = HueLight.State(json.loads(response.text)['state'])
def get_brightness(self) -> int:
"""Get currrent brightness
Returns:
int: 0-254
"""
return self.get_state().get_brightness()
def get_ct(self) -> int:
"""Get current colortemp
Returns:
int: 0,153-500
"""
return self.get_state().get_ct()

@ -8,6 +8,14 @@ from ..UserOrError import UserOrError
def connect(ipaddress: str) -> UserOrError:
"""Connect this bridge to tinge and get the username
Args:
ipaddress (str): ip address of the bridge
Returns:
UserOrError: Error if it is not connected otherwise the username
"""
user_or_error: UserOrError = UserOrError()
body: str = '{{"devicetype":"{0}"}}'.format("tinge")
path: str = ""
@ -25,11 +33,30 @@ def connect(ipaddress: str) -> UserOrError:
def is_valid_config(filename: str) -> bool:
"""Currently doesn't do much, but we ckan get mor elaborate checks later if we want
Args:
filename (str): The filename to check
Returns:
bool: True if we think it is ok, otherwise False
"""
return os.path.exists(filename) and os.path.getsize(filename) > 0
def make_request(ipaddress: str, path: str, method: str = "GET",
body: str = '') -> requests.Response:
"""Helper function to make an API call to the Hue API on the bridge
Args:
ipaddress (str): ip address of the bridge
path (str): the API endpoint
method (str, optional): HTTP method. Defaults to "GET".
body (str, optional): The body or parameters to the API call. Defaults to ''.
Returns:
requests.Response: The response from the Hue API
"""
rfunct = requests.get
url = "http://{}/api/{}".format(ipaddress, path)
if method == "PUT":

@ -3,26 +3,55 @@
class UserOrError:
"""Class that can be either a username or an error message from the bridge
"""
def __init__(self):
"""Constructor
"""
self.UNKNOWNERROR = 9999
self.muser: str = str()
self.merror: bool = True
self.mcode: int = self.UNKNOWNERROR
def get_error_code(self) -> int:
"""Thhis is an error code if this an error
Returns:
int: error code from Hue API
"""
return self.mcode
def get_user(self) -> str:
"""Get the username
Returns:
str: the username we stored
"""
return self.muser
def is_error(self) -> bool:
"""Check if this is an error
Returns:
bool: True if ther is an error or False otherwise
"""
return self.merror
def set_error(self, code: int):
"""Set an error code
Args:
code (int): An error code to set
"""
self.merror = True
self.mcode = code
def set_user(self, username: str):
"""Set the username
Args:
username (str): Username to set
"""
self.merror = False
self.muser = username

@ -12,7 +12,12 @@ from .UserOrError import UserOrError
class Tinge:
"""The class that keeps track of bridges and config
"""
def __init__(self):
"""Constructor
"""
self.m_bridges: list[HueBridge] = list()
self.m_discovered: list[str] = list()
self.m_config = os.path.join(os.environ['HOME'], ".config/tinge/config")
@ -22,10 +27,14 @@ class Tinge:
self.write_all_bridges_to_conf()
def create_confdir(self):
"""Create the config dir if it does not allready exist
"""
if not os.path.exists(os.path.dirname(self.m_config)):
os.makedirs(os.path.dirname(self.m_config))
def discover_new_bridges(self):
"""Use UPnP to discover bridges on the current network
"""
upnp: UPnP = UPnP()
discovered_devices = upnp.discover()
if not discovered_devices:
@ -47,10 +56,17 @@ class Tinge:
self.m_discovered.append(device.host)
return
def get_bridges(self):
def get_bridges(self) -> list[HueBridge]:
"""Get the bridges
Returns:
list[HueBridge]: The bridges we keep track off
"""
return self.m_bridges
def read_bridges_from_file(self):
"""Read config file and add back previously discovered bridges
"""
if is_valid_config(self.m_config):
with open(self.m_config, 'r') as configfile:
mbridges = toml.loads(configfile.read())
@ -61,6 +77,8 @@ class Tinge:
self.m_discovered.append(key)
def write_all_bridges_to_conf(self):
"""Save to file
"""
with open(self.m_config, 'w') as configfile:
for bridge in self.m_bridges:
configfile.write('["{}"]\nuser = "{}"\n'.format(bridge.get_ipaddress(), bridge.get_user()))

Loading…
Cancel
Save