🧪 새 튜토리얼이 계속 업데이트 중 — 로봇 팔부터 센서까지
본문으로 건너뛰기

AmazingHand 웹 제어 ​

브라우저에서 AmazingHand 로봇 손을 제어합니다. 왼손 / 오른손 두 가지 모드와 두 가지 조작 방식이 있습니다:

  • 수동 제어 — 서보 슬라이더 8개 + 프리셋 포즈 19개 + 한 번에 중간 위치 복귀
  • 카메라 텔레오퍼레이션 — 브라우저에서 로컬로 MediaPipe가 손 동작을 인식해 실시간으로 손가락에 매핑합니다(영상은 컴퓨터 밖으로 나가지 않음)
  • 중국어/영어 전환 — 오른쪽 상단에서 전환하며, 선택은 브라우저에 저장되어 다음에 열어도 이전 언어가 유지됩니다

이 페이지는 공용 웹 제어 설명입니다. SCS0009 전용 매핑/변환은 SCS0009 웹 제어를, PWM 전용 배선/플래싱/캘리브레이션은 PWM 웹 제어를 참고하세요.

빠른 시작 ​

터미널을 열고 Amazinghand-web 디렉터리로 이동하세요:

bash
pip install -r requirements.txt

python server.py --no-connect   # 권장: 서버만 먼저 띄우고 포트는 페이지에서 선택 (로봇 손은 움직이지 않음)

# python server.py                # 시리얼 포트를 자동 감지하고 연결
# python server.py --port COM9    # 특정 포트 지정
# python server.py --hand left    # 왼손만 활성화
# python server.py --driver pwm   # ESP32-S3 PWM 서보 버전 사용
# python server.py --dry-run      # 시리얼 포트를 전혀 열지 않고 UI만 검증

브라우저에서 http://localhost:8000 을 엽니다.

정적 프런트엔드를 직접 사용할 수도 있습니다(Web Serial, Python 서버 불필요):

페이지에서 시리얼 포트 선택 ​

페이지 상단에 툴바가 한 줄 있습니다. 먼저 드라이버를 고르고, 그다음 시리얼 포트를 고르세요:

드라이버  [SCS0009 버스 ▾]  시리얼 포트  [COM3 ★ USB-Enhanced-SERIAL CH343 ▾]  [새로 고침]  [연결]  [연결 해제]  已连接 COM3
  • 드라이버 드롭다운: SCS0009 버스(공식 버스 서보, 기본값) 또는 PWM(ESP32-S3)(PWM 웹 제어 참고)
  • 드롭다운에는 이 컴퓨터의 모든 시리얼 포트가 나열되며, 흔한 USB-시리얼 칩(CH34x / CP210x / FTDI 등)은 ★ 표시가 붙고 맨 앞에 정렬됩니다
  • 새로 고침을 누르면 다시 스캔합니다(보드를 꽂았다 뺀 뒤 사용)
  • 연결을 누르면 선택한 시리얼 포트를 엽니다; 연결되면 자동으로 중간 위치로 복귀합니다
  • 연결 해제는 먼저 토크를 해제한 뒤 연결을 끊으므로 서보가 있던 자리에 그대로 고정되지 않습니다(PWM 버전에는 토크 명령이 없으니 아래 설명 참고)
  • 시리얼 포트를 바꾸려면 새 포트를 선택하고 "연결"을 누르면 됩니다; 이전 포트는 자동으로 닫힙니다

정적 배포(GitHub Pages)에는 Python 서버가 없습니다: 브라우저는 개인정보 보호를 위해 시리얼 포트를 조용히 열거할 수 없고, 권한을 부여한 장치만 볼 수 있습니다. 이때 **시리얼 포트 추가…**를 눌러 브라우저 팝업에서 제어 보드를 선택하세요; 위의 "이 컴퓨터의 모든 시리얼 포트 나열" 동작은 server.py 서버 모드에서만 적용됩니다.

웹 상단: 드라이버 선택, 시리얼 포트 툴바와 왼손/오른손 전환

--no-connect는 이 흐름을 위한 옵션입니다: 시작할 때 서버만 띄우고 시리얼 포트를 전혀 건드리지 않으므로, 자동으로 잘못된 포트에 연결되어 손이 함부로 움직이는 일이 없습니다. 시작하자마자 자동 연결을 원하면 이 파라미터를 빼세요.

