Browse Source

Fix for Issue#16 (#17)

tags/0.0.1
Micke Nordin 4 months ago
parent
commit
df5403a519
Signed by: Micke Nordin <hej@mic.ke> GPG Key ID: 014B273D614BE877
5 changed files with 88 additions and 34 deletions
  1. 20
    7
      main.py
  2. 3
    0
      requirements.txt
  3. 15
    0
      tinge/HueBridge/__init__.py
  4. 14
    8
      tinge/HueUtils/__init__.py
  5. 36
    19
      tinge/__init__.py

+ 20
- 7
main.py View File

@@ -5,7 +5,7 @@ from typing import Union
5 5
 import wx
6 6
 import wx.lib.scrolledpanel as scrolled
7 7
 
8
-from tinge import Tinge, HueBridge, HueGroup, HueLight
8
+from tinge import Tinge, HueBridge, HueGroup, HueLight, HueUtils
9 9
 
10 10
 
11 11
 class Hui(wx.Frame):
@@ -40,7 +40,6 @@ class Hui(wx.Frame):
40 40
         self.m_off_icon: str = '☾'
41 41
         self.m_unreachable_icon: str = '⚠'
42 42
         self.m_tinge: Tinge = Tinge()
43
-        self.m_bridges: list[HueBridge] = self.m_tinge.get_bridges()
44 43
         self.cur_bridge: Union[None, HueBridge] = None
45 44
         self.cur_group: Union[None, HueGroup] = None
46 45
         # create a panel in the frame
@@ -56,8 +55,8 @@ class Hui(wx.Frame):
56 55
         """Add bridges to sizer, the entry point of the program
57 56
         """
58 57
         self.SetTitle('Tinge - All Bridges')
59
-        if self.m_bridges:
60
-            for bridge in self.m_bridges:
58
+        if self.m_tinge.get_bridges():
59
+            for bridge in self.m_tinge.get_bridges():
61 60
                 btn: wx.Button = wx.Button(self.pnl, label=str(bridge))
62 61
                 self.sizer.Add(btn, 0, wx.EXPAND)
