Skip to content

Commit 5e8f280

Browse files
authored
Merge pull request #2 from blankey1337/feat/config-management
Feat/config management
2 parents 369e9f4 + 863f67a commit 5e8f280

7 files changed

Lines changed: 89 additions & 76 deletions

File tree

.github/workflows/lint.yml

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Lint
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
paths:
7+
- 'software/**'
8+
pull_request:
9+
branches: [ "main" ]
10+
paths:
11+
- 'software/**'
12+
13+
jobs:
14+
lint:
15+
runs-on: ubuntu-latest
16+
defaults:
17+
run:
18+
working-directory: ./software
19+
20+
steps:
21+
- uses: actions/checkout@v4
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v4
25+
with:
26+
python-version: "3.10"
27+
28+
- name: Install dependencies
29+
run: |
30+
python -m pip install --upgrade pip
31+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
32+
pip install ruff
33+
34+
- name: Run Ruff (Linting)
35+
run: ruff check .
36+

software/examples/alohamini/record_bi.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,15 @@ def main():
2828
parser.add_argument("--task_description", type=str, default="My task description4", help="Task description")
2929
parser.add_argument("--remote_ip", type=str, default="127.0.0.1", help="Robot host IP")
3030
parser.add_argument("--robot_id", type=str, default="lekiwi_host", help="Robot ID")
31+
parser.add_argument("--left_arm_port", type=str, default="/dev/am_arm_leader_left", help="Left leader arm port")
32+
parser.add_argument("--right_arm_port", type=str, default="/dev/am_arm_leader_right", help="Right leader arm port")
3133
args = parser.parse_args()
3234

3335
# === Robot and teleop config ===
3436
robot_config = LeKiwiClientConfig(remote_ip=args.remote_ip, id=args.robot_id)
3537
leader_arm_config = BiSO100LeaderConfig(
36-
left_arm_port="/dev/am_arm_leader_left",
37-
right_arm_port="/dev/am_arm_leader_right",
38+
left_arm_port=args.left_arm_port,
39+
right_arm_port=args.right_arm_port,
3840
id="so101_leader_bi3",
3941
)
4042
keyboard_config = KeyboardTeleopConfig()

software/examples/alohamini/teleoperate_bi.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
parser.add_argument("--use_dummy", action="store_true", help="Do not connect robot, only print actions")
1515
parser.add_argument("--fps", type=int, default=30, help="Main loop frequency (frames per second)")
1616
parser.add_argument("--remote_ip", type=str, default="127.0.0.1", help="LeKiwi host IP address")
17+
parser.add_argument("--left_arm_port", type=str, default="/dev/am_arm_leader_left", help="Left leader arm port")
18+
parser.add_argument("--right_arm_port", type=str, default="/dev/am_arm_leader_right", help="Right leader arm port")
1719

1820
args = parser.parse_args()
1921

@@ -27,8 +29,8 @@
2729
# Create configs
2830
robot_config = LeKiwiClientConfig(remote_ip=args.remote_ip, id="my_alohamini")
2931
bi_cfg = BiSO100LeaderConfig(
30-
left_arm_port="/dev/am_arm_leader_left",
31-
right_arm_port="/dev/am_arm_leader_right",
32+
left_arm_port=args.left_arm_port,
33+
right_arm_port=args.right_arm_port,
3234
id="so101_leader_bi3",
3335
)
3436
leader = BiSO100Leader(bi_cfg)

software/examples/alohamini/teleoperate_bi_voice.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
parser.add_argument("--use_dummy", action="store_true", help="Do not connect robot, only print actions")
1919
parser.add_argument("--fps", type=int, default=30, help="Main loop frequency (frames per second)")
2020
parser.add_argument("--remote_ip", type=str, default="127.0.0.1", help="Alohamini host IP address")
21+
parser.add_argument("--left_arm_port", type=str, default="/dev/am_arm_leader_left", help="Left leader arm port")
22+
parser.add_argument("--right_arm_port", type=str, default="/dev/am_arm_leader_right", help="Right leader arm port")
2123
args = parser.parse_args()
2224