시리얼 로그는 server.log에 기록됩니다(.bat으로 시작하면 창이 순식간에 지나가므로 이 파일을 보세요).

배선 점검 ​

스캔된 서보 ID를 나열하고 config.json과 손별로 대조합니다.

bash
python tools/scan_servos.py --port COM9

COM9를 실제 사용하는 포트 번호로 바꾸세요.

이 도구는 SCS0009 버스 서보에만 적용됩니다. PWM 버전의 배선 점검은 PWM 웹 제어를 보거나 AmazingHand-PWM/tools/serial_test.py를 사용하세요.


프리셋 포즈(19개) ​

수동 제어 패널: 네 손가락 슬라이더, 프리셋 포즈 19개와 조작 버튼

포즈설명포즈설명
open펼치기wave1손 흔들기
close / stone주먹wave_r / wave_l좌우 스와이프
okOKring_close약지만 쥐기
greeting인사middle_close중지만 쥐기
victory / two / scissors브이 / 둘pointer_close검지만 쥐기
paper보thumb_close엄지만 쥐기
one / three하나 / 셋hifive하이파이브

포즈를 직접 수정하려면 config.json의 poses 섹션을 편집하세요; 키 이름은 공식 YAML의 이름과 대응합니다.

왼손과 오른손에 서로 다른 포즈 값을 사용할 수 있습니다 ​

각 포즈의 positions는 왼손/오른손이 공유합니다. 기계 조립 차이 때문에 특정 손에 다른 값이 필요하면, 같은 항목에 right 또는 left 키를 추가하면 positions를 덮어씁니다:

jsonc
"close": {
  "label": "握拳",
  "positions": [75, 75, 75, 75, 75, 75, 18, 18],   // 양손 공용
  "right":     [ ... 8개 값 ... ]                   // 선택 사항: 오른손 전용
}

두 가지 모드 ​

모드동작
오른손오른손 서보 8개에만 전송
왼손왼손 서보 8개에만 전송(SCS0009는 미러와 −35.16° 오프셋 자동 적용; PWM은 왼손 채널 매핑 사용)

한 번에 한 손만 제어하며 상단 "제어 대상"으로 전환합니다; 전환해도 다른 손은 영향을 받지 않고 현재 포즈를 유지한 채 제자리에 있습니다. SCS0009 양손은 같은 버스에 연결할 수 있고, PWM은 손마다 ESP32-S3 보드 하나가 제어합니다.


카메라 텔레오퍼레이션 ​

브라우저에서 MediaPipe 손 랜드마크를 로컬로 실행합니다(리소스는 Amazinghand-web/web/mediapipe/에 있으며 오프라인에서도 동작하고, 페이지는 Amazinghand-web-io/mediapipe/도 우선 탐색합니다). 21개 랜드마크를 손가락 4개의 포즈 값으로 변환해 현재 선택한 손으로 실시간 전송합니다.

카메라 패널: 시작, 스켈레톤 표시, 3단계 포즈 캡처와 상태 영역

1. 먼저 세 가지 포즈를 캡처합니다 ​

카메라를 시작한 뒤 포즈를 취하고 해당 버튼을 눌러 한 번 캡처합니다(약 1초 평균). 순서는 상관없고, 잘못 캡처했으면 같은 버튼을 다시 누르면 됩니다. 세 개를 모두 캡처해야 상태가 녹색으로 바뀝니다; 캡처가 끝나기 전에는 아무것도 전송하지 않으며 로봇 손은 중간 위치 자세로 움직이지 않습니다.

버튼포즈제공하는 것
① 펴고 벌리기손가락을 곧게 펴고 벌린 상태, 엄지는 바깥쪽굴곡/신전의 "곧게 폄" 끝 + 벌림의 "벌어짐" 끝
② 펴고 모으기손가락을 곧게 펴고 모은 상태, 엄지는 검지에 붙임벌림의 "모음" 끝
③ 주먹 쥐기주먹을 쥐고 엄지는 손바닥 쪽으로 접음굴곡/신전의 "주먹" 끝

2. 두 축은 각각 두 점 실측으로 정의합니다 ​

굴곡/신전과 벌림 모두 실측 엔드포인트 두 개로 직선을 정의합니다:

