Skip to content
This repository was archived by the owner on Jun 3, 2025. It is now read-only.

Commit 0831167

Browse files
Merge pull request #32 from alberto-abarzua/31-add-tool-settings-support-and-improve-flashing
31 add tool settings support and improve flashing
2 parents 6bd21b9 + ecfc109 commit 0831167

File tree

28 files changed

+757
-464
lines changed

28 files changed

+757
-464
lines changed

.env.template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export ESP_WIFI_SSID=ssid
33
export ESP_WIFI_PASSWORD=password
44
export ESP_CONTROLLER_SERVER_HOST=backend
55
export ESP_CONTROLLER_SERVER_PORT=8500
6+
export ESP_FLASH_PORT=
67

78
# CONTROLLER DEFAULT SETTINGS
89
CONTROLLER_WEBSOCKET_PORT=8600

backend/src/config/main_arm.toml

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,17 @@ title = "Robot arm configuration"
3535
a6x= 169.0
3636
a6z= 0
3737
a6y= 0
38+
[tool]
39+
name = "tool"
40+
max_angle_rad = 0.0
41+
min_angle_rad = 0.0
42+
[tool.driver]
43+
type = "servo"
44+
pin = 26
45+
3846

3947
[[joints]]
40-
name = "joinT_1"
48+
name = "joint_1"
4149
homing_direction = -1 # 1 or -1
4250
conversion_rate_axis_joint = 9.3 # Gear ratio
4351
homing_offset_rad = 0 # Offset from home position
@@ -51,7 +59,7 @@ title = "Robot arm configuration"
5159
# type = "servo"
5260
# pin = 20
5361
[joints.endstop]
54-
type = "hall"
62+
type = "none"
5563
pin = 26
5664
# [joints.endstop]
5765
# type = "dummy"
@@ -82,7 +90,7 @@ title = "Robot arm configuration"
8290
dir_pin = 16
8391
step_pin = 15
8492
[joints.endstop]
85-
type = "hall"
93+
type = "none"
8694
pin = 33
8795

8896
[[joints]]
@@ -108,7 +116,7 @@ title = "Robot arm configuration"
108116
dir_pin = 21
109117
step_pin = 19
110118
[joints.endstop]
111-
type = "hall"
119+
type = "none"
112120
pin = 35
113121

114122
[[joints]]
@@ -121,6 +129,6 @@ title = "Robot arm configuration"
121129
dir_pin = 23
122130
step_pin = 22
123131
[joints.endstop]
124-
type = "hall"
132+
type = "none"
125133
pin = 34
126134

backend/src/main.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1+
from contextlib import asynccontextmanager
2+
13
from fastapi import FastAPI
24
from fastapi.middleware.cors import CORSMiddleware
35

46
from routers.move import router as move_router
57
from routers.settings import router as settings_router
68
from utils.general import start_controller, stop_controller
79

8-
app = FastAPI()
10+
11+
@asynccontextmanager
12+
async def controller_lifespan(_: FastAPI): # type: ignore # noqa: ANN201
13+
start_controller()
14+
15+
yield
16+
17+
stop_controller()
18+
19+
20+
app = FastAPI(lifespan=controller_lifespan)
921

1022
app.add_middleware(
1123
CORSMiddleware,
@@ -18,13 +30,3 @@
1830

1931
app.include_router(move_router, prefix="/move")
2032
app.include_router(settings_router, prefix="/settings")
21-
22-
23-
@app.on_event("startup")
24-
async def startup_event() -> None:
25-
start_controller()
26-
27-
28-
@app.on_event("shutdown")
29-
async def shutdown_event() -> None:
30-
stop_controller()

backend/src/routers/move.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pydantic import BaseModel
66
from robot_arm_controller.control.arm_kinematics import ArmPose
77
from robot_arm_controller.controller import ArmController
8+
from robot_arm_controller.utils.algebra import degree2rad
89

910
from utils.general import controller_dependency
1011

@@ -23,17 +24,20 @@ class Move(BaseModel):
2324
pitch: float
2425
yaw: float
2526
wait: Optional[bool] = False
27+
degrees: Optional[bool] = True
2628

2729

2830
class Tool(BaseModel):
2931
toolValue: float
3032
wait: Optional[bool] = False
33+
degrees: Optional[bool] = True
3134

3235

3336
class MoveJoint(BaseModel):
3437
joint_idx: int
3538
joint_value: float
3639
wait: Optional[bool] = False
40+
degrees: Optional[bool] = True
3741

3842

3943
class HomeJoint(BaseModel):
@@ -44,6 +48,7 @@ class HomeJoint(BaseModel):
4448
class MoveJoints(BaseModel):
4549
joint_values: list
4650
wait: Optional[bool] = False
51+
degrees: Optional[bool] = True
4752

4853

4954
# --------
@@ -76,8 +81,9 @@ def home_joint(
7681
def move(move: Move, controller: ArmController = controller_dependency) -> JSONResponse:
7782
move_dict = move.model_dump()
7883
wait = move_dict.pop("wait")
84+
degrees = move_dict.pop("degrees")
7985

80-
pose = ArmPose(**move_dict)
86+
pose = ArmPose(**move_dict, degree=degrees)
8187

8288
move_is_possible = controller.move_to(pose)
8389

@@ -95,8 +101,9 @@ def move_relative(
95101
) -> JSONResponse:
96102
move_dict = move.model_dump()
97103
wait = move_dict.pop("wait")
104+
degrees = move_dict.pop("degrees")
98105

99-
pose = ArmPose(**move_dict)
106+
pose = ArmPose(**move_dict, degree=degrees)
100107

101108
move_is_possible = controller.move_to_relative(pose)
102109

@@ -114,7 +121,9 @@ def valid_pose(
114121
) -> JSONResponse:
115122
move_dict = move.model_dump()
116123
move_dict.pop("wait")
117-
pose = ArmPose(**move_dict)
124+
degrees = move_dict.pop("degrees")
125+
pose = ArmPose(**move_dict, degree=degrees)
126+
118127
move_is_possible = controller.valid_pose(pose)
119128
if move_is_possible:
120129
return JSONResponse(content={"message": "Pose is valid"}, status_code=200)
@@ -133,9 +142,14 @@ def move_joint(
133142
) -> Dict[Any, Any]:
134143
move_dict = move.model_dump()
135144
wait = move_dict.pop("wait")
145+
degrees = move_dict.pop("degrees")
136146

137147
joint_idx = move_dict.pop("joint_idx")
138148
joint_value = move_dict.pop("joint_value")
149+
150+
if degrees:
151+
joint_value = degree2rad(joint_value)
152+
139153
controller.move_joint_to(joint_idx, joint_value)
140154
if wait:
141155
controller.wait_done_moving()
@@ -148,9 +162,13 @@ def move_joint_to_relative(
148162
) -> Dict[Any, Any]:
149163
move_dict = move.model_dump()
150164
wait = move_dict.pop("wait")
165+
degrees = move_dict.pop("degrees")
151166

152167
joint_idx = move_dict.pop("joint_idx")
153168
joint_value = move_dict.pop("joint_value")
169+
if degrees:
170+
joint_value = degree2rad(joint_value)
171+
154172
controller.move_joint_to_relative(joint_idx, joint_value)
155173
if wait:
156174
controller.wait_done_moving()
@@ -163,8 +181,12 @@ def move_joints_to_relative(
163181
) -> Dict[Any, Any]:
164182
move_dict = move.model_dump()
165183
wait = move_dict.pop("wait")
184+
degrees = move_dict.pop("degrees")
166185

167186
joint_values = move_dict.pop("joint_values")
187+
if degrees:
188+
joint_values = [degree2rad(joint_value) for joint_value in joint_values]
189+
168190
controller.move_joints_to_relative(joint_values)
169191
if wait:
170192
controller.wait_done_moving()
@@ -176,21 +198,42 @@ def move_joints_to_relative(
176198
# --------
177199

178200

179-
@router.post("/tool/move/")
201+
@router.post("/tool/")
180202
def tool_post(
181203
tool: Tool, controller: ArmController = controller_dependency
182204
) -> Dict[Any, Any]:
183205
tool_dict = tool.model_dump()
184206
tool_value = tool_dict["toolValue"]
185207
wait = tool_dict["wait"]
208+
degrees = tool_dict["degrees"]
209+
if degrees:
210+
tool_value = degree2rad(tool_value)
211+
186212
controller.set_tool_value(tool_value)
187213
if wait:
188214
controller.wait_done_moving()
189215
return {"message": "Moved"}
190216

191217

218+
@router.post("/tool/relative/")
219+
def tool_relative(
220+
tool: Tool, controller: ArmController = controller_dependency
221+
) -> Dict[Any, Any]:
222+
tool_dict = tool.model_dump()
223+
tool_value = tool_dict["toolValue"]
224+
wait = tool_dict["wait"]
225+
degrees = tool_dict["degrees"]
226+
if degrees:
227+
tool_value = degree2rad(tool_value)
228+
229+
print("\n\n\ntool_value", tool_value)
230+
controller.set_tool_value_relative(tool_value)
231+
if wait:
232+
controller.wait_done_moving()
233+
return {"message": "Moved"}
234+
235+
192236
@router.get("/tool/current/")
193237
def tool_get(controller: ArmController = controller_dependency) -> Dict[Any, Any]:
194238
tool_value = controller.tool_value
195-
196239
return {"toolValue": tool_value}

backend/src/routers/settings.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
from pathlib import Path
33
from typing import Any, Dict, Optional
44

5-
from fastapi import APIRouter
5+
from fastapi import APIRouter, Query
66
from pydantic import BaseModel
77
from robot_arm_controller.controller import ArmController, ControllerStatus, Settings
8+
from robot_arm_controller.utils.algebra import rad2degree
89

910
from utils.general import controller_dependency
1011

@@ -41,13 +42,27 @@ def get_items(
4142

4243

4344
@router.get("/status/")
44-
def status(controller: ArmController = controller_dependency) -> Dict[Any, Any]:
45+
def status(
46+
controller: ArmController = controller_dependency,
47+
degrees: bool = Query(True, description="Return angles in degrees"),
48+
) -> Dict[Any, Any]:
4549
pose = controller.current_pose
46-
status_dict: Dict[str, Any] = deepcopy(pose.as_dict)
50+
status_dict: Dict[str, Any] = deepcopy(pose.get_dict(degrees=degrees))
51+
4752
status_dict["toolValue"] = controller.tool_value
53+
if degrees:
54+
status_dict["toolValue"] = rad2degree(status_dict["toolValue"])
55+
4856
status_dict["isHomed"] = controller.is_homed
4957
status_dict["moveQueueSize"] = controller.move_queue_size
50-
status_dict["currentAngles"] = controller.current_angles
58+
controller_angles = controller.current_angles
59+
if degrees:
60+
currentAngles = []
61+
for angle in controller_angles:
62+
currentAngles.append(rad2degree(angle))
63+
status_dict["currentAngles"] = currentAngles
64+
else:
65+
status_dict["currentAngles"] = controller_angles
5166

5267
status_dict["connected"] = controller.status == ControllerStatus.RUNNING
5368
status_dict["status"] = controller.status

controller/src/robot_arm_controller/control/arm_kinematics.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,26 @@ def as_dict(self) -> Dict[str, float]:
149149
"yaw": self.yaw,
150150
}
151151

152+
def get_dict(self, degrees: bool = False) -> Dict[str, float]:
153+
if not degrees:
154+
return {
155+
"x": self.x,
156+
"y": self.y,
157+
"z": self.z,
158+
"roll": self.roll,
159+
"pitch": self.pitch,
160+
"yaw": self.yaw,
161+
}
162+
else:
163+
return {
164+
"x": self.x,
165+
"y": self.y,
166+
"z": self.z,
167+
"roll": np.rad2deg(self.roll),
168+
"pitch": np.rad2deg(self.pitch),
169+
"yaw": np.rad2deg(self.yaw),
170+
}
171+
152172
def __add__(self, other: "ArmPose") -> "ArmPose":
153173
if not isinstance(other, ArmPose):
154174
raise ValueError("Can only add two ArmPose instances")

controller/src/robot_arm_controller/controller.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,14 @@ def configure_from_file(self, file: Path, reload: bool = True) -> None:
196196
for key, value in arm_params.items():
197197
self.arm_params.__setattr__(key, value)
198198

199+
# Tool settings
200+
201+
tool = contents["tool"]
202+
tool_driver = tool["driver"]
203+
if tool_driver["type"] == "servo":
204+
pin = int(tool_driver["pin"])
205+
self.set_tool_driver_servo(pin)
206+
199207
default_speed = joint_configuration["speed_rad_per_s"]
200208
default_homing_direction = joint_configuration["homing_direction"]
201209
default_steps_per_rev_motor_axis = joint_configuration["steps_per_rev_motor_axis"]
@@ -263,8 +271,10 @@ def configure_from_file(self, file: Path, reload: bool = True) -> None:
263271
self.set_setting_joint(Settings.CONVERSION_RATE_AXIS_JOINTS, conversion_rate_axis_joint, i)
264272
self.set_setting_joint(Settings.HOMING_OFFSET_RADS, homing_offset_rads, i)
265273
self.set_setting_joint(Settings.DIR_INVERTED, dir_inverted, i)
274+
266275
self.arm_params.joints[i].set_bounds(min_angle_rad, max_angle_rad)
267276

277+
self.print_state()
268278
if reload:
269279
self.file_reload_thread = threading.Thread(target=self.check_and_reload_config, args=(file,))
270280
self.file_reload_thread.start()
@@ -416,6 +426,10 @@ def set_tool_value(self, angle: float) -> None:
416426
if self.print_status:
417427
console.log(f"Setting tool value to: {angle}", style="set_tool")
418428

429+
def set_tool_value_relative(self, angle: float) -> None:
430+
target_angle = self.tool_value + angle
431+
self.set_tool_value(target_angle)
432+
419433
def wait_until_angles_at_target(self, target_angles: List[float], epsilon: float = 0.01) -> None:
420434
while not allclose(self.current_angles, target_angles, atol=epsilon) and not self.stop_event.is_set():
421435
time.sleep(0.2)
@@ -446,6 +460,12 @@ def stop_movement(self) -> None:
446460
if self.print_status:
447461
console.log("Stopping arm", style="info")
448462

463+
def print_state(self) -> None:
464+
message = Message(MessageOp.STATUS, 7)
465+
self.controller_server.send_message(message, mutex=True)
466+
if self.print_status:
467+
console.log("Printing arm state", style="info")
468+
449469
"""
450470
----------------------------------------
451471
API Methods -- CONFIG
@@ -515,6 +535,12 @@ def set_joint_endstop_none(self, joint_idx: int) -> None:
515535
if self.print_status:
516536
console.log(f"Setting endstop none for joint {joint_idx}", style="set_settings")
517537

538+
def set_tool_driver_servo(self, pin: int) -> None:
539+
message = Message(MessageOp.CONFIG, 35, [float(pin)])
540+
self.controller_server.send_message(message, mutex=True)
541+
if self.print_status:
542+
console.log(f"Setting servo driver for tool on pin {pin}", style="set_settings")
543+
518544

519545
class SingletonArmController:
520546
_instance: Optional[ArmController] = None

0 commit comments

Comments
 (0)