Coverage for farmbot_sidecar_starter_pack/functions/information.py: 100%
116 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-09-11 15:43 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-09-11 15:43 -0700
1"""
2Information class.
3"""
5# └── functions/information
6# ├── [API] api_get()
7# ├── [API] api_patch()
8# ├── [API] api_post()
9# ├── [API] api_delete()
10# ├── [API] safe_z()
11# ├── [API] garden_size()
12# ├── [API] group()
13# ├── [API] curve()
14# ├── [BROKER] measure_soil_height()
15# ├── [BROKER] read_status()
16# ├── [BROKER] read_pin()
17# └── [BROKER] read_sensor()
19from .broker import BrokerConnect
20from .api import ApiConnect
23class Information():
24 """Information class."""
26 def __init__(self, state):
27 self.broker = BrokerConnect(state)
28 self.api = ApiConnect(state)
29 self.state = state
31 def api_get(self, endpoint, database_id=None, data_print=True):
32 """Get information about a specific endpoint."""
33 self.state.print_status(
34 description=f"Retrieving {endpoint} information.")
36 endpoint_data = self.api.request("GET", endpoint, database_id)
38 if data_print:
39 self.state.print_status(
40 update_only=True,
41 endpoint_json=endpoint_data)
42 else:
43 self.state.print_status(
44 update_only=True,
45 description=f"Fetched {len(endpoint_data)} items.")
47 return endpoint_data
49 def api_patch(self, endpoint, new_data, database_id=None):
50 """Change information contained within an endpoint."""
51 self.state.print_status(description=f"Editing {endpoint}.")
53 result = self.api.request(
54 method="PATCH",
55 endpoint=endpoint,
56 database_id=database_id,
57 payload=new_data)
59 self.state.print_status(update_only=True, endpoint_json=result)
61 return result
63 def api_post(self, endpoint, new_data):
64 """Create new information contained within an endpoint."""
65 self.state.print_status(description=f"Adding new data to {endpoint}.")
67 result = self.api.request(
68 method="POST",
69 endpoint=endpoint,
70 database_id=None,
71 payload=new_data)
73 self.state.print_status(update_only=True, endpoint_json=result)
75 return result
77 def api_delete(self, endpoint, database_id=None):
78 """Delete information contained within an endpoint."""
79 self.state.print_status(
80 description=f"Deleting {endpoint} with id={database_id}.")
82 result = self.api.request("DELETE", endpoint, database_id=database_id)
84 self.state.print_status(update_only=True, endpoint_json=result)
86 return result
88 def safe_z(self):
89 """Returns the highest safe point along the z-axis."""
90 self.state.print_status(description="Retrieving safe z value...")
92 config_data = self.api_get('fbos_config')
93 z_value = config_data["safe_height"]
95 self.state.print_status(
96 description=f"Safe z={z_value}", update_only=True)
97 return z_value
99 def garden_size(self):
100 """Return size of garden bed."""
101 self.state.print_status(description="Retrieving garden size...")
103 json_data = self.api_get('firmware_config')
105 x_steps = json_data['movement_axis_nr_steps_x']
106 x_mm = json_data['movement_step_per_mm_x']
108 y_steps = json_data['movement_axis_nr_steps_y']
109 y_mm = json_data['movement_step_per_mm_y']
111 z_steps = json_data['movement_axis_nr_steps_z']
112 z_mm = json_data['movement_step_per_mm_z']
114 garden_size = {
115 "x": x_steps / x_mm,
116 "y": y_steps / y_mm,
117 "z": z_steps / z_mm,
118 }
120 self.state.print_status(endpoint_json=garden_size, update_only=True)
121 return garden_size
123 def group(self, group_id=None):
124 """Returns all group info or single by id."""
125 self.state.print_status(description="Retrieving group information...")
127 if group_id is None:
128 group_data = self.api_get("point_groups")
129 else:
130 group_data = self.api_get('point_groups', group_id)
132 self.state.print_status(endpoint_json=group_data, update_only=True)
133 return group_data
135 def curve(self, curve_id=None):
136 """Returns all curve info or single by id."""
137 self.state.print_status(description="Retrieving curve information...")
139 if curve_id is None:
140 curve_data = self.api_get("curves")
141 else:
142 curve_data = self.api_get('curves', curve_id)
144 self.state.print_status(endpoint_json=curve_data, update_only=True)
145 return curve_data
147 def measure_soil_height(self):
148 """Use the camera to measure the soil height at the current location."""
149 self.state.print_status(description="Measuring soil height...")
151 measure_soil_height_message = {
152 "kind": "execute_script",
153 "args": {
154 "label": "Measure Soil Height"
155 }
156 }
158 self.broker.publish(measure_soil_height_message)
160 def read_status(self, path=None):
161 """Returns the FarmBot status tree."""
162 path_str = "" if path is None else f" of {path}"
163 self.state.print_status(description=f"Reading status{path_str}...")
164 status_message = {
165 "kind": "read_status",
166 "args": {}
167 }
168 self.broker.publish(status_message)
170 status_trees = self.state.last_messages.get("status", [])
171 status_tree = None if len(status_trees) == 0 else status_trees[-1]
173 if path is not None:
174 for key in path.split("."):
175 status_tree = status_tree[key]
177 self.state.print_status(update_only=True, endpoint_json=status_tree)
178 return status_tree
180 @staticmethod
181 def convert_mode_to_number(mode):
182 """Converts mode string to mode number."""
183 modes = ["digital", "analog"]
184 if str(mode).lower() not in modes:
185 raise ValueError(f"Invalid mode: {mode} not in {modes}")
186 return 0 if mode.lower() == "digital" else 1
188 def read_pin(self, pin_number, mode="digital"):
189 """Reads the given pin by number."""
190 pin_mode = self.convert_mode_to_number(mode)
191 self.state.print_status(
192 description=f"Reading pin {pin_number} ({mode})...")
193 read_pin_message = {
194 "kind": "read_pin",
195 "args": {
196 "pin_number": pin_number,
197 "label": "---",
198 "pin_mode": pin_mode,
199 }
200 }
201 self.broker.publish(read_pin_message)
203 def read_sensor(self, sensor_name):
204 """Reads the given sensor."""
205 self.state.print_status(description=f"Reading {sensor_name} sensor...")
206 sensor = self.get_resource_by_name("sensors", sensor_name)
207 if sensor is None:
208 return
209 sensor_id = sensor["id"]
210 mode = sensor["mode"]
212 sensor_message = {
213 "kind": "read_pin",
214 "args": {
215 "pin_mode": mode,
216 "label": "---",
217 "pin_number": {
218 "kind": "named_pin",
219 "args": {
220 "pin_type": "Sensor",
221 "pin_id": sensor_id,
222 }
223 }
224 }
225 }
227 self.broker.publish(sensor_message)
229 def get_resource_by_name(self, endpoint, resource_name, name_key="label", query=None):
230 """Find a resource by name."""
231 self.state.print_status(
232 description=f"Searching for {resource_name} in {endpoint}.")
233 resources = self.state.fetch_cache(endpoint)
234 if resources is None:
235 resources = self.api_get(endpoint, data_print=False)
236 else:
237 self.state.print_status(
238 description=f"Using {len(resources)} cached items.")
239 if query is not None:
240 for key, value in query.items():
241 resources = [res for res in resources if res[key] == value]
242 names = [resource[name_key] for resource in resources]
243 if resource_name not in names:
244 error = f"ERROR: '{resource_name}' not in {endpoint}: {names}."
245 self.state.print_status(description=error, update_only=True)
246 self.state.error = error
247 self.state.clear_cache(endpoint)
248 return None
250 self.state.save_cache(endpoint, resources)
251 resource = [p for p in resources if p[name_key] == resource_name][0]
252 return resource