t    = (측정값 − 엔드포인트A) / (엔드포인트B − 엔드포인트A)        // 0~1로 클램프
목표 = A.목표값 + (B.목표값 − A.목표값) × t

장점은 각 손가락 측정값의 부호가 자동으로 흡수된다는 점입니다 — 따라서 "특정 손가락은 반대로" 같은 스위치가 필요 없습니다(검지와 약지는 중축 양쪽에 있고 엄지는 관절 체인이 또 달라 방향이 원래 반대이지만, 실측 엔드포인트 두 개가 이를 자동으로 맞춰 줍니다).

엔드포인트 목표값은 config.json의 camera 섹션에 있습니다:

축엔드포인트약지중지검지
굴곡/신전곧게 폄 → 주먹−50 → 90−50 → 90−50 → 90
벌림모음 → 벌림−45 → +450 → 0+45 → −45

중지의 벌림 양 끝은 모두 0입니다(중지는 좌우로 벌어지지 않습니다).

3. 엄지는 굴곡/신전만 합니다 ⚠️ ​

엄지의 CMC는 대립 관절이고 굴곡과 좌우 벌림이 강하게 결합되어 있습니다 — 엄지를 검지에 붙인 상태는 이미 벌림 위치에 있으므로 그 순간 측정한 "굴곡 영점"은 오염되어 있습니다. 억지로 두 축으로 나누면 둘 다 부정확해집니다.

그래서 엄지는 굴곡/신전만 하고 벌림은 항상 0입니다(두 서보의 벌림 차동량이 0). 필요한 참조 포즈는 ①(바깥쪽을 가리킴)과 ③(손바닥 쪽을 가리킴) 두 가지이며 추가로 캡처할 필요가 없습니다.

4. 굽힐수록 벌림이 감쇠합니다 ​

최종 벌림은 다음 식으로 조정됩니다:

side = side_measured × (1 − curl)

splayDeg는 MCP→PIP 방향을 손바닥 가로축에 투영한 값인데, 손가락 굴곡은 바로 그 축을 중심으로 회전합니다 — 일정 굽힘을 넘으면 이 투영의 부호가 뒤집혀 "벌어짐"이 갑자기 반대쪽 끝으로 튑니다.

감쇠가 없으면 이 튐이 servo2 = 굴곡/신전 − 벌림을 타고 짝수 ID 서보를 반대 방향으로 끌고 갑니다 (홀수 ID는 굴곡/신전 + 벌림을 받으므로 변화 방향이 반대여서 덜 눈에 띕니다). 증상은 "주먹을 절반쯤 쥘 때 12번과 16번 서보가 갑자기 반전"입니다 — 12번은 왼손 약지의 두 번째 서보, 16번은 왼손 검지의 두 번째 서보로, 정확히 벌림 성분이 0이 아닐 때 차동에 끌려가는 두 서보입니다.

감쇠를 넣으면 꽉 쥘 때 벌림은 항상 0이 되며, 측정값이 얼마나 엉망이든 상관없습니다. 게다가 주먹은 원래 손가락을 모은 상태여야 합니다.

5. 스켈레톤 색상 ​

손가락색상
엄지주황
검지빨강
중지파랑
약지노랑
새끼손가락어두운 회색
손바닥 뼈중립 회색

새끼손가락은 제스처 매핑에 참여하지 않지만, 밑동 MCP(17번 랜드마크)가 오른손 손바닥 좌표계를 정의하므로 삭제할 수 없습니다.

튜닝 치트시트 ​

바꾸고 싶은 것바꿀 곳
각 축 목표값config.json의 camera 섹션
좌우 손의 벌림 방향이 반대camera.side_sign에서 해당 손을 -1로(1 = 그대로)
부드러움tracking.js의 SMOOTH(기본 0.35; 작을수록 안정적이지만 반응이 느림)
손가락 굽힘 판정 속도세 가지 포즈를 다시 캡처

side_sign은 "제어 대상"에서 선택한 손에 적용되며, 수정 후 페이지를 새로 고침하면 되고 서버를 재시작할 필요는 없습니다:

jsonc
"side_sign": { "right": 1, "left": -1 }   // 왼손의 벌림 축 전체를 반전