63 62
                 self.Bind(wx.EVT_BUTTON,
@@ -217,11 +216,25 @@ class Hui(wx.Frame):
217 216
         else:
218 217
             self.add_single_light(lightid)
219 218
 
220
-    def discover_new_bridges(self):
219
+    def discover_new_bridges(self) -> bool:
221 220
         """Call back for button that is displayed if no bridges were found
221
+
222
+        Returns:
223
+            bool: True if we found any bridge, False otherwise
222 224
         """
223
-        self.m_tinge.discover_new_bridges()
224
-        self.add_bridges()
225
+        found_any: bool = False
226
+        found_bridges: list[dict] = self.m_tinge.discover_new_bridges()
227
+        if found_bridges:
228
+            for bridge in found_bridges:
229
+                user_or_error = HueUtils.connect(bridge['ipaddress'])
230
+                while user_or_error.is_error():
231
+                    user_or_error = HueUtils.connect(bridge['ipaddress'])
232
+                self.m_tinge.append_bridge(HueBridge(bridge['ipaddress'], user_or_error.get_user(), bridge['name']))
233
+                found_any = True
234
+            self.m_tinge.write_all_bridges_to_conf()
235
+            self.add_bridges()
236
+        return found_any
237
+
225 238
 
226 239
     def get_ok_cancel_answer_from_modal(self, message: str) -> bool:
227 240
         """Display a message dialog and return ok or cancel

+ 3
- 0
requirements.txt View File

@@ -1,3 +1,6 @@
1 1
 toml==0.10.1
2 2
 UPnPy==1.1.8
3 3
 requests==2.25.1
4
+
5
+wxPython~=4.0.7
6
+simplejson~=3.17.2

+ 15
- 0
tinge/HueBridge/__init__.py View File

@@ -27,6 +27,8 @@ class HueBridge:
27 27
         else:
28 28
             self.m_name = self.m_ipaddress
29 29
         self.m_lights: list[HueLight] = self.discover_lights()
30
+        self.discover_new_lights()
31
+        self.m_new_lights: list[HueLight] = self.get_new_lights()
30 32
         self.m_groups: list[HueGroup] = self.discover_groups()
31 33
 
32 34
     def __str__(self) -> str:
@@ -183,6 +185,19 @@ class HueBridge:
183 185
         """
184 186
         return self.m_lights
185 187
 
188
+    def get_new_lights(self) -> list[HueLight]:
189
+        path: str = "{}/lights/new".format(self.m_username)
190
+        response = make_request(self.m_ipaddress, path)
191
+        newlights: list[HueLight] = list()
192
+        for lightid, nameobj in response.json().items():
193
+            if lightid != "lastscan":
194
+                print(lightid)
195
+                if not self.get_light_by_id(int(lightid)):
196
+                    lightpath: str = "{}/lights/{}".format(self.m_username, int(lightid))
197
+                    lightresponse = make_request(self.m_ipaddress, lightpath)
198
+                    newlights.append(HueLight(int(lightid), lightresponse.json(), self.get_ipaddress(),self.get_user()))
199
+        return newlights
200
+
186 201
     def get_user(self) -> str:
187 202
         """A user, or username, is more like a password and is needed to authenticate with the Hue  API
188 203
 

+ 14
- 8
tinge/HueUtils/__init__.py View File

@@ -1,6 +1,7 @@
1 1
 #!/usr/bin/env python3
2 2
 # -*- coding: utf-8 -*-
3 3
 import os
4
+from typing import Union
4 5
 
5 6
 import requests
6 7
 
@@ -21,12 +22,14 @@ def connect(ipaddress: str) -> UserOrError:
21 22
     path: str = ""
22 23
     method: str = "POST"
23 24
     response: requests.Response = make_request(ipaddress, path, method, body)
24
-
25
-    data: dict = response.json()[0]
26
-    if 'error' in data.keys():
27
-        user_or_error.set_error(data['error']['type'])
28
-    elif 'success' in data.keys():
29
-        user_or_error.set_user(data['success']['username'])
25
+    if response:
26
+        data: dict = response.json()[0]
27
+        if 'error' in data.keys():
28
+            user_or_error.set_error(data['error']['type'])
29
+        elif 'success' in data.keys():
30
+            user_or_error.set_user(data['success']['username'])
31
+        else:
32
+            user_or_error.set_error(user_or_error.UNKNOWNERROR)
30 33
     else:
31 34
         user_or_error.set_error(user_or_error.UNKNOWNERROR)
32 35
     return user_or_error
@@ -45,7 +48,7 @@ def is_valid_config(filename: str) -> bool:
45 48
 
46 49
 
47 50
 def make_request(ipaddress: str, path: str, method: str = "GET",
48
-                 body: str = '') -> requests.Response:
51
+                 body: str = '') -> Union[None, requests.Response]:
49 52
     """Helper function to make an API call to the Hue API on the bridge
50 53
 
51 54
     Args:
@@ -70,5 +73,8 @@ def make_request(ipaddress: str, path: str, method: str = "GET",
70 73
     elif body:
71 74
         response = rfunct(url, data=body)
72 75
     else:
73
-        response = rfunct(url)
76
+        try:
77
+            response = rfunct(url)
78
+        except requests.exceptions.ConnectionError:
79
+            response = None
74 80
     return response

+ 36
- 19
tinge/__init__.py View File

@@ -1,13 +1,14 @@
1 1
 #!/usr/bin/env python3
2 2
 # -*- coding: utf-8 -*-
3 3
 import os
4
-import time
4
+from typing import Union
5 5
 
6
+import simplejson
6 7
 import toml
7 8
 from upnpy import UPnP
8 9
 
9 10
 from .HueBridge import HueBridge
10
-from .HueUtils import connect, is_valid_config
11
+from .HueUtils import connect, is_valid_config, make_request
11 12
 from .UserOrError import UserOrError
12 13
 
13 14
 
@@ -23,8 +24,10 @@ class Tinge:
23 24
         self.m_config = os.path.join(os.environ['HOME'], ".config/tinge/config")
24 25
         self.create_confdir()
25 26
         self.read_bridges_from_file()
26
-        self.discover_new_bridges()
27
-        self.write_all_bridges_to_conf()
27
+
28
+    def append_bridge(self, bridge: HueBridge):
29
+        self.m_bridges.append(bridge)
30
+        self.m_discovered.append(bridge.get_ipaddress())
28 31
 
29 32
     def create_confdir(self):
30 33
         """Create the config dir if it does not allready exist
@@ -32,29 +35,43 @@ class Tinge:
32 35
         if not os.path.exists(os.path.dirname(self.m_config)):
33 36
             os.makedirs(os.path.dirname(self.m_config))
34 37
 
35
-    def discover_new_bridges(self):
38
+    def discover_new_bridges(self) -> Union[None, list[dict]]:
36 39
         """Use UPnP to discover bridges on the current network
37 40
         """
38 41
         upnp: UPnP = UPnP()
39 42
         discovered_devices = upnp.discover()
43
+        discovered_bridges: list[dict] = list()
44
+        seen_ips: list[str] = list()
40 45
         if not discovered_devices:
41 46
             print("No devices discovered at this time")
42
-            return
47
+            return None
43 48
         for device in discovered_devices:
44
-            if device.get_friendly_name().startswith("Philips hue") and device.host not in self.m_discovered:
45
-                user_or_error: UserOrError = connect(device.host)
46
-                print("Is error: {}".format(str(user_or_error.is_error())))
47
-                while user_or_error.is_error():
48
-                    print("Is error: {}".format(str(user_or_error.get_error_code())))
49
-                    if user_or_error.get_error_code() == 101:
50
-                        print("Please press the button on your Hue Bridge")
51
-                    time.sleep(5)
52
-                    user_or_error = connect(device.host)
53
-                bridge: HueBridge = HueBridge(device.host, user_or_error.get_user())
49
+            discovered: bool = False
50
+            if (device.host not in self.m_discovered) and (device.host not in seen_ips):
51
+                seen_ips.append(device.host)
52
+                # Let's check if the device has the default name, if so we assume it's a hue bridge
53
+                if device.get_friendly_name().startswith("Philips hue"):
54
+                    discovered = True
55
+                # If not we try to do a request against the api and see if we get an answer we can understand
56
+                else:
57
+                    try:
58
+                        response = make_request(device.host, "1234/lights")
59
+                        if response:
60
+                            resp = response.json()[0]
61
+                            if 'error' in resp.keys():
62
+                                m_keys = resp['error'].keys()
63
+                                if 'description' in m_keys and 'address' in m_keys and 'type' in m_keys:
64
+                                    if resp['error']['description'] == "unauthorized user" and resp['error'][
65
+                                        'address'] == "/lights" and resp['error']['type'] == 1:
66
+                                        discovered = True
67
+                    except simplejson.errors.JSONDecodeError:
68
+                        pass
54 69
 
55
-                self.m_bridges.append(bridge)
56
-                self.m_discovered.append(device.host)
57
-        return
70
+                if discovered:
71
+                    bridge = {'ipaddress': device.host, 'name': device.get_friendly_name()}
72
+                    if bridge not in discovered_bridges:
73
+                        discovered_bridges.append(bridge)
74
+        return discovered_bridges
58 75
 
59 76
     def get_bridges(self) -> list[HueBridge]:
60 77
         """Get the bridges

Loading…
Cancel
Save