Coverage for farmbot_sidecar_starter_pack/functions/resources.py: 100%

92 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-08-30 13:00 -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 

19def validate_assertion_type(assertion_type): 

20 """Validate assertion type.""" 

21 if assertion_type not in ASSERTION_TYPES: 

22 raise ValueError(f"Invalid assertion_type: {assertion_type} not in {ASSERTION_TYPES}") 

23 

24OPERATORS = ["<", ">", "is", "not", "is_undefined"] 

25IF_STATEMENT_VARIABLE_STRINGS = ["x", "y", "z", *[f"pin{str(i)}" for i in range(70)]] 

26NAMED_PIN_TYPES = ["Peripheral", "Sensor"] 

27 

28def validate_if_statement_args(named_pin_type, variable, operator): 

29 """Validate if statement arguments.""" 

30 if operator not in OPERATORS: 

31 raise ValueError(f"Invalid operator: {operator} not in {OPERATORS}") 

32 if named_pin_type is None and variable not in IF_STATEMENT_VARIABLE_STRINGS: 

33 raise ValueError(f"Invalid variable: {variable} not in {IF_STATEMENT_VARIABLE_STRINGS}") 

34 if named_pin_type is not None and named_pin_type not in NAMED_PIN_TYPES: 

35 raise ValueError(f"Invalid named_pin_type: {named_pin_type} not in {NAMED_PIN_TYPES}") 

36 

37class Resources(): 

38 """Resources class.""" 

39 def __init__(self, state): 

40 self.broker = BrokerConnect(state) 

41 self.info = Information(state) 

42 self.state = state 

43 

44 # TODO: mark_as() 

45 

46 # TODO: sort_points(points, method) 

47 

48 def sequence(self, sequence_name): 

49 """Executes a predefined sequence.""" 

50 self.state.print_status(description="Running {sequence_name} sequence.") 

51 

52 sequence = self.info.get_resource_by_name("sequences", sequence_name, "name") 

53 if sequence is None: 

54 return 

55 

56 sequence_message = { 

57 "kind": "execute", 

58 "args": { 

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

60 } 

61 } 

62 

63 self.broker.publish(sequence_message) 

64 

65 

66 def get_seed_tray_cell(self, tray_name, tray_cell): 

67 """Identifies and returns the location of specified cell in the seed tray.""" 

68 self.state.print_status(description="Identifying seed tray cell location.") 

69 

70 tray_tool = self.info.get_resource_by_name("tools", tray_name, "name") 

71 if tray_tool is None: 

72 return 

73 tray_data = self.info.get_resource_by_name( 

74 "points", tray_tool["id"], "tool_id", {"pointer_type": "ToolSlot"}) 

75 if tray_data is None: 

76 self.state.print_status( 

77 description=f"{tray_name} must be mounted in a slot.", 

78 update_only=True) 

79 return 

80 

81 cell = tray_cell.upper() 

82 

83 seeder_needle_offset = 17.5 

84 cell_spacing = 12.5 

85 

86 cells = { 

87 "A1": {"x": 0, "y": 0}, 

88 "A2": {"x": 0, "y": 1}, 

89 "A3": {"x": 0, "y": 2}, 

90 "A4": {"x": 0, "y": 3}, 

91 

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

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

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

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

96 

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

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

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

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

101 

102 "D1": {"x": -3, "y": 0}, 

103 "D2": {"x": -3, "y": 1}, 

104 "D3": {"x": -3, "y": 2}, 

105 "D4": {"x": -3, "y": 3} 

106 } 

107 

108 if cell not in cells: 

109 raise ValueError("Seed Tray Cell must be one of **A1** through **D4**") 

110 

111 flip = 1 

112 if tray_data["pullout_direction"] == 1: 

113 flip = 1 

114 elif tray_data["pullout_direction"] == 2: 

115 flip = -1 

116 else: 

117 raise ValueError("Seed Tray **SLOT DIRECTION** must be `Positive X` or `Negative X`") 

118 

119 a1 = { 

120 "x": tray_data["x"] - seeder_needle_offset + (1.5 * cell_spacing * flip), 

121 "y": tray_data["y"] - (1.5 * cell_spacing * flip), 

122 "z": tray_data["z"] 

123 } 

124 

125 offset = { 

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

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

128 } 

129 

130 cell_xyz = { 

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

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

133 "z": a1["z"], 

134 } 

135 

136 self.state.print_status( 

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

138 update_only=True) 

139 return cell_xyz 

140 

141 def detect_weeds(self): 

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

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

144 

145 detect_weeds_message = { 

146 "kind": "execute_script", 

147 "args": { 

148 "label": "plant-detection" 

149 } 

150 } 

151 

152 self.broker.publish(detect_weeds_message) 

153 

154 def lua(self, code_snippet): 

155 """Executes custom Lua code snippets to perform complex tasks or automations.""" 

156 self.state.print_status(description="Running Lua code") 

157 

158 lua_message = { 

159 "kind": "lua", 

160 "args": { 

161 "lua": code_snippet.strip() 

162 } 

163 } 

164 

165 self.broker.publish(lua_message) 

166 

167 def if_statement(self, variable, operator, value, then_sequence_name=None, else_sequence_name=None, named_pin_type=None): 

168 """Performs conditional check and executes actions based on the outcome.""" 

169 

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

171 

172 validate_if_statement_args(named_pin_type, variable, operator) 

173 if named_pin_type is not None: 

174 endpoint = named_pin_type.lower() + "s" 

175 resource = self.info.get_resource_by_name(endpoint, variable) 

176 if resource is None: 

177 return 

178 variable = { 

179 "kind": "named_pin", 

180 "args": { 

181 "pin_type": named_pin_type, 

182 "pin_id": resource["id"] 

183 } 

184 } 

185 

186 if_statement_message = { 

187 "kind": "_if", 

188 "args": { 

189 "lhs": variable, 

190 "op": operator, 

191 "rhs": value, 

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

193 "_else": {"kind": "nothing", "args": {}}, 

194 } 

195 } 

196 

197 sequence_names = { 

198 "_then": then_sequence_name, 

199 "_else": else_sequence_name, 

200 } 

201 for key, sequence_name in sequence_names.items(): 

202 if sequence_name is not None: 

203 sequence = self.info.get_resource_by_name("sequences", sequence_name, "name") 

204 if sequence is None: 

205 return 

206 sequence_id = sequence["id"] 

207 if_statement_message["args"][key] = { 

208 "kind": "execute", 

209 "args": {"sequence_id": sequence_id}, 

210 } 

211 

212 self.broker.publish(if_statement_message) 

213 

214 def assertion(self, code, assertion_type, recovery_sequence_name=None): 

215 """Evaluates an expression.""" 

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

217 

218 validate_assertion_type(assertion_type) 

219 

220 assertion_message = { 

221 "kind": "assertion", 

222 "args": { 

223 "assertion_type": assertion_type, 

224 "lua": code, 

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

226 } 

227 } 

228 

229 if recovery_sequence_name is not None: 

230 sequence = self.info.get_resource_by_name("sequences", recovery_sequence_name, "name") 

231 if sequence is None: 

232 return 

233 recovery_sequence_id = sequence["id"] 

234 assertion_message["args"]["_then"] = { 

235 "kind": "execute", 

236 "args": {"sequence_id": recovery_sequence_id}, 

237 } 

238 

239 self.broker.publish(assertion_message)