Compare commits

...

7 Commits
master ... kivy

4
.gitignore vendored

@ -4,6 +4,10 @@ __pycache__/
*.py[cod]
*$py.class
.buildozer/
bin/
deb_dist/
# C extensions
*.so

@ -0,0 +1,12 @@
<BridgeScreen>:
name: 'bridges'
layout: layout
ScrollView:
do_scroll_x: False
do_scroll_y: True
BoxLayout:
id: layout
orientation: 'vertical'

@ -0,0 +1,169 @@
from logging import disable
import kivy
kivy.require('2.2.1')
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.stacklayout import StackLayout
from tinge import HueBridge, HueGroup, HueUtils, Tinge
class HueApp(App):
def build(self):
self.tinge = Tinge()
sm = ScreenManager()
sm.add_widget(BridgeScreen(sm, self.tinge, name='bridge_screen'))
return sm
class GroupScreen(Screen):
def __init__(self, sm, tinge, id: int, bridge: HueBridge, **kwargs):
super(GroupScreen, self).__init__(**kwargs)
self.sm = sm
self.tinge = tinge
self.id = id
self.group = bridge.get_group_by_id(id)
self.bridge = bridge
self.layout = StackLayout()
self.add_widget(self.layout)
for button in self.get_light_buttons():
self.layout.add_widget(button)
def get_light_buttons(self):
buttons = []
for light in self.group.get_lights():
button = Button(text=str(light),
size_hint=(1, 0.1),
on_press=lambda _: self.press_button(light))
buttons.append(button)
backbutton = Button(text="Back",
size_hint=(1, 0.1),
on_press=lambda _: self.press_back_button())
buttons.append(backbutton)
return buttons
def press_back_button(self):
self.sm.current = "bridge_" + str(self.bridge.id)
def press_button(self, light):
print(button, light)
class SingleBridgeScreen(Screen):
def __init__(self, sm, tinge, bridge: HueBridge, **kwargs):
super(SingleBridgeScreen, self).__init__(**kwargs)
self.sm = sm
self.tinge = tinge
self.bridge = bridge
self.layout = StackLayout()
self.add_widget(self.layout)
self.groups = []
for group in self.bridge.get_groups():
self.layout.add_widget(self.get_group_button(group))
self.layout.add_widget(
Button(text="Back",
size_hint=(1, 0.1),
on_press=lambda _: self.press_back_button()))
def get_group_button(self, group):
button = Button(text=str(group), size_hint=(1, 0.1))
button.bind(on_press=lambda _: self.press_button(group.get_id()))
return button
def press_back_button(self):
self.sm.current = "bridge_screen"
def press_button(self, group: int):
print(group)
group_name: str = 'group_' + str(group)
group_screen = GroupScreen(self.sm,
self.tinge,
group,
self.bridge,
name=group_name)
if not self.sm.has_screen(group_name):
self.sm.add_widget(group_screen)
self.sm.current = group_name
class BridgeScreen(Screen):
bridges = []
layout = BoxLayout()
def __init__(self, sm, tinge, **kwargs):
super(BridgeScreen, self).__init__(**kwargs)
self.sm = sm
self.tinge = tinge
for bridge_button in self.get_bridge_buttons():
self.layout.add_widget(bridge_button)
def press_button(self, bridge):
bridge_name = "bridge_" + str(bridge.id)
if not self.sm.has_screen(bridge_name):
self.sm.add_widget(
SingleBridgeScreen(self.sm,
self.tinge,
bridge,
name=bridge_name))
self.sm.current = bridge_name
def discover_bridge(self):
bridges = self.tinge.discover_new_bridges()
if bridges:
for bridge in bridges:
print(bridge)
if not self.tinge.bridge_discovered(bridge['ipaddress']):
# Display popup lable to add bridge and get username
popup = Popup(title='Connect Bridge',
size_hint=(None, None),
size=(400, 400))
new_bridge_button = Button(
text='Press button on Bridge',
size_hint=(1, 0.1),
on_press=lambda _: self.wait_for_button_press(
popup, bridge['ipaddress']))
popup.add_widget(new_bridge_button)
popup.open()
def wait_for_button_press(self, popup, ipaddress):
bridge_id = len(self.tinge.get_bridges())
user_or_error = HueUtils.connect(ipaddress)
while user_or_error.is_error():
user_or_error = HueUtils.connect(ipaddress)
self.tinge.append_bridge(
HueBridge(bridge_id, ipaddress, user_or_error.get_user(),
ipaddress))
self.tinge.write_all_bridges_to_conf()
popup.dismiss()
def get_bridge_buttons(self):
buttons = []
bridges = self.tinge.get_bridges()
for bridge in bridges:
bridge_button = Button(
text=str(bridge.get_ipaddress()),
size_hint=(1, 0.1),
on_press=lambda _: self.press_button(bridge))
buttons.append(bridge_button)
bridge_button = Button(text='Discover Bridges',
size_hint=(1, 0.1),
on_press=lambda _: self.discover_bridge())
buttons.append(bridge_button)
sizer = Label(disabled=True)
buttons.append(sizer)
return buttons
if __name__ == '__main__':
HueApp().run()

