Coverage for farmbot/functions/resources.py: 100%
100 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-09-12 12:03 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-09-12 12:03 -0700
1"""
2Resources class.
3"""
5# └── functions/resources.py
6# ├── [BROKER] sort_points()
7# ├── [BROKER] sequence()
8# ├── [BROKER] get_seed_tray_cell()
9# ├── [BROKER] detect_weeds()
10# ├── [BROKER] lua()
11# ├── [BROKER] if_statement()
12# └── [BROKER] assertion()
14from .broker import BrokerConnect
15from .information import Information
17ASSERTION_TYPES = ["abort", "recover", "abort_recover", "continue"]
20def validate_assertion_type(assertion_type):
21 """Validate assertion type."""
22 if assertion_type not in ASSERTION_TYPES:
23 msg = "Invalid assertion_type: "
24 msg += f"{assertion_type} not in {ASSERTION_TYPES}"
25 raise ValueError(msg)
28OPERATORS = ["<", ">", "is", "not", "is_undefined"]
29IF_STATEMENT_VARIABLE_STRINGS = [
30 "x",
31 "y",
32 "z",
33 *[f"pin{str(i)}" for i in range(70)]]
34NAMED_PIN_TYPES = ["Peripheral", "Sensor"]
37def validate_if_statement_args(named_pin_type, variable, operator):
38 """Validate if statement arguments."""
39 if operator not in OPERATORS:
40 raise ValueError(f"Invalid operator: {operator} not in {OPERATORS}")
41 if named_pin_type is None and variable not in IF_STATEMENT_VARIABLE_STRINGS:
42 msg = "Invalid variable: "
43 msg += f"{variable} not in {IF_STATEMENT_VARIABLE_STRINGS}"
44 raise ValueError(msg)
45 if named_pin_type is not None and named_pin_type not in NAMED_PIN_TYPES:
46 msg = "Invalid named_pin_type: "
47 msg += f"{named_pin_type} not in {NAMED_PIN_TYPES}"
48 raise ValueError(msg)
51class Resources():
52 """Resources class."""
54 def __init__(self, state):
55 self.broker = BrokerConnect(state)
56 self.info = Information(state)
57 self.state = state
59 # TODO: mark_as()
61 # TODO: sort_points(points, method)
63 def sequence(self, sequence_name):
64 """Executes a predefined sequence."""
65 self.state.print_status(
66 description="Running {sequence_name} sequence.")
68 sequence = self.info.get_resource_by_name(
69 endpoint="sequences",
70 resource_name=sequence_name,
71 name_key="name")
72 if sequence is None:
73 return
75 sequence_message = {
76 "kind": "execute",
77 "args": {
78 "sequence_id": sequence["id"],
79 }
80 }
82 self.broker.publish(sequence_message)
84 def get_seed_tray_cell(self, tray_name, tray_cell):
85 """Identifies and returns the location of specified cell in the seed tray."""
86 self.state.print_status(
87 description="Identifying seed tray cell location.")
89 tray_tool = self.info.get_resource_by_name("tools", tray_name, "name")
90 if tray_tool is None:
91 return
92 tray_data = self.info.get_resource_by_name(
93 "points", tray_tool["id"], "tool_id", {"pointer_type": "ToolSlot"})
94 if tray_data is None:
95 self.state.print_status(
96 description=f"{tray_name} must be mounted in a slot.",
97 update_only=True)
98 return
100 cell = tray_cell.upper()
102 seeder_needle_offset = 17.5
103 cell_spacing = 12.5
105 cells = {
106 "A1": {"x": 0, "y": 0},
107 "A2": {"x": 0, "y": 1},
108 "A3": {"x": 0, "y": 2},
109 "A4": {"x": 0, "y": 3},
111 "B1": {"x": -1, "y": 0},
112 "B2": {"x": -1, "y": 1},
113 "B3": {"x": -1, "y": 2},
114 "B4": {"x": -1, "y": 3},
116 "C1": {"x": -2, "y": 0},
117 "C2": {"x": -2, "y": 1},
118 "C3": {"x": -2, "y": 2},
119 "C4": {"x": -2, "y": 3},
121 "D1": {"x": -3, "y": 0},
122 "D2": {"x": -3, "y": 1},
123 "D3": {"x": -3, "y": 2},
124 "D4": {"x": -3, "y": 3}
125 }
127 if cell not in cells:
128 msg = "Seed Tray Cell must be one of **A1** through **D4**"
129 raise ValueError(msg)
131 flip = 1
132 if tray_data["pullout_direction"] == 1:
133 flip = 1
134 elif tray_data["pullout_direction"] == 2:
135 flip = -1
136 else:
137 msg = "Seed Tray **SLOT DIRECTION** must be `Positive X` or `Negative X`"
138 raise ValueError(msg)
140 a1 = {
141 "x": tray_data["x"] - seeder_needle_offset + (1.5 * cell_spacing * flip),
142 "y": tray_data["y"] - (1.5 * cell_spacing * flip),
143 "z": tray_data["z"]
144 }
146 offset = {
147 "x": cell_spacing * cells[cell]["x"] * flip,
148 "y": cell_spacing * cells[cell]["y"] * flip
149 }
151 cell_xyz = {
152 "x": a1["x"] + offset["x"],
153 "y": a1["y"] + offset["y"],
154 "z": a1["z"],
155 }
157 self.state.print_status(
158 description=f"Cell {tray_cell} is at {cell_xyz}.",
159 update_only=True)
160 return cell_xyz
162 def detect_weeds(self):
163 """Scans the garden to detect weeds."""
164 self.state.print_status(description="Detecting weeds...")
166 detect_weeds_message = {
167 "kind": "execute_script",
168 "args": {
169 "label": "plant-detection"
170 }
171 }
173 self.broker.publish(detect_weeds_message)
175 def lua(self, lua_code):
176 """Executes custom Lua code snippets to perform complex tasks or automations."""
177 self.state.print_status(description="Running Lua code")
179 lua_message = {
180 "kind": "lua",
181 "args": {
182 "lua": lua_code.strip()
183 }
184 }
186 self.broker.publish(lua_message)
188 def if_statement(self,
189 variable,
190 operator,
191 value,
192 then_sequence_name=None,
193 else_sequence_name=None,
194 named_pin_type=None):
195 """Performs conditional check and executes actions based on the outcome."""
197 self.state.print_status(description="Executing if statement.")
199 validate_if_statement_args(named_pin_type, variable, operator)
200 if named_pin_type is not None:
201 endpoint = named_pin_type.lower() + "s"
202 resource = self.info.get_resource_by_name(endpoint, variable)
203 if resource is None:
204 return
205 variable = {
206 "kind": "named_pin",
207 "args": {
208 "pin_type": named_pin_type,
209 "pin_id": resource["id"]
210 }
211 }
213 if_statement_message = {
214 "kind": "_if",
215 "args": {
216 "lhs": variable,
217 "op": operator,
218 "rhs": value,
219 "_then": {"kind": "nothing", "args": {}},
220 "_else": {"kind": "nothing", "args": {}},
221 }
222 }
224 sequence_names = {
225 "_then": then_sequence_name,
226 "_else": else_sequence_name,
227 }
228 for key, sequence_name in sequence_names.items():
229 if sequence_name is not None:
230 sequence = self.info.get_resource_by_name(
231 endpoint="sequences",
232 resource_name=sequence_name,
233 name_key="name")
234 if sequence is None:
235 return
236 sequence_id = sequence["id"]
237 if_statement_message["args"][key] = {
238 "kind": "execute",
239 "args": {"sequence_id": sequence_id},
240 }
242 self.broker.publish(if_statement_message)
244 def assertion(self, lua_code, assertion_type, recovery_sequence_name=None):
245 """Evaluates an expression."""
246 self.state.print_status(description="Executing assertion.")
248 validate_assertion_type(assertion_type)
250 assertion_message = {
251 "kind": "assertion",
252 "args": {
253 "assertion_type": assertion_type,
254 "lua": lua_code,
255 "_then": {"kind": "nothing", "args": {}},
256 }
257 }
259 if recovery_sequence_name is not None:
260 sequence = self.info.get_resource_by_name(
261 endpoint="sequences",
262 resource_name=recovery_sequence_name,
263 name_key="name")
264 if sequence is None:
265 return
266 recovery_sequence_id = sequence["id"]
267 assertion_message["args"]["_then"] = {
268 "kind": "execute",
269 "args": {"sequence_id": recovery_sequence_id},
270 }
272 self.broker.publish(assertion_message)