Implement rename and delete
This commit introduces the ability to rename or delete a light. There is also a special icon for unreachable lights now.
19
README.md
|
@ -21,10 +21,15 @@ cd tinge
|
||||||
Requires a recent Python3 and pip3, most likely python >= 3.7. It is only tested with 3.9 on PostmarketOS on the PinePhone and on Debian Bullseye though.
|
Requires a recent Python3 and pip3, most likely python >= 3.7. It is only tested with 3.9 on PostmarketOS on the PinePhone and on Debian Bullseye though.
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
70
main.py
|
@ -38,6 +38,7 @@ class Hui(wx.Frame):
|
||||||
super().__init__(*args, **kw)
|
super().__init__(*args, **kw)
|
||||||
self.m_on_icon: str = '☼'
|
self.m_on_icon: str = '☼'
|
||||||
self.m_off_icon: str = '☾'
|
self.m_off_icon: str = '☾'
|
||||||
|
self.m_unreachable_icon: str = '⚠'
|
||||||
self.m_tinge: Tinge = Tinge()
|
self.m_tinge: Tinge = Tinge()
|
||||||
self.m_bridges: list[HueBridge] = self.m_tinge.get_bridges()
|
self.m_bridges: list[HueBridge] = self.m_tinge.get_bridges()
|
||||||
self.cur_bridge: Union[None, HueBridge] = None
|
self.cur_bridge: Union[None, HueBridge] = None
|
||||||
|
@ -114,6 +115,8 @@ class Hui(wx.Frame):
|
||||||
icon: str = self.m_off_icon
|
icon: str = self.m_off_icon
|
||||||
if light.is_on():
|
if light.is_on():
|
||||||
icon = self.m_on_icon
|
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)
|
toggle_btn: wx.Button = wx.Button(self.pnl, label=icon)
|
||||||
inner_sizer.Add(toggle_btn, 1, wx.EXPAND)
|
inner_sizer.Add(toggle_btn, 1, wx.EXPAND)
|
||||||
self.Bind(wx.EVT_BUTTON,
|
self.Bind(wx.EVT_BUTTON,
|
||||||
|
@ -145,6 +148,8 @@ class Hui(wx.Frame):
|
||||||
icon: str = self.m_off_icon
|
icon: str = self.m_off_icon
|
||||||
if is_on:
|
if is_on:
|
||||||
icon = self.m_on_icon
|
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)
|
toggle_btn: wx.Button = wx.Button(self.pnl, label=icon)
|
||||||
self.sizer.Add(toggle_btn, 0, wx.EXPAND)
|
self.sizer.Add(toggle_btn, 0, wx.EXPAND)
|
||||||
self.Bind(wx.EVT_BUTTON,
|
self.Bind(wx.EVT_BUTTON,
|
||||||
|
@ -187,6 +192,30 @@ class Hui(wx.Frame):
|
||||||
self.sizer.Add(e_slider, 0, wx.EXPAND)
|
self.sizer.Add(e_slider, 0, wx.EXPAND)
|
||||||
self.Bind(wx.EVT_SCROLL,
|
self.Bind(wx.EVT_SCROLL,
|
||||||
lambda event: self.set_saturation(event, light.get_id()), e_slider)
|
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),
|
||||||
|
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_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):
|
def discover_new_bridges(self):
|
||||||
"""Call back for button that is displayed if no bridges were found
|
"""Call back for button that is displayed if no bridges were found
|
||||||
|
@ -194,6 +223,34 @@ class Hui(wx.Frame):
|
||||||
self.m_tinge.discover_new_bridges()
|
self.m_tinge.discover_new_bridges()
|
||||||
self.add_bridges()
|
self.add_bridges()
|
||||||
|
|
||||||
|
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):
|
def goto_bridge(self, bridge: HueBridge):
|
||||||
"""Call back for a bridge button
|
"""Call back for a bridge button
|
||||||
|
|
||||||
|
@ -217,6 +274,17 @@ class Hui(wx.Frame):
|
||||||
self.cur_group = group
|
self.cur_group = group
|
||||||
self.add_lights(group.get_lights())
|
self.add_lights(group.get_lights())
|
||||||
|
|
||||||
|
def rename_light_and_goto_light(self, lightid):
|
||||||
|
"""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)
|
||||||
|
|
||||||
def set_brightness(self, event: wx.ScrollEvent, lightid: int):
|
def set_brightness(self, event: wx.ScrollEvent, lightid: int):
|
||||||
"""Call back for brightness slider
|
"""Call back for brightness slider
|
||||||
|
|
||||||
|
@ -283,7 +351,7 @@ class Hui(wx.Frame):
|
||||||
"""Combo call back for toggle and goto group
|
"""Combo call back for toggle and goto group
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
lightid (int): The light id oof the light to toggle
|
lightid (int): The light id of the light to toggle
|
||||||
lights (list[HueLight]): The lights to display after toggle
|
lights (list[HueLight]): The lights to display after toggle
|
||||||
"""
|
"""
|
||||||
self.cur_bridge.get_light_by_id(lightid).toggle()
|
self.cur_bridge.get_light_by_id(lightid).toggle()
|
||||||
|
|
Before Width: | Height: | Size: 77 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 79 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
BIN
screenshots/scrot10.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
screenshots/scrot11.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
screenshots/scrot12.png
Normal file
After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
BIN
screenshots/scrot3.png
Normal file
After Width: | Height: | Size: 73 KiB |
BIN
screenshots/scrot4.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
screenshots/scrot5.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
screenshots/scrot6.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
screenshots/scrot7.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
screenshots/scrot8.png
Normal file
After Width: | Height: | Size: 78 KiB |
BIN
screenshots/scrot9.png
Normal file
After Width: | Height: | Size: 46 KiB |
|
@ -190,3 +190,14 @@ class HueBridge:
|
||||||
str: The username
|
str: The username
|
||||||
"""
|
"""
|
||||||
return self.m_username
|
return self.m_username
|
||||||
|
|
||||||
|
def remove_light(self, light: HueLight):
|
||||||
|
"""Remove a light from all groups and self
|
||||||
|
|
||||||
|
Args:
|
||||||
|
light (HueLight): The light to remove
|
||||||
|
|
||||||
|
"""
|
||||||
|
for group in self.m_groups:
|
||||||
|
group.remove_light(light)
|
||||||
|
self.m_lights.remove(light)
|
||||||
|
|
|
@ -142,6 +142,12 @@ class HueGroup:
|
||||||
on = True
|
on = True
|
||||||
return on
|
return on
|
||||||
|
|
||||||
|
def remove_light(self, light: HueLight):
|
||||||
|
"""Remove a light
|
||||||
|
"""
|
||||||
|
if light in self.m_lights:
|
||||||
|
self.m_lights.remove(light)
|
||||||
|
|
||||||
def toggle(self):
|
def toggle(self):
|
||||||
"""Toggle all lights of the group
|
"""Toggle all lights of the group
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -297,6 +297,14 @@ class HueLight:
|
||||||
"""
|
"""
|
||||||
return self.get_sat() != -1
|
return self.get_sat() != -1
|
||||||
|
|
||||||
|
def delete(self):
|
||||||
|
"""Delete the light
|
||||||
|
|
||||||
|
"""
|
||||||
|
method: str = "DELETE"
|
||||||
|
path = "{}/lights/{}".format(self.m_parent_bridge_user, self.get_id())
|
||||||
|
make_request(self.m_parent_bridge_ip, path, method)
|
||||||
|
|
||||||
def get_brightness(self) -> int:
|
def get_brightness(self) -> int:
|
||||||
"""Get currrent brightness
|
"""Get currrent brightness
|
||||||
|
|
||||||
|
@ -362,6 +370,18 @@ class HueLight:
|
||||||
"""
|
"""
|
||||||
return self.get_state().is_reachable()
|
return self.get_state().is_reachable()
|
||||||
|
|
||||||
|
def rename(self, name):
|
||||||
|
"""Set the name of the light
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name (str): The new name for the light
|
||||||
|
"""
|
||||||
|
method: str = "PUT"
|
||||||
|
payload = '{{"name":"{0}"}}'.format(name)
|
||||||
|
path = "{}/lights/{}".format(self.m_parent_bridge_user, self.get_id())
|
||||||
|
make_request(self.m_parent_bridge_ip, path, method, payload)
|
||||||
|
self.update_state()
|
||||||
|
|
||||||
def set_brightness(self, bri: int):
|
def set_brightness(self, bri: int):
|
||||||
"""Set the brightness of the light
|
"""Set the brightness of the light
|
||||||
|
|
||||||
|
@ -431,3 +451,4 @@ class HueLight:
|
||||||
path: str = "{}/lights/{}".format(self.m_parent_bridge_user, self.m_id)
|
path: str = "{}/lights/{}".format(self.m_parent_bridge_user, self.m_id)
|
||||||
response = make_request(self.m_parent_bridge_ip, path)
|
response = make_request(self.m_parent_bridge_ip, path)
|
||||||
self.m_state = HueLight.State(json.loads(response.text)['state'])
|
self.m_state = HueLight.State(json.loads(response.text)['state'])
|
||||||
|
self.m_name = json.loads(response.text)['name']
|
||||||
|
|