@ -1,555 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from typing import Union
import wx
import wx.lib.scrolledpanel as scrolled
from tinge import HueBridge, HueGroup, HueLight, HueUtils, Tinge, is_bridge
class Hui(wx.Frame):
"""This is the Hue GUI class
Args:
wx (Frame): Parent class
"""
def redraw(*args):
"""Decorator used for redrawing the widgets in the sizer
Returns:
function: The decorated function
"""
func = args[0]
def wrapper(self, *wrapper_args):
"""The wrapper function for the decorator
"""
self.sizer.Clear(delete_windows=True)
func(self, *wrapper_args)
self.sizer.Layout()
return wrapper
def __init__(self, *args, **kw):
"""Constructor
"""
super().__init__(*args, **kw)
self.m_on_icon: str = '☼'
self.m_off_icon: str = '☾'
self.m_unreachable_icon: str = '⚠'
self.m_tinge: Tinge = Tinge()
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()
@redraw
def add_bridges(self):
"""Add bridges to sizer, the entry point of the program
"""
self.SetTitle('Tinge - All Bridges')
all_unreachable: bool = True
no_bridges: bool = True
if self.m_tinge.get_bridges():
no_bridges = False
for bridge in self.m_tinge.get_bridges():
if bridge.is_reachable():
bridge.refresh_bridge()
all_unreachable = False
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:
label = "{} {} ({})".format(self.m_unreachable_icon,
str(bridge), "unreachable")
btn: wx.Button = wx.Button(self.pnl, label=label)
self.sizer.Add(btn, 0, wx.EXPAND)
if no_bridges or all_unreachable:
label = "Discover bridge"
btn: wx.Button = wx.Button(self.pnl, label=label)
self.sizer.Add(btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON, lambda event: self.discover_new_bridges(),
btn)
@redraw
def add_manage_bridge(self):
"""Add bridges to sizer, the entry point of the program
"""
self.SetTitle('Tinge - Manage Bridge')
label = "Delete Bridge"
btn: wx.Button = wx.Button(self.pnl, label=label)
self.sizer.Add(btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON, lambda event: self.delete_bridge(),
btn)
back_btn: wx.Button = wx.Button(self.pnl, label="Go Back")
self.sizer.Add(back_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON, lambda event: self.add_groups(self.cur_bridge.get_groups()),
back_btn)
@redraw
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.SetTitle("Tinge - {}".format(self.cur_bridge.m_name))
bridge_btn: wx.Button = wx.Button(self.pnl, label="All Bridges")
has_unattached: bool = len(self.cur_bridge.unattached_lights) > 0
self.sizer.Add(bridge_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON, lambda event: self.add_bridges(), bridge_btn)
if has_unattached:
group_label: wx.StaticText = wx.StaticText(self.pnl,
label=" ⚯ Groups ⚯ ",
style=wx.ALIGN_CENTER)
self.sizer.Add(group_label, 0, wx.EXPAND)
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, 1, wx.EXPAND)
self.Bind(
wx.EVT_BUTTON,
lambda event, mgroupid=groupid: self.toggle_group(mgroupid),
toggle_btn)
label: str = "{}".format(str(group))
group_btn: wx.Button = wx.Button(self.pnl,
label=label,
style=wx.BU_LEFT)
inner_sizer.Add(group_btn, 4, 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)
if has_unattached:
unattached_label: wx.StaticText = wx.StaticText(
self.pnl,
label=" ⚬ Unattached lights ⚬ ",
style=wx.ALIGN_CENTER)
self.sizer.Add(unattached_label, 0, wx.EXPAND)
for light in self.cur_bridge.unattached_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
elif not light.is_reachable():
icon = self.m_unreachable_icon
toggle_btn: wx.Button = wx.Button(self.pnl, label=icon)
inner_sizer.Add(toggle_btn, 1, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mlightid=lightid: self.
toggle_light_and_goto_group(mlightid, lights),
toggle_btn)
label: str = "{}".format(light)
light_btn: wx.Button = wx.Button(self.pnl,
label=label,
style=wx.BU_LEFT)
inner_sizer.Add(light_btn, 4, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mlightid=lightid: self.
add_single_light(mlightid, True),
light_btn)
self.sizer.Add(inner_sizer, 0, wx.EXPAND)
bridge_mgm_btn: wx.Button = wx.Button(self.pnl, label="Manage Bridge")
self.sizer.Add(bridge_mgm_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON, lambda event: self.manage_bridge(), bridge_mgm_btn)
def add_manual_discovery_dialog(self) -> bool:
self.sizer.Clear(delete_windows=True)
found_any: bool = False
text_entry: wx.TextEntryDialog = wx.TextEntryDialog(
self.pnl,
"Manually enter IP address of bridge:",
caption="Auto discovery failure")
warn_label: wx.StaticText = wx.StaticText(
self.pnl, label="Waiting for Button Press on Bridge")
if text_entry.ShowModal() == wx.ID_OK:
ipaddress: str = text_entry.GetValue()
if is_bridge(ipaddress):
self.sizer.Add(warn_label, 0, wx.ALIGN_CENTER)
self.sizer.Layout()
user_or_error = HueUtils.connect(ipaddress)
while user_or_error.is_error():
user_or_error = HueUtils.connect(ipaddress)
self.m_tinge.append_bridge(
HueBridge(ipaddress, user_or_error.get_user(), ipaddress))
found_any = True
self.m_tinge.write_all_bridges_to_conf()
else:
label = "Supplied IP Address did not match a Bridge.",
failure_msg: wx.GenericMessageDialog = wx.GenericMessageDialog(
self.pnl, label, caption="Try again!")
failure_msg.ShowModal()
return found_any
@redraw
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.SetTitle("Tinge - {}".format(self.cur_group))
group_btn: wx.Button = wx.Button(self.pnl, label=str(self.cur_bridge))
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
elif not light.is_reachable():
icon = self.m_unreachable_icon
toggle_btn: wx.Button = wx.Button(self.pnl, label=icon)
inner_sizer.Add(toggle_btn, 1, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mlightid=lightid: self.
toggle_light_and_goto_group(mlightid, lights),
toggle_btn)
label: str = "{}".format(light)
light_btn: wx.Button = wx.Button(self.pnl,
label=label,
style=wx.BU_LEFT)
inner_sizer.Add(light_btn, 4, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mlightid=lightid: self.add_single_light(
mlightid),
light_btn)
self.sizer.Add(inner_sizer, 0, wx.EXPAND)
@redraw
def add_single_light(self, lightid: int, unattached: bool = False):
"""Call back for light button
Args:
lightid (int): The light id of the light to display
unattached (bool, optional): Is the light unattached to any group?
"""
light: HueLight = self.cur_bridge.get_light_by_id(lightid)
self.SetTitle("Tinge - {}".format(light))
is_on: bool = light.is_on()
if unattached:
group_btn: wx.Button = wx.Button(self.pnl,
label=str(self.cur_bridge))
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)
else:
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
elif not light.is_reachable():
icon = self.m_unreachable_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:
if light.can_set_brightness():
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,
lambda event: self.set_brightness(event, light.get_id()),
b_slider)
# Slider for colortemp
if 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,
lambda event: self.set_colortemp(event, light.get_id()),
c_slider)
# Slider for hue
if light.can_set_hue():
d_label: wx.StaticText = wx.StaticText(self.pnl, label="Hue")
self.sizer.Add(d_label, 0, wx.EXPAND)
d_slider: wx.Slider = wx.Slider(self.pnl,
value=light.get_hue(),
minValue=0,
maxValue=65535)
self.sizer.Add(d_slider, 0, wx.EXPAND)
self.Bind(wx.EVT_SCROLL,
lambda event: self.set_hue(event, light.get_id()),
d_slider)
# Slider for saturation
if light.can_set_sat():
e_label: wx.StaticText = wx.StaticText(self.pnl,
label="Saturation")
self.sizer.Add(e_label, 0, wx.EXPAND)
e_slider: wx.Slider = wx.Slider(self.pnl,
value=light.get_sat(),
minValue=0,
maxValue=254)
self.sizer.Add(e_slider, 0, wx.EXPAND)
self.Bind(
wx.EVT_SCROLL,
lambda event: self.set_saturation(event, light.get_id()),
e_slider)
rename_btn: wx.Button = wx.Button(self.pnl, label="Rename")
self.sizer.Add(rename_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mlightid=lightid: self.
rename_light_and_goto_light(mlightid, unattached),
rename_btn)
delete_btn: wx.Button = wx.Button(self.pnl, label="Delete")
self.sizer.Add(delete_btn, 0, wx.EXPAND)
self.Bind(wx.EVT_BUTTON,
lambda event, mlightid=lightid: self.
delete_light_and_goto_group(mlightid),
delete_btn)
def delete_bridge(self):
dlg: wx.MessageDialog = wx.MessageDialog(self.pnl,
"Delete " + self.cur_bridge.m_name + "?",
"Are you sure?",
style=wx.CANCEL | wx.CANCEL_DEFAULT | wx.OK)
dlg.SetOKCancelLabels("&Yes", "&Don't delete")
reply: int = dlg.ShowModal()
if reply == wx.ID_CANCEL:
self.add_groups(self.cur_bridge.get_groups())
else:
self.m_tinge.delete_bridge(self.cur_bridge)
self.add_bridges()
def delete_light_and_goto_group(self, lightid):
"""Combo call back for delete and goto group
Args:
lightid (int): The light id of the light to delete
"""
if self.get_ok_cancel_answer_from_modal(
"Are you sure you want to delete this light?"):
light: HueLight = self.cur_bridge.get_light_by_id(lightid)
light.delete()
self.cur_bridge.remove_light(light)
self.add_lights(self.cur_group.get_lights())
else:
self.add_single_light(lightid)
def discover_new_bridges(self) -> bool:
"""Call back for button that is displayed if no bridges were found
Returns:
bool: True if we found any bridge, False otherwise
"""
found_any: bool = False
found_bridges: list[dict] = self.m_tinge.discover_new_bridges()
if found_bridges:
for bridge in found_bridges:
user_or_error = HueUtils.connect(bridge['ipaddress'])
while user_or_error.is_error():
user_or_error = HueUtils.connect(bridge['ipaddress'])
self.m_tinge.append_bridge(
HueBridge(bridge['ipaddress'], user_or_error.get_user(),
bridge['name']))
found_any = True
self.m_tinge.write_all_bridges_to_conf()
self.add_bridges()
else:
found_any = self.add_manual_discovery_dialog()
self.add_bridges()
return found_any
def get_ok_cancel_answer_from_modal(self, message: str) -> bool:
"""Display a message dialog and return ok or cancel
Args:
message (str): The message to display
Returns:
bool: The response from the user
"""
with wx.MessageDialog(self.pnl,
message,
style=wx.OK | wx.CANCEL
| wx.CANCEL_DEFAULT) as dlg:
return dlg.ShowModal() == wx.ID_OK
def get_text_answer_from_modal(self,
message: str,
cap: str,
val: str = "") -> str:
"""Display a text entry and return the content
Args:
message (str): The message to display
cap (str): The caption to display
val (str, optional): The default value to display, defaults to the empty string
Returns:
str: The response from the user
"""
with wx.TextEntryDialog(self.pnl, message, caption=cap,
value=val) as dlg:
dlg.ShowModal()
answer: str = dlg.GetValue()
return answer
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 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 manage_bridge(self):
"""Call back for manage bridge button
"""
self.add_manage_bridge()
def rename_light_and_goto_light(self, lightid, unattached: bool = False):
"""Combo call back to rename a light and display that light again
Args:
lightid ([type]): The light id of the light to rename/display
"""
newname: str = self.get_text_answer_from_modal("Set new name",
"New name:")
if newname:
self.cur_bridge.get_light_by_id(lightid).rename(newname)
self.add_single_light(lightid, unattached)
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 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)
def set_hue(self, event, lightid):
"""Call back for hue slider
Args:
event (wx.ScrollEvent): The scroll event to react to
lightid (int): The light id of the light to adjust hue of
"""
hue: int = event.GetPosition()
light: HueLight = self.cur_bridge.get_light_by_id(lightid)
light.set_hue(hue)
def set_saturation(self, event, lightid):
"""Call back for saturation slider
Args:
event (wx.ScrollEvent): The scroll event to react to
lightid (int): The light id of the light to adjust saturation of
"""
sat: int = event.GetPosition()
light: HueLight = self.cur_bridge.get_light_by_id(lightid)
light.set_sat(sat)
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 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.add_single_light(lightid)
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 of 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)
if __name__ == "__main__":
app = wx.App()
frm = Hui(None, title="Tinge")
frm.Show()
app.MainLoop()

