Coverage for /Users/fmorton/GitHub/BirdBrain-Python-Library-2/src/birdbrain_request.py: 96%
124 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 11:24 -0400
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-12 11:24 -0400
1import inspect
2import time
3import urllib.request
5from birdbrain_constant import BirdbrainConstant
6from birdbrain_exception import BirdbrainException
7from birdbrain_utility import BirdbrainUtility
9class BirdbrainRequest:
10 @classmethod
11 def uri(self, *args):
12 return("http://127.0.0.1:30061/" + BirdbrainUtility.flatten_string(args))
14 @classmethod
15 def is_not_connected_response(self, response):
16 return((response.lower() == "not connected"))
18 @classmethod
19 def response(self, *args):
20 if "false" in args: return False
22 try:
23 if BirdbrainConstant.BIRDBRAIN_TEST: print("Test: URI", self.uri(*args))
25 response_request = urllib.request.urlopen(self.uri(*args))
26 except (ConnectionError, urllib.error.URLError, urllib.error.HTTPError):
27 raise(BirdbrainException("Error: Request to device failed"))
29 response = response_request.read().decode('utf-8').lower()
31 if BirdbrainConstant.BIRDBRAIN_TEST: print("Test: response", response)
33 if (self.is_not_connected_response(response)): raise(BirdbrainException("Error: The device is not connected"))
35 time.sleep(0.01) # hack to prevent http requests from overloading the BlueBird Connector
37 return response
39 @classmethod
40 def response_status(self, *args):
41 return BirdbrainRequest.request_status(BirdbrainRequest.response(args))
43 @classmethod
44 def is_connected(self, device):
45 try:
46 response = self.response('hummingbird', 'in', 'orientation', 'Shake', device)
47 except BirdbrainException:
48 return False
50 return True
52 @classmethod
53 def is_not_connected(self, device):
54 return(not self.is_connected(device))
56 @classmethod
57 def stop_all(self, device):
58 return(self.request_status(self.response('hummingbird', 'out', 'stopall', device)))
60 @classmethod
61 def request_status(self, status):
62 if BirdbrainConstant.BIRDBRAIN_TEST: print("Test: request status is", status)
64 if status is None: return None
66 if status == 'true': return(True)
67 if status == 'led set': return(True)
68 if status == 'triled set': return(True)
69 if status == 'servo set': return(True)
70 if status == 'buzzer set': return(True)
71 if status == 'symbol set': return(True)
72 if status == 'print set': return(True)
73 if status == 'all stopped': return(True)
75 if status == 'finch moved': return(True)
76 if status == 'finch turned': return(True)
77 if status == 'finch wheels started': return(True)
78 if status == 'finch wheels stopped': return(True)
79 if status == 'finch encoders reset': return(True)
81 if status == 'false': return(False)
82 if status == 'not connected': return(False)
83 if status == 'invalid orientation': return(False)
84 if status == 'invalid port': return(False)
86 return(None)
88 @classmethod
89 def calculate_angle(self, intensity):
90 return int(int(intensity) * 255 / 180)
92 @classmethod
93 def calculate_intensity(self, intensity):
94 return int(int(BirdbrainUtility.bounds(intensity, 0, 100)) * 255 / 100)
96 @classmethod
97 def calculate_speed(self, speed):
98 if int(speed) in range(-10, 10): return 255
100 # QUESTION: why this calculation instead of normal mapping to 0..255 (and 255 means stop)
101 # return ((int(speed) * 23 / 100) + 122)
103 if int(speed) < 0:
104 return int(119 - (-int(speed) / 100 * 45))
105 else:
106 return int((int(speed) / 100 * 25) + 121)
108 @classmethod
109 def calculate_left_or_right(self, direction):
110 if direction == BirdbrainConstant.LEFT: return 'Left'
111 if direction == BirdbrainConstant.RIGHT: return 'Right'
113 return 'None'
115 @classmethod
116 def validate(self, validate, valid_range, validate_message):
117 if not str(validate) in valid_range: raise BirdbrainException(validate_message)
119 return True
121 @classmethod
122 def validate_port(self, port, valid_range, allow_all = False):
123 if allow_all and str(port) == 'all': return True
125 return BirdbrainRequest.validate(port, valid_range, f"Port {str(port)} out of range.")
127 @classmethod
128 def sensor_response(self, device, sensor, other = None, options = {}):
129 if other is False: return False # for invalid directions
131 factor = options["factor"] if "factor" in options else BirdbrainConstant.DEFAULT_FACTOR
132 min_response = options["min_response"] if "min_response" in options else BirdbrainConstant.DEFAULT_UNLIMITED_MIN_RESPONSE
133 max_response = options["max_response"] if "max_response" in options else BirdbrainConstant.DEFAULT_UNLIMITED_MAX_RESPONSE
134 type_method = options["type_method"] if "type_method" in options else BirdbrainConstant.DEFAULT_TYPE_METHOD
136 request = ['hummingbird', 'in', sensor]
137 if other is not None: request.append(other)
138 request.append(device)
140 response = (float(BirdbrainRequest.response(request)) * factor)
142 response = round(BirdbrainUtility.decimal_bounds(response, min_response, max_response), 3)
144 if type_method == 'int': return int(response)
146 return response
148 @classmethod
149 def xyz_response(self, device, sensor, type_method = 'int'):
150 x = round(float(BirdbrainRequest.response('hummingbird', 'in', sensor, 'X', device)), 3)
151 y = round(float(BirdbrainRequest.response('hummingbird', 'in', sensor, 'Y', device)), 3)
152 z = round(float(BirdbrainRequest.response('hummingbird', 'in', sensor, 'Z', device)), 3)
154 if type_method == 'int':
155 return [int(x), int(y), int(z)]
156 else:
157 return [float(x), float(y), float(z)]
159 @classmethod
160 def tri_led_response(self, device, port, r_intensity, g_intensity, b_intensity, valid_range, allow_all = False):
161 """Set TriLED of a certain port requested to a valid intensity."""
162 self.validate_port(port, valid_range, allow_all)
164 calc_r = BirdbrainRequest.calculate_intensity(r_intensity)
165 calc_g = BirdbrainRequest.calculate_intensity(g_intensity)
166 calc_b = BirdbrainRequest.calculate_intensity(b_intensity)
168 return BirdbrainRequest.response_status('hummingbird', 'out', 'triled', port, calc_r, calc_g, calc_b, device)
170 @classmethod
171 def orientation_response(self, device, sensor, orientations, orientation_results, orientation_in_between):
172 for index, target_orientation in enumerate(orientations):
173 response = self.response("hummingbird", "in", sensor, target_orientation, device)
175 if (response == "true"): return orientation_results[index]
177 return orientation_in_between