2325
USE_DUMMY = args.use_dummy
@@ -29,8 +31,8 @@
2931
# Create configs
3032
robot_config = LeKiwiClientConfig(remote_ip=args.remote_ip, id="my_alohamini")
3133
bi_cfg = BiSO100LeaderConfig(
32-
left_arm_port="/dev/am_arm_leader_left",
33-
right_arm_port="/dev/am_arm_leader_right",
34+
left_arm_port=args.left_arm_port,
35+
right_arm_port=args.right_arm_port,
3436
id="so101_leader_bi3",
3537
)
3638

software/examples/debug/motors.py

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
OperatingMode,
1212
)
1313

14-
DEFAULT_PORT = "/dev/ttyACM0"
14+
DEFAULT_PORT = os.getenv("FEETECH_PORT", "/dev/ttyACM0")
1515
HALF_TURN_DEGREE = 180
1616

1717
GENERAL_ACTIONS = {"sleep", "print"}
@@ -24,8 +24,8 @@
2424

2525
def probe_scan_ids(port: str) -> dict[int, str]:
2626
"""
27-
探针扫描 ID 范围,返回 {id: model_name}(只含在线电机)。
28-
仅做 ping,不对电机写寄存器;断开时不关力矩。
27+
Probe ID range and return {id: model_name} (only online motors).
28+
Only ping, do not write to registers; do not disable torque on disconnect.
2929
"""
3030

3131
probe_bus = build_bus(port, {})
@@ -58,7 +58,7 @@ def probe_scan_ids(port: str) -> dict[int, str]:
5858

5959
def build_motors_from_scan(port: str):
6060
"""
61-
基于 probe_scan_ids 结果,构建 motors 字典(名称统一为 motor_<id>)。
61+
Build motors dict based on probe_scan_ids results (names unified as motor_<id>).
6262
"""
6363
from lerobot.motors.motors_bus import Motor, MotorNormMode
6464
found = probe_scan_ids(port)
@@ -125,13 +125,13 @@ def _motor_angle_from_position(position):
125125

126126
def get_motors_states(port):
127127
"""
128-
动态表格显示电机状态;总是先扫描 [scan_start, scan_end],只显示在线电机。
129-
- 传了 motors 也不会直接用,而是按 ID 范围扫描后重建 motors(保证不读离线 ID)
128+
Display motor states in a dynamic table; always scan [scan_start, scan_end] first, showing only online motors.
129+
- Even if motors are passed, they are not used directly; motors are rebuilt after scanning the ID range (ensuring no offline IDs are read)
130130
"""
131131
import time, sys, shutil
132132
from lerobot.motors.motors_bus import Motor, MotorNormMode
133133

134-
# ---------- ANSI 工具 ----------
134+
# ---------- ANSI Utils ----------
135135
CSI = "\x1b["
136136
def _hide_cursor(): sys.stdout.write(f"{CSI}?25l"); sys.stdout.flush()
137137
def _show_cursor(): sys.stdout.write(f"{CSI}?25h"); sys.stdout.flush()
@@ -152,17 +152,17 @@ def F(v, w, a=">"):
152152
f"{F(st.get('Temperature'),4)} | {F(st.get('Port','-'),16,'<')}")
153153
return row[:maxw] if len(row) > maxw else row
154154

155-
# ---------- 第一步:用探针扫描在线电机 ----------
155+
# ---------- Step 1: Scan for online motors using probe ----------
156156

157157
motors = build_motors_from_scan(port)
158158
if not motors:
159159
print(f"No motors found in ID range [{SCAN_START}, {SCAN_END}] on {port}.")
160160
return
161161

162162