@ -3,26 +3,32 @@ import setuptools
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setuptools.setup(
name="tinge",
version="0.0.3",
author="Micke Nordin",
author_email="hej@mic.ke",
data_files = [('share/applications', ['data/org.smolnet.tinge.desktop']),('share/icons/hicolor/scalable/apps',['data/org.smolnet.tinge.svg']),],
description="A GUI for Philips Hue lights.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://code.smolnet.org/micke/tinge",
project_urls={
"Bug Tracker": "https://code.smolnet.org/micke/tinge/issues",
},
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GPL-3.0",
"Operating System :: OS Independent",
],
package_dir={"": "src"},
packages=setuptools.find_packages(where="src"),
python_requires=">=3.9",
scripts=["scripts/tinge"],
)
setuptools.setup(name="tinge",
version="0.0.3",
author="Micke Nordin",
author_email="hej@mic.ke",
data_files=[
('share/applications', ['data/org.smolnet.tinge.desktop'
]),
('share/icons/hicolor/scalable/apps',
['data/org.smolnet.tinge.svg']),
],
description="A GUI for Philips Hue lights.",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://code.smolnet.org/micke/tinge",
project_urls={
"Bug Tracker":
"https://code.smolnet.org/micke/tinge/issues",
},
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GPL-3.0",
"Operating System :: OS Independent",
],
package_dir={"": "src"},
packages=setuptools.find_packages(where="src"),
python_requires=">=3.9",
entry_points={'gui_scripts': [
'tinge = main:main',
]})

