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

1import inspect 

2import time 

3import urllib.request 

4 

5from birdbrain_constant import BirdbrainConstant 

6from birdbrain_exception import BirdbrainException 

7from birdbrain_utility import BirdbrainUtility 

8 

9class BirdbrainRequest: 

10 @classmethod 

11 def uri(self, *args): 

12 return("http://127.0.0.1:30061/" + BirdbrainUtility.flatten_string(args)) 

13 

14 @classmethod 

15 def is_not_connected_response(self, response): 

16 return((response.lower() == "not connected")) 

17 

18 @classmethod 

19 def response(self, *args): 

20 if "false" in args: return False 

21 

22 try: 

23 if BirdbrainConstant.BIRDBRAIN_TEST: print("Test: URI", self.uri(*args)) 

24 

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")) 

28 

29 response = response_request.read().decode('utf-8').lower() 

30 

31 if BirdbrainConstant.BIRDBRAIN_TEST: print("Test: response", response) 

32 

33 if (self.is_not_connected_response(response)): raise(BirdbrainException("Error: The device is not connected")) 

34 

35 time.sleep(0.01) # hack to prevent http requests from overloading the BlueBird Connector 

36 

37 return response 

38 

39 @classmethod 

40 def response_status(self, *args): 

41 return BirdbrainRequest.request_status(BirdbrainRequest.response(args)) 

42 

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 

49 

50 return True 

51 

52 @classmethod 

53 def is_not_connected(self, device): 

54 return(not self.is_connected(device)) 

55 

56 @classmethod 

57 def stop_all(self, device): 

58 return(self.request_status(self.response('hummingbird', 'out', 'stopall', device))) 

59 

60 @classmethod 

61 def request_status(self, status): 

62 if BirdbrainConstant.BIRDBRAIN_TEST: print("Test: request status is", status) 

63 

64 if status is None: return None 

65 

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) 

74 

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) 

80 

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) 

85 

86 return(None) 

87 

88 @classmethod 

89 def calculate_angle(self, intensity): 

90 return int(int(intensity) * 255 / 180) 

91 

92 @classmethod 

93 def calculate_intensity(self, intensity): 

94 return int(int(BirdbrainUtility.bounds(intensity, 0, 100)) * 255 / 100) 

95 

96 @classmethod 

97 def calculate_speed(self, speed): 

98 if int(speed) in range(-10, 10): return 255 

99 

100 # QUESTION: why this calculation instead of normal mapping to 0..255 (and 255 means stop) 

101 # return ((int(speed) * 23 / 100) + 122) 

102 

103 if int(speed) < 0: 

104 return int(119 - (-int(speed) / 100 * 45)) 

105 else: 

106 return int((int(speed) / 100 * 25) + 121) 

107 

108 @classmethod 

109 def calculate_left_or_right(self, direction): 

110 if direction == BirdbrainConstant.LEFT: return 'Left' 

111 if direction == BirdbrainConstant.RIGHT: return 'Right' 

112 

113 return 'None' 

114 

115 @classmethod 

116 def validate(self, validate, valid_range, validate_message): 

117 if not str(validate) in valid_range: raise BirdbrainException(validate_message) 

118 

119 return True 

120 

121 @classmethod 

122 def validate_port(self, port, valid_range, allow_all = False): 

123 if allow_all and str(port) == 'all': return True 

124 

125 return BirdbrainRequest.validate(port, valid_range, f"Port {str(port)} out of range.") 

126 

127 @classmethod 

128 def sensor_response(self, device, sensor, other = None, options = {}): 

129 if other is False: return False # for invalid directions 

130 

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 

135 

136 request = ['hummingbird', 'in', sensor] 

137 if other is not None: request.append(other) 

138 request.append(device) 

139 

140 response = (float(BirdbrainRequest.response(request)) * factor) 

141 

142 response = round(BirdbrainUtility.decimal_bounds(response, min_response, max_response), 3) 

143 

144 if type_method == 'int': return int(response) 

145 

146 return response 

147 

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) 

153 

154 if type_method == 'int': 

155 return [int(x), int(y), int(z)] 

156 else: 

157 return [float(x), float(y), float(z)] 

158 

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) 

163 

164 calc_r = BirdbrainRequest.calculate_intensity(r_intensity) 

165 calc_g = BirdbrainRequest.calculate_intensity(g_intensity) 

166 calc_b = BirdbrainRequest.calculate_intensity(b_intensity) 

167 

168 return BirdbrainRequest.response_status('hummingbird', 'out', 'triled', port, calc_r, calc_g, calc_b, device) 

169 

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) 

174 

175 if (response == "true"): return orientation_results[index] 

176 

177 return orientation_in_between