굴곡/신전(flexion)은 side_sign의 영향을 받지 않습니다.


프로젝트 구조 ​

Amazinghand-web-io/          프런트엔드(정적, 단독 배포 가능)
├── index.html
├── app.js                   UI, 드라이버 전환, 포즈와 카메라 UI
├── io.js                    SCS0009 Web Serial 백엔드
├── pwm.js                   PWM Web Serial 백엔드
├── common.js                공유 설정, 포즈 변환과 제한
├── ws.js                    WebSocket 백엔드(Python 서버 모드)
├── tracking.js              MediaPipe 카메라 추적
├── i18n.js                  중국어/영어 문구
├── config.json              드라이버, 서보 매핑, 포즈 라이브러리, 카메라 파라미터
└── tools/pwm_protocol_test.mjs

Amazinghand-web/
├── server.py                HTTP + WebSocket + 시리얼 서버
├── requirements.txt
├── tools/scan_servos.py     SCS0009 버스를 스캔하고 ID 확인
└── web/mediapipe/           MediaPipe 리소스(오프라인, 약 42MB)

프런트엔드/백엔드 프로토콜:

jsonc
// 프런트엔드 → 백엔드   target: "left" | "right"
{ "cmd": "ports" }                                  // 시리얼 포트 다시 스캔
{ "cmd": "driver", "driver": "pwm" }                // scs0009 | pwm
{ "cmd": "connect",    "port": "COM3" }
{ "cmd": "disconnect" }
{ "cmd": "target", "target": "left" }
{ "cmd": "set",    "target": "left", "angles": { "pointer": [0, 0], ... } }
{ "cmd": "pose",   "target": "left", "name": "close" }
{ "cmd": "middle", "target": "left" }
{ "cmd": "torque", "target": "left", "on": true }   // SCS0009에만 해당
{ "cmd": "stop" }

// 백엔드 → 프런트엔드
{ "type": "state", "hands": {...}, "poses": {...}, "positions": {...},
  "axes": {...}, "camera": {...}, "target": "left", "driver": "scs0009",
  "capabilities": { "torque": true, "speed": true }, "pwm": {...},
  "limits": {...}, "port": { "available": [...], "current": "COM3", "connected": true } }
{ "type": "ports" | "connected" | "disconnected", "ports": [...], ... }
{ "type": "error", "msg": "连接 COM_FAKE 失败:系统找不到指定的文件。" }

안전 설계 ​

항목설명
포즈 제한포즈 값 ±120°(config.json의 limits), 19개 포즈 모두 초과하지 않는지 확인함
변화 중복 제거변화 < 0.5°는 전송하지 않아 버스가 노이즈로 가득 차지 않음
쓰기 스로틀SCS0009 최소 간격 20ms; PWM 직접 구동 약 30ms
E-stopSCS0009: 모든 토크 해제; PWM: 0xFF를 보내 제스처 중단, 서보는 전원이 들어온 채 위치 유지
종료 보호SCS0009: Ctrl+C 시 토크 해제; PWM: 시리얼 포트 연결 해제 후 서보 위치 유지
한 손 디버그--hand left/right

자주 묻는 질문 ​

카메라 인식이 부정확 → 조명을 고르게 하고 손바닥을 렌즈에 정면으로 향하게 하세요. tracking.js의 SMOOTH(기본 0.35)가 클수록 부드럽지만 반응이 느립니다.

카메라 제스처가 틀리고 특정 손가락이 따라오지 않음 → 먼저 세 포즈가 모두 캡처되었는지(상태가 녹색인지) 확인하세요; 방향이 반대면 해당 포즈를 다시 캡처하세요; 엄지는 굴곡/신전만 하고 벌림에 참여하지 않습니다.

주먹을 절반쯤 쥘 때 특정 서보가 갑자기 반전 → 벌림이 굽힘에 따라 0으로 감쇠하는지(side *= (1 - curl)), 그리고 벌림 제어량의 부호/크기가 비정상인지 확인하세요.

SCS0009 방향/ID/중간 위치 문제 → SCS0009 웹 제어와 SCS0009 디버깅과 캘리브레이션을 참고하세요.

PWM 연결/보드레이트/캘리브레이션 문제 → PWM 웹 제어를 참고하세요.

더 많은 문제 → FAQ.