@ -12,16 +12,18 @@ class HueBridge:
"""This class represents a Hue Bridge
"""
def __init__(self, ipaddress: str, username: str,
def __init__(self, id: int, ipaddress: str, username: str,
name: str = "", is_reachable: bool = True):
""" Constructor
Args:
id (int): The id of the bridge
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.id: int = id
self.m_ipaddress: str = ipaddress
self.m_username: str = username
self.m_is_reachable = is_reachable

@ -72,6 +72,14 @@ class HueLight:
"""
return self.m_hue
def get_name(self) -> str:
"""Get the name of the light
Returns:
str: Name of the light
"""
return self.m_name
def get_sat(self):
"""Get current saturation of the light

@ -48,6 +48,7 @@ class Tinge:
"""
upnp: UPnP = UPnP()
discovered_devices = list()
print("Discovering new bridges")
try:
discovered_devices = upnp.discover()
except http.client.BadStatusLine:
@ -84,6 +85,15 @@ class Tinge:
"""
return self.m_bridges
def bridge_discovered(self, ipaddress: str) -> bool:
"""
Check if a bridge has been discovered
"""
for bridge in self.m_bridges:
if bridge.ipaddress == ipaddress:
return True
return False
def read_bridges_from_file(self) -> None:
"""Read config file and add back previously discovered bridges
"""
@ -92,14 +102,15 @@ class Tinge:
mbridges = toml.loads(configfile.read())
for key, value in mbridges.items():
if key not in self.m_discovered:
id = len(self.m_discovered)
response = make_request(key, "{}/".format(value['user']))
if response:
name = "{} ({})".format(response.json()['config']['name'], key)
bridge: HueBridge = HueBridge(key, value['user'], name)
bridge: HueBridge = HueBridge(id, key, value['user'], name)
self.m_bridges.append(bridge)
self.m_discovered.append(key)
else:
bridge: HueBridge = HueBridge(key, value['user'], is_reachable=False)
bridge: HueBridge = HueBridge(id, key, value['user'], is_reachable=False)
self.m_bridges.append(bridge)
self.m_discovered.append(key)

@ -1,3 +1,3 @@
[DEFAULT]
Depends3: python3-upnpy, python3-wxgtk4.0, python3-toml, python3-requests
Debian-Version: 2
Depends3: python3-upnpy, python3-kivy, python3-toml, python3-requests
Debian-Version: 1

Loading…
Cancel
Save