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

1""" 

2Resources class. 

3""" 

4 

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

13 

14from .broker import BrokerConnect 

15from .information import Information 

16 

17ASSERTION_TYPES = ["abort", "recover", "abort_recover", "continue"] 

18 

19 

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) 

26 

27 

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

35 

36 

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) 

49 

50 

51class Resources(): 

52 """Resources class.""" 

53 

54 def __init__(self, state): 

55 self.broker = BrokerConnect(state) 

56 self.info = Information(state) 

57 self.state = state 

58 

59 # TODO: mark_as() 

60 

61 # TODO: sort_points(points, method) 

62 

63 def sequence(self, sequence_name): 

64 """Executes a predefined sequence.""" 

65 self.state.print_status( 

66 description="Running {sequence_name} sequence.") 

67 

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 

74 

75 sequence_message = { 

76 "kind": "execute", 

77 "args": { 

78 "sequence_id": sequence["id"], 

79 } 

80 } 

81 

82 self.broker.publish(sequence_message) 

83 

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

88 

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 

99 

100 cell = tray_cell.upper() 

101 

102 seeder_needle_offset = 17.5 

103 cell_spacing = 12.5 

104 

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}, 

110 

111 "B1": {"x": -1, "y": 0}, 

112 "B2": {"x": -1, "y": 1}, 

113 "B3": {"x": -1, "y": 2}, 

114 "B4": {"x": -1, "y": 3}, 

115 

116 "C1": {"x": -2, "y": 0}, 

117 "C2": {"x": -2, "y": 1}, 

118 "C3": {"x": -2, "y": 2}, 

119 "C4": {"x": -2, "y": 3}, 

120 

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 } 

126 

127 if cell not in cells: 

128 msg = "Seed Tray Cell must be one of **A1** through **D4**" 

129 raise ValueError(msg) 

130 

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) 

139 

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 } 

145 

146 offset = { 

147 "x": cell_spacing * cells[cell]["x"] * flip, 

148 "y": cell_spacing * cells[cell]["y"] * flip 

149 } 

150 

151 cell_xyz = { 

152 "x": a1["x"] + offset["x"], 

153 "y": a1["y"] + offset["y"], 

154 "z": a1["z"], 

155 } 

156 

157 self.state.print_status( 

158 description=f"Cell {tray_cell} is at {cell_xyz}.", 

159 update_only=True) 

160 return cell_xyz 

161 

162 def detect_weeds(self): 

163 """Scans the garden to detect weeds.""" 

164 self.state.print_status(description="Detecting weeds...") 

165 

166 detect_weeds_message = { 

167 "kind": "execute_script", 

168 "args": { 

169 "label": "plant-detection" 

170 } 

171 } 

172 

173 self.broker.publish(detect_weeds_message) 

174 

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

178 

179 lua_message = { 

180 "kind": "lua", 

181 "args": { 

182 "lua": lua_code.strip() 

183 } 

184 } 

185 

186 self.broker.publish(lua_message) 

187 

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

196 

197 self.state.print_status(description="Executing if statement.") 

198 

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 } 

212 

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 } 

223 

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 } 

241 

242 self.broker.publish(if_statement_message) 

243 

244 def assertion(self, lua_code, assertion_type, recovery_sequence_name=None): 

245 """Evaluates an expression.""" 

246 self.state.print_status(description="Executing assertion.") 

247 

248 validate_assertion_type(assertion_type) 

249 

250 assertion_message = { 

251 "kind": "assertion", 

252 "args": { 

253 "assertion_type": assertion_type, 

254 "lua": lua_code, 

255 "_then": {"kind": "nothing", "args": {}}, 

256 } 

257 } 

258 

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 } 

271 

272 self.broker.publish(assertion_message)