163-
# ---------- 第二步:用“只包含在线电机”的字典进入动态显示 ----------
163+
# ---------- Step 2: Enter dynamic display using dict containing only online motors ----------
164164
bus = build_bus(port, motors)
165-
if not _connect_bus(bus): # 你的辅助函数里保持 handshake=False 更稳
165+
if not _connect_bus(bus): # Keep handshake=False in helper function for stability
166166
return
167167

168168
try:
@@ -213,7 +213,7 @@ def F(v, w, a=">"):
213213

214214

215215
maxw = _term_width()
216-
sep = "-" * min(maxw, 140) # 120 放宽到 140
216+
sep = "-" * min(maxw, 140) # Widened from 120 to 140
217217
header = (f"{'NAME':<15} | {'ID':>3} | {'POS':>6} | {'OFF':>6} | {'ANG':>6} | "
218218
f"{'LOAD':>6} | {'ACC':>6} | {'VOLT':>4} | {'CURR(MA)':>8} | {'TEMP':>4} | PORT")
219219
header = header[:maxw] if len(header) > maxw else header
@@ -235,7 +235,7 @@ def F(v, w, a=">"):
235235
except KeyboardInterrupt:
236236
pass
237237
finally:
238-
# 关键:不要在断开时去“对所有电机”关力矩,避免离线 ID 报错
238+
# Key: Do not disable torque for "all motors" on disconnect to avoid errors from offline IDs.
239239
try:
240240
bus.disconnect(disable_torque=False)
241241
finally:
@@ -248,9 +248,9 @@ def configure_motor_id(port: str, current_id: int, new_id: int):
248248
current_id = int(current_id)
249249
new_id = int(new_id)
250250
if current_id == new_id:
251-
raise SystemExit("current_id == new_id,没有必要改。")
251+
raise SystemExit("current_id == new_id, no change needed.")
252252

253-
# 1) 先用“裸总线”去 ping 校验(不带 motors 映射,避免库做多余事)
253+
# 1) Ping check using "bare bus" (without motors mapping to avoid extra library actions)
254254
probe_bus = FeetechMotorsBus(port=port, motors={})
255255
try:
256256
probe_bus.connect(handshake=False)
@@ -265,11 +265,11 @@ def configure_motor_id(port: str, current_id: int, new_id: int):
265265
pass
266266

267267
if not ok_cur:
268-
raise SystemExit(f"[ABORT] 未发现 ID={current_id} 在线,放弃改 ID。")
268+
raise SystemExit(f"[ABORT] ID={current_id} not online, aborting ID change.")
269269
if ok_new:
270-
raise SystemExit(f"[ABORT] 目标 ID={new_id} 已被占用,放弃改 ID。")
270+
raise SystemExit(f"[ABORT] Target ID={new_id} is occupied, aborting ID change.")
271271

272-
# 2) 用“只包含 current_id 的单电机字典”建立总线(名字无所谓,只要 id current_id
272+
# 2) Build bus with single-motor dict containing current_id (name doesn't matter, as long as id is current_id)
273273
tmp_name = f"motor_{current_id}"
274274
one_motor = {
275275
tmp_name: Motor(id=current_id, model=DEFAULT_FEETECH_MODEL, norm_mode=MotorNormMode.RANGE_0_100)
@@ -280,7 +280,7 @@ def configure_motor_id(port: str, current_id: int, new_id: int):
280280
bus.connect(handshake=False)
281281
print(f"Connected on port {bus.port} (current_id={current_id})")
282282

283-
# 保险:解锁 & 力矩处理(按你电机需要,可调整/删掉)
283+
# Safety: Unlock & Torque handling (adjust/remove as needed)
284284
try:
285285
bus.write("Lock", tmp_name, 0, normalize=False)
286286
except Exception:
@@ -290,12 +290,12 @@ def configure_motor_id(port: str, current_id: int, new_id: int):
290290
except Exception:
291291
pass
292292

293-
# 3) 直接对“当前 id 的那颗”写寄存器:ID = new_id
293+
# 3) Directly write register for "current id": ID = new_id
294294
bus.write("ID", tmp_name, new_id, normalize=False)
295295
time.sleep(0.2)
296296

297-
# 4) 改后校验:当前 id 应该失联,new_id 应该在线
298-
# 这里用一个“空 bus”去 ping,避免受 motors 映射影响
297+
# 4) Post-change verify: current id should be offline, new_id should be online
298+
# Use an "empty bus" to ping here to avoid being affected by motors mapping
299299
probe_bus2 = FeetechMotorsBus(port=port, motors={})
300300
try:
301301
probe_bus2.connect(handshake=False)
@@ -309,7 +309,7 @@ def configure_motor_id(port: str, current_id: int, new_id: int):
309309

310310
if ok_cur2 or not ok_new2:
311311
raise SystemExit(
312-
f"[VERIFY FAIL] 改后验证失败:ID={current_id} 仍在线={ok_cur2}, ID={new_id} 在线={ok_new2}"
312+
f"[VERIFY FAIL] Post-change verify failed: ID={current_id} still online={ok_cur2}, ID={new_id} online={ok_new2}"
313313
)
314314

315315
print(f"[OK] Changed ID {current_id} -> {new_id} (model={DEFAULT_FEETECH_MODEL})")
@@ -386,16 +386,16 @@ def move_motor_to_position(
386386
position: int,
387387
):
388388
"""
389-
直接用数值ID控制电机到指定 position(原始ticks)。
390-
- 不需要 motors{},不需要电机名。
391-
- 默认确保处于“位置模式”,必要时切换。
389+
Control motor directly by numeric ID to specified position (raw ticks).
390+
- No motors{} or motor names needed.
391+
- Default to ensuring "Position Mode", switching if necessary.
392392
"""
393393
tmp_name = f"motor_{int(motor_id)}"
394394
one_motor = {
395395
tmp_name: Motor(id=int(motor_id), model=DEFAULT_FEETECH_MODEL, norm_mode=MotorNormMode.RANGE_0_100)
396396
}
397397

398-
# 可选:简单范围夹取(12bit 0~4095;按你家实际寄存器范围调整)
398+
# Optional: Simple range clamping (12bit 0~4095; adjust according to your register range)
399399
try:
400400
position = int(position)
401401
position = max(0, min(4095, position))
@@ -407,7 +407,7 @@ def move_motor_to_position(
407407
bus.connect(handshake=False)
408408
print(f"Connected on port {bus.port} (ID={motor_id})")
409409

410-
# 若不确定电机当前模式,保险起见切到“位置模式”
410+
# Switch to "Position Mode" for safety if current mode is uncertain
411411
bus.disable_torque(tmp_name)
412412
bus.write("Operating_Mode", tmp_name, OperatingMode.POSITION.value, normalize=False)
413413
bus.enable_torque(tmp_name)

software/src/lerobot/robots/alohamini/config_lekiwi.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15+
import os
1516
from dataclasses import dataclass, field
1617

1718
from lerobot.cameras.configs import CameraConfig, Cv2Rotation
@@ -43,8 +44,8 @@ def lekiwi_cameras_config() -> dict[str, CameraConfig]:
4344
@RobotConfig.register_subclass("lekiwi")
4445
@dataclass
4546
class LeKiwiConfig(RobotConfig):
46-
left_port: str = "/dev/am_arm_follower_left" # port to connect to the bus
47-
right_port: str = "/dev/am_arm_follower_right" # port to connect to the bus
47+
left_port: str = os.getenv("LEKIWI_LEFT_PORT", "/dev/am_arm_follower_left") # port to connect to the bus
48+
right_port: str = os.getenv("LEKIWI_RIGHT_PORT", "/dev/am_arm_follower_right") # port to connect to the bus
4849
disable_torque_on_disconnect: bool = True
4950

5051
# `max_relative_target` limits the magnitude of the relative positional target vector for safety purposes.

0 commit comments

Comments
 (0)