modification guide - dingdongdengdong/astra_ws GitHub Wiki

ArUco ๋งˆ์ปค ์ขŒํ‘œ ๋ฐ ๋ฌผ๋ฆฌ์  ํŒŒ๋ผ๋ฏธํ„ฐ ์ˆ˜์ • ๊ฐ€์ด๋“œ

1. ArUco ๋งˆ์ปค ์ขŒํ‘œ ์ˆ˜์ •

1.1 ํ˜„์žฌ ๋งˆ์ปค ์ขŒํ‘œ ๋ถ„์„

ํŒŒ์ผ ์œ„์น˜: non_ros_src/astra_teleop/src/astra_teleop/process.py

ํ˜„์žฌ ๋งˆ์ปค ์ขŒํ‘œ (SCARA ๋กœ๋ด‡ํŒ” ๊ธฐ์ค€):

obj_points_map = { # right part
    # top_left, top_right, bottom_right, bottom_left
    0:  [ (-15.00, -15.00,  48.28), (-15.00,  15.00,  48.28), ( 15.00,  15.00,  48.28), ( 15.00, -15.00,  48.28) ],
    
    1:  [ ( 23.54, -15.00,  44.75), ( 23.54,  15.00,  44.75), ( 44.75,  15.00,  23.54), ( 44.75, -15.00,  23.54) ],
    2:  [ (-15.00, -23.54,  44.75), ( 15.00, -23.54,  44.75), ( 15.00, -44.75,  23.54), (-15.00, -44.75,  23.54) ],
    # ... ๋” ๋งŽ์€ ๋งˆ์ปค๋“ค
}

1.2 ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์šฉ ๋งˆ์ปค ์ขŒํ‘œ ์ธก์ • ๋ฐฉ๋ฒ•

1.2.1 ์ธก์ • ๋„๊ตฌ ์ค€๋น„

# ์ธก์ •์šฉ ์Šคํฌ๋ฆฝํŠธ ์˜ˆ์‹œ
import numpy as np
import cv2
from astra_teleop.process import get_solve

def measure_marker_positions():
    """
    ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์—์„œ ArUco ๋งˆ์ปค๋“ค์˜ 3D ์ขŒํ‘œ๋ฅผ ์ธก์ •ํ•˜๋Š” ํ•จ์ˆ˜
    """
    # 1. ์นด๋ฉ”๋ผ ์บ˜๋ฆฌ๋ธŒ๋ ˆ์ด์…˜ (์ด๋ฏธ ์™„๋ฃŒ๋˜์–ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •)
    camera_matrix = np.array([fx, 0, cx], [0, fy, cy], [0, 0, 1](/dingdongdengdong/astra_ws/wiki/fx,-0,-cx],-[0,-fy,-cy],-[0,-0,-1))
    distortion_coefficients = np.array([k1, k2, p1, p2, k3])
    
    # 2. ๊ฐ ๋งˆ์ปค์˜ ์‹ค์ œ 3D ์œ„์น˜ ์ธก์ •
    marker_positions = {}
    
    for marker_id in range(36):  # 0-35๋ฒˆ ๋งˆ์ปค
        # ์‹ค์ œ ๋กœ๋ด‡ํŒ”์—์„œ ๋งˆ์ปค ์œ„์น˜๋ฅผ ์ธก์ •
        # ์˜ˆ: ๋ ˆ์ด์ € ์ธก์ •๊ธฐ, 3D ์Šค์บ๋„ˆ, ๋˜๋Š” ์ˆ˜๋™ ์ธก์ •
        x, y, z = measure_marker_3d_position(marker_id)
        
        # ๋งˆ์ปค์˜ 4๊ฐœ ์ฝ”๋„ˆ ์ขŒํ‘œ ๊ณ„์‚ฐ (30mm x 30mm ๋งˆ์ปค ๊ธฐ์ค€)
        marker_size = 30.0  # mm
        half_size = marker_size / 2
        
        corners_3d = [
            (x - half_size, y - half_size, z),  # top_left
            (x + half_size, y - half_size, z),  # top_right  
            (x + half_size, y + half_size, z),  # bottom_right
            (x - half_size, y + half_size, z),  # bottom_left
        ]
        
        marker_positions[marker_id] = corners_3d
    
    return marker_positions

1.2.2 ์‹ค์ œ ์ธก์ • ์˜ˆ์‹œ

๊ธฐ์กด SCARA ๋กœ๋ด‡ํŒ” ๋งˆ์ปค ๋ฐฐ์น˜:

๋งˆ์ปค 0: (-15.00, -15.00, 48.28) mm  # ์˜ค๋ฅธ์ชฝ ํŒ” ์ƒ๋‹จ
๋งˆ์ปค 1: ( 23.54, -15.00, 44.75) mm  # ์˜ค๋ฅธ์ชฝ ํŒ” ์ธก๋ฉด
๋งˆ์ปค 2: (-15.00, -23.54, 44.75) mm  # ์˜ค๋ฅธ์ชฝ ํŒ” ํ›„๋ฉด
...
๋งˆ์ปค 18: (-15.00, -15.00, 48.28) mm # ์™ผ์ชฝ ํŒ” ์ƒ๋‹จ (๊ธฐ์กด๊ณผ ๋™์ผ)

์ƒˆ๋กœ์šด ์ž์œ 6์ถ• ๋กœ๋ด‡ํŒ” ๋งˆ์ปค ๋ฐฐ์น˜ (์˜ˆ์‹œ):

๋งˆ์ปค 0: (-25.00, -20.00, 60.00) mm  # ๋” ํฐ ๋กœ๋ด‡ํŒ”, ๋” ๋„“์€ ๊ฐ„๊ฒฉ
๋งˆ์ปค 1: ( 35.00, -20.00, 55.00) mm  # ํŒ” ๊ธธ์ด๊ฐ€ ๊ธธ์–ด์ ธ์„œ ๋” ๋ฉ€๋ฆฌ
๋งˆ์ปค 2: (-25.00, -35.00, 55.00) mm  # ์ž‘์—… ๊ณต๊ฐ„์ด ๋„“์–ด์ง
...
๋งˆ์ปค 18: (-25.00, -20.00, 60.00) mm # ์™ผ์ชฝ ํŒ”๋„ ๋™์ผํ•œ ํŒจํ„ด

1.3 ์ˆ˜์ •๋œ ๋งˆ์ปค ์ขŒํ‘œ ์ ์šฉ

1.3.1 process.py ์ˆ˜์ • ์˜ˆ์‹œ

# non_ros_src/astra_teleop/src/astra_teleop/process.py

def get_solve(scale=1):
    # ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์šฉ ๋งˆ์ปค ์ขŒํ‘œ
    obj_points_map = { # right part (์ƒˆ๋กœ์šด ์ž์œ 6์ถ• ๋กœ๋ด‡ํŒ”)
        # top_left, top_right, bottom_right, bottom_left
        0:  [ (-25.00, -20.00,  60.00), (-25.00,  20.00,  60.00), ( 25.00,  20.00,  60.00), ( 25.00, -20.00,  60.00) ],
        
        1:  [ ( 35.00, -20.00,  55.00), ( 35.00,  20.00,  55.00), ( 55.00,  20.00,  35.00), ( 55.00, -20.00,  35.00) ],
        2:  [ (-25.00, -35.00,  55.00), ( 25.00, -35.00,  55.00), ( 25.00, -55.00,  35.00), (-25.00, -55.00,  35.00) ],
        3:  [ (-35.00,  20.00,  55.00), (-35.00, -20.00,  55.00), (-55.00, -20.00,  35.00), (-55.00,  20.00,  35.00) ],
        4:  [ ( 25.00,  35.00,  55.00), (-25.00,  35.00,  55.00), (-25.00,  55.00,  35.00), ( 25.00,  55.00,  35.00) ],
        
        5:  [ ( 60.00, -20.00,  20.00), ( 60.00,  20.00,  20.00), ( 60.00,  20.00, -20.00), ( 60.00, -20.00, -20.00) ],
        6:  [ ( 35.00, -55.00,  20.00), ( 55.00, -35.00,  20.00), ( 55.00, -35.00, -20.00), ( 35.00, -55.00, -20.00) ],
        7:  [ (-25.00, -60.00,  20.00), ( 25.00, -60.00,  20.00), ( 25.00, -60.00, -20.00), (-25.00, -60.00, -20.00) ],
        8:  [ (-55.00, -35.00,  20.00), (-35.00, -55.00,  20.00), (-35.00, -55.00, -20.00), (-55.00, -35.00, -20.00) ],
        9:  [ (-60.00,  20.00,  20.00), (-60.00, -20.00,  20.00), (-60.00, -20.00, -20.00), (-60.00,  20.00, -20.00) ],
        10: [ (-35.00,  55.00,  20.00), (-55.00,  35.00,  20.00), (-55.00,  35.00, -20.00), (-35.00,  55.00, -20.00) ],
        11: [ ( 25.00,  60.00,  20.00), (-25.00,  60.00,  20.00), (-25.00,  60.00, -20.00), ( 25.00,  60.00, -20.00) ],
        12: [ ( 55.00,  35.00,  20.00), ( 35.00,  55.00,  20.00), ( 35.00,  55.00, -20.00), ( 55.00,  35.00, -20.00) ],
        
        13: [ ( 55.00, -20.00, -35.00), ( 55.00,  20.00, -35.00), ( 35.00,  20.00, -55.00), ( 35.00, -20.00, -55.00) ],
        14: [ (-25.00, -55.00, -35.00), ( 25.00, -55.00, -35.00), ( 25.00, -35.00, -55.00), (-25.00, -35.00, -55.00) ],
        15: [ (-55.00,  20.00, -35.00), (-55.00, -20.00, -35.00), (-35.00, -20.00, -55.00), (-35.00,  20.00, -55.00) ],
        16: [ ( 25.00,  55.00, -35.00), (-25.00,  55.00, -35.00), (-25.00,  35.00, -55.00), ( 25.00,  35.00, -55.00) ],
    }
    
    # ์™ผ์ชฝ ํŒ” ๋งˆ์ปค๋“ค (18-35๋ฒˆ) - ๊ธฐ์กด๊ณผ ๋™์ผํ•œ ํŒจํ„ด์œผ๋กœ ๋ณต์‚ฌ
    for i in range(18 - 1): # left part
        obj_points_map[i + 18] = obj_points_map[i]
    
    # ๋‚˜๋จธ์ง€ solve ํ•จ์ˆ˜๋Š” ๋™์ผ...

1.3.2 ์Šค์ผ€์ผ ํŒฉํ„ฐ ์กฐ์ •

# teleoprator.py์—์„œ ์Šค์ผ€์ผ ํŒฉํ„ฐ ์กฐ์ •
class Teleopoperator:
    def __init__(self):        
        # ... ๊ธฐ์กด ์ฝ”๋“œ ...
        
        # ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์— ๋งž๋Š” ์Šค์ผ€์ผ ํŒฉํ„ฐ ์กฐ์ •
        # ๊ธฐ์กด: scale=1.0
        # ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์ด ๋” ํฌ๋‹ค๋ฉด: scale=1.2
        # ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์ด ๋” ์ž‘๋‹ค๋ฉด: scale=0.8
        self.solve = get_solve(scale=1.2)  # ์˜ˆ์‹œ: 20% ๋” ํฐ ๋กœ๋ด‡ํŒ”

2. ๋ฌผ๋ฆฌ์  ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ •

2.1 ๊ทธ๋ฆฌํผ ๊ด€๋ จ ํŒŒ๋ผ๋ฏธํ„ฐ

2.1.1 ๊ทธ๋ฆฌํผ ์ตœ๋Œ€ ๊ฐœ๋ฐฉ ๊ฑฐ๋ฆฌ

ํŒŒ์ผ ์œ„์น˜: non_ros_src/astra_teleop_web/src/astra_teleop_web/teleoprator.py

ํ˜„์žฌ ์„ค์ •:

GRIPPER_MAX = 0.055  # 55mm (๊ธฐ์กด SCARA ๋กœ๋ด‡ํŒ”)

์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์šฉ ์ˆ˜์ • ์˜ˆ์‹œ:

# ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์˜ ๊ทธ๋ฆฌํผ ํฌ๊ธฐ์— ๋งž๊ฒŒ ์กฐ์ •
GRIPPER_MAX = 0.080  # 80mm (๋” ํฐ ๊ทธ๋ฆฌํผ)
# ๋˜๋Š”
GRIPPER_MAX = 0.040  # 40mm (๋” ์ž‘์€ ๊ทธ๋ฆฌํผ)

2.1.2 ๊ทธ๋ฆฌํผ ์ œ์–ด ๋กœ์ง ํ™•์ธ

def pedal_cb(self, pedal_real_values):
    # ... ๊ธฐ์กด ์ฝ”๋“œ ...
    
    if self.teleop_mode == "arm":
        # ... ๊ธฐ์กด ์ฝ”๋“œ ...
        
        gripper_pos = {}
        for side in ["left", "right"]:
            # GRIPPER_MAX๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ž๋™์œผ๋กœ ๋ฐ˜์˜๋จ
            gripper_pos[side] = (1 - values[f"{side}-gripper"]) * GRIPPER_MAX

2.2 ๋ฆฌํ”„ํŠธ ๊ด€๋ จ ํŒŒ๋ผ๋ฏธํ„ฐ

2.2.1 ์ดˆ๊ธฐ ๋ฆฌํ”„ํŠธ ๋†’์ด

ํ˜„์žฌ ์„ค์ •:

INITIAL_LIFT_DISTANCE = 0.8  # 800mm (๊ธฐ์กด SCARA ๋กœ๋ด‡ํŒ”)

์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์šฉ ์ˆ˜์ • ์˜ˆ์‹œ:

# ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์˜ ์ž‘์—… ๋†’์ด์— ๋งž๊ฒŒ ์กฐ์ •
INITIAL_LIFT_DISTANCE = 1.0   # 1000mm (๋” ํฐ ๋กœ๋ด‡ํŒ”)
# ๋˜๋Š”
INITIAL_LIFT_DISTANCE = 0.6   # 600mm (๋” ์ž‘์€ ๋กœ๋ด‡ํŒ”)

2.2.2 ๋ฆฌํ”„ํŠธ ๋ฒ”์œ„ ๋ฐ ์†๋„

ํ˜„์žฌ ์„ค์ • (pedal_cb ํ•จ์ˆ˜ ๋‚ด๋ถ€):

LIFT_VEL_MAX = 0.5           # ๋ฆฌํ”„ํŠธ ์†๋„
LIFT_DISTANCE_MIN = 0        # ์ตœ์†Œ ๋†’์ด
LIFT_DISTANCE_MAX = 1.2      # ์ตœ๋Œ€ ๋†’์ด

์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์šฉ ์ˆ˜์ • ์˜ˆ์‹œ:

def pedal_cb(self, pedal_real_values):
    # ... ๊ธฐ์กด ์ฝ”๋“œ ...
    
    if self.teleop_mode == "arm":
        # ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์— ๋งž๋Š” ๋ฆฌํ”„ํŠธ ํŒŒ๋ผ๋ฏธํ„ฐ
        LIFT_VEL_MAX = 0.3           # ๋” ์•ˆ์ „ํ•œ ์†๋„
        LIFT_DISTANCE_MIN = 0.2      # ์ตœ์†Œ ๋†’์ด ์ฆ๊ฐ€
        LIFT_DISTANCE_MAX = 1.5      # ์ตœ๋Œ€ ๋†’์ด ์ฆ๊ฐ€
        
        # ... ๋‚˜๋จธ์ง€ ๋กœ์ง์€ ๋™์ผ

2.3 ํ—ค๋“œ ํ‹ธํŠธ ๊ณ„์‚ฐ ํ•จ์ˆ˜

2.3.1 ํ˜„์žฌ ์„ค์ •

def get_head_tilt(self, lift_distance):
    point0_lift = 0
    point0_tilt = 1.36
    point1_lift = 0.8
    point1_tilt = 1.06
    
    return point0_tilt + (point1_tilt - point0_tilt) * (lift_distance - point0_lift) / (point1_lift - point0_lift)

2.3.2 ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์šฉ ์ˆ˜์ •

def get_head_tilt(self, lift_distance):
    # ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์˜ ์ž‘์—… ๊ณต๊ฐ„์— ๋งž๊ฒŒ ์กฐ์ •
    point0_lift = 0.2        # ์ตœ์†Œ ๋†’์ด ์ฆ๊ฐ€
    point0_tilt = 1.36       # ์ตœ์†Œ ๋†’์ด์—์„œ์˜ ํ‹ธํŠธ ๊ฐ๋„
    point1_lift = 1.0        # ์ตœ๋Œ€ ๋†’์ด ์ฆ๊ฐ€ (INITIAL_LIFT_DISTANCE์™€ ๋งž์ถค)
    point1_tilt = 1.06       # ์ตœ๋Œ€ ๋†’์ด์—์„œ์˜ ํ‹ธํŠธ ๊ฐ๋„
    
    return point0_tilt + (point1_tilt - point0_tilt) * (lift_distance - point0_lift) / (point1_lift - point0_lift)

3. ์‹ค์ œ ์ˆ˜์ • ์ž‘์—… ์ˆœ์„œ

3.1 1๋‹จ๊ณ„: ๋งˆ์ปค ์ขŒํ‘œ ์ธก์ •

# 1. ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์—์„œ ๋งˆ์ปค ๋ฐฐ์น˜
# 2. 3D ์ขŒํ‘œ ์ธก์ • (๋ ˆ์ด์ € ์ธก์ •๊ธฐ, 3D ์Šค์บ๋„ˆ ๋“ฑ)
# 3. ์ธก์ • ๊ฒฐ๊ณผ๋ฅผ Excel์ด๋‚˜ CSV๋กœ ์ •๋ฆฌ

3.2 2๋‹จ๊ณ„: ์ฝ”๋“œ ์ˆ˜์ •

# 1. process.py์˜ obj_points_map ์—…๋ฐ์ดํŠธ
# 2. teleoprator.py์˜ ๋ฌผ๋ฆฌ์  ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ •
# 3. ์Šค์ผ€์ผ ํŒฉํ„ฐ ์กฐ์ •

3.3 3๋‹จ๊ณ„: ํ…Œ์ŠคํŠธ

# ๋งˆ์ปค ๊ฐ์ง€ ํ…Œ์ŠคํŠธ
from astra_teleop.process import get_solve

solve = get_solve(scale=1.2)  # ์ƒˆ๋กœ์šด ์Šค์ผ€์ผ
# ์‹ค์ œ ์นด๋ฉ”๋ผ๋กœ ๋งˆ์ปค ๊ฐ์ง€ ํ…Œ์ŠคํŠธ

3.4 4๋‹จ๊ณ„: ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

# ์ „์ฒด ํ…”๋ ˆ์˜คํผ๋ ˆ์ด์…˜ ์‹œ์Šคํ…œ ํ…Œ์ŠคํŠธ
# - ๋งˆ์ปค ๊ธฐ๋ฐ˜ ์† ์ถ”์  ์ •ํ™•๋„ ํ™•์ธ
# - ๊ทธ๋ฆฌํผ ์ œ์–ด ๋ฒ”์œ„ ํ™•์ธ
# - ๋ฆฌํ”„ํŠธ ๋™์ž‘ ๋ฒ”์œ„ ํ™•์ธ

4. ์ฃผ์˜์‚ฌํ•ญ

4.1 ์ขŒํ‘œ๊ณ„ ์ผ๊ด€์„ฑ

  • ๋ชจ๋“  ๋งˆ์ปค ์ขŒํ‘œ๊ฐ€ ๋™์ผํ•œ ๊ธฐ์ค€์ ์„ ์‚ฌ์šฉํ•˜๋Š”์ง€ ํ™•์ธ
  • ์™ผ์ชฝ/์˜ค๋ฅธ์ชฝ ํŒ”์˜ ๋Œ€์นญ์„ฑ ์œ ์ง€

4.2 ๋‹จ์œ„ ํ†ต์ผ

  • ๋ชจ๋“  ์ขŒํ‘œ๋Š” mm ๋‹จ์œ„
  • ๋ชจ๋“  ๊ฑฐ๋ฆฌ๋Š” m ๋‹จ์œ„
  • ๋ชจ๋“  ๊ฐ๋„๋Š” ๋ผ๋””์•ˆ ๋‹จ์œ„

4.3 ์•ˆ์ „์„ฑ ํ™•์ธ

  • ์ƒˆ๋กœ์šด ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์•ˆ์ „ํ•œ ๋ฒ”์œ„ ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธ
  • ๊ทธ๋ฆฌํผ ๊ฐœ๋ฐฉ ๊ฑฐ๋ฆฌ๊ฐ€ ๋ฌผ๋ฆฌ์  ํ•œ๊ณ„๋ฅผ ์ดˆ๊ณผํ•˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธ
  • ๋ฆฌํ”„ํŠธ ๋ฒ”์œ„๊ฐ€ ๋กœ๋ด‡ํŒ”์˜ ๊ธฐ๊ตฌํ•™์  ํ•œ๊ณ„๋ฅผ ์ดˆ๊ณผํ•˜์ง€ ์•Š๋Š”์ง€ ํ™•์ธ

5. ์˜ˆ์‹œ: ์ „์ฒด ์ˆ˜์ •๋œ ํŒŒ์ผ

5.1 ์ˆ˜์ •๋œ process.py ์˜ˆ์‹œ

# non_ros_src/astra_teleop/src/astra_teleop/process.py

def get_solve(scale=1):
    # ์ƒˆ๋กœ์šด ์ž์œ 6์ถ• ๋กœ๋ด‡ํŒ”์šฉ ๋งˆ์ปค ์ขŒํ‘œ
    obj_points_map = {
        # ์˜ค๋ฅธ์ชฝ ํŒ” ๋งˆ์ปค๋“ค (0-17๋ฒˆ)
        0:  [ (-25.00, -20.00,  60.00), (-25.00,  20.00,  60.00), ( 25.00,  20.00,  60.00), ( 25.00, -20.00,  60.00) ],
        1:  [ ( 35.00, -20.00,  55.00), ( 35.00,  20.00,  55.00), ( 55.00,  20.00,  35.00), ( 55.00, -20.00,  35.00) ],
        # ... ๋‚˜๋จธ์ง€ ๋งˆ์ปค๋“ค
        
        # ์™ผ์ชฝ ํŒ” ๋งˆ์ปค๋“ค (18-35๋ฒˆ) - ์˜ค๋ฅธ์ชฝ๊ณผ ๋™์ผํ•œ ํŒจํ„ด
        18: [ (-25.00, -20.00,  60.00), (-25.00,  20.00,  60.00), ( 25.00,  20.00,  60.00), ( 25.00, -20.00,  60.00) ],
        19: [ ( 35.00, -20.00,  55.00), ( 35.00,  20.00,  55.00), ( 55.00,  20.00,  35.00), ( 55.00, -20.00,  35.00) ],
        # ... ๋‚˜๋จธ์ง€ ๋งˆ์ปค๋“ค
    }
    
    # ๋‚˜๋จธ์ง€ solve ํ•จ์ˆ˜๋Š” ๋™์ผ...

5.2 ์ˆ˜์ •๋œ teleoprator.py ์˜ˆ์‹œ

# non_ros_src/astra_teleop_web/src/astra_teleop_web/teleoprator.py

# ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์— ๋งž๋Š” ๋ฌผ๋ฆฌ์  ํŒŒ๋ผ๋ฏธํ„ฐ
GRIPPER_MAX = 0.080              # 80mm (๋” ํฐ ๊ทธ๋ฆฌํผ)
INITIAL_LIFT_DISTANCE = 1.0       # 1000mm (๋” ๋†’์€ ์ž‘์—… ๊ณต๊ฐ„)
FAR_SEEING_HEAD_TILT = 0.26       # ๋™์ผ

class Teleopoperator:
    def __init__(self):        
        # ... ๊ธฐ์กด ์ฝ”๋“œ ...
        
        # ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์— ๋งž๋Š” ์Šค์ผ€์ผ ํŒฉํ„ฐ
        self.solve = get_solve(scale=1.2)  # 20% ๋” ํฐ ๋กœ๋ด‡ํŒ”
        
        # ... ๋‚˜๋จธ์ง€ ์ฝ”๋“œ๋Š” ๋™์ผ

Robot-Arm-Modification

๊ฐœ์š” (Overview)

๋กœ๋ด‡ํŒ”์˜ ํฌ๊ธฐ์™€ ํ˜•ํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ, ํ…”๋ ˆ์˜คํผ๋ ˆ์ด์…˜ ์‹œ์Šคํ…œ์—์„œ ์ˆ˜์ •ํ•ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„๋“ค์„ ๋ถ„์„ํ•œ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค. ์ด ๋ฌธ์„œ๋Š” astra_teleop_web ํŒจํ‚ค์ง€์˜ ๊ตฌ์กฐ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

์›๊ฒฉ์กฐ์ข… ์‹œ์Šคํ…œ: 6์ถ• SCARA โ†’ ์ž์œ 6์ถ• ๋กœ๋ด‡ํŒ” ์ „ํ™˜

ํ•ต์‹ฌ ๊ฒฐ๋ก : ์ˆ˜์ • ํ•„์š” ์—†์Œ

์›๊ฒฉ์กฐ์ข… ์‹œ์Šคํ…œ์˜ ๊ด€์ ์—์„œ 6์ถ• SCARA์—์„œ ์ž์œ 6์ถ• ๋กœ๋ด‡ํŒ”๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์€ ๊ธฐ์กด ํ…”๋ ˆ์˜คํผ๋ ˆ์ด์…˜ ์ฝ”๋“œ๋ฅผ ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์ด์œ  ๋ถ„์„

1. ์ถ”์ƒํ™”๋œ ์ธํ„ฐํŽ˜์ด์Šค ์‚ฌ์šฉ

ํ˜„์žฌ teleoprator.py๋Š” ๋กœ๋ด‡ํŒ”์˜ ๊ตฌ์ฒด์ ์ธ ๊ธฐ๊ตฌํ•™์— ์˜์กดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค:

# teleoprator.py์˜ ํ•ต์‹ฌ ์ฝœ๋ฐฑ๋“ค
self.on_pub_goal = None          # ๋ชฉํ‘œ ์œ„์น˜ ์ „์†ก (์ถ”์ƒํ™”๋จ)
self.on_pub_gripper = None       # ๊ทธ๋ฆฌํผ ์ œ์–ด (์ถ”์ƒํ™”๋จ)
self.on_get_current_eef_pose = None  # ํ˜„์žฌ ์œ„์น˜ ์กฐํšŒ (์ถ”์ƒํ™”๋จ)
self.on_get_initial_eef_pose = None  # ์ดˆ๊ธฐ ์œ„์น˜ ์กฐํšŒ (์ถ”์ƒํ™”๋จ)

2. ์ขŒํ‘œ๊ณ„ ๋ณ€ํ™˜์˜ ์ถ”์ƒํ™”

# hand_cb์—์„œ ๋กœ๋ด‡ ๋ช…๋ น ์ƒ์„ฑ
Tsgoal = self.Tscam[side] @ self.Tcamgoal_last[side]
self.on_pub_goal(side, Tsgoal)  # 4x4 ๋ณ€ํ™˜ ํ–‰๋ ฌ๋กœ ์ถ”์ƒํ™”๋จ

3. ๊ธฐ๊ตฌํ•™ ๋…๋ฆฝ์  ์„ค๊ณ„

  • ์ž…๋ ฅ: ArUco ๋งˆ์ปค โ†’ 3D ์œ„์น˜ (์นด๋ฉ”๋ผ ์ขŒํ‘œ๊ณ„)
  • ์ฒ˜๋ฆฌ: ์ขŒํ‘œ๊ณ„ ๋ณ€ํ™˜ (์นด๋ฉ”๋ผ โ†’ ๋กœ๋ด‡)
  • ์ถœ๋ ฅ: 4x4 ๋ณ€ํ™˜ ํ–‰๋ ฌ (์ถ”์ƒํ™”๋œ ๋กœ๋ด‡ ๋ช…๋ น)

์‹ค์ œ ์ˆ˜์ •์ด ํ•„์š”ํ•œ ๋ถ€๋ถ„

1. ํ•˜์œ„ ๋ ˆ๋ฒจ ์ œ์–ด๊ธฐ๋งŒ ์ˆ˜์ •

# astra_controller ํŒจํ‚ค์ง€์—์„œ๋งŒ ์ˆ˜์ • ํ•„์š”
# - arm_controller.py: 6์ถ• ์ž์œ ๋„์— ๋งž๋Š” ์ œ์–ด ๋กœ์ง
# - URDF ํŒŒ์ผ: ์ƒˆ๋กœ์šด ๊ธฐ๊ตฌํ•™ ์ •์˜
# - MoveIt ์„ค์ •: ์ƒˆ๋กœ์šด ๊ธฐ๊ตฌํ•™ ๋ฐ˜์˜

2. ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋“ค์˜ ๊ตฌํ˜„์ฒด๋งŒ ๋ณ€๊ฒฝ

# teleoprator.py๋Š” ์ˆ˜์ • ๋ถˆํ•„์š”
# ๋Œ€์‹  ์ด ์ฝœ๋ฐฑ๋“ค์„ ๊ตฌํ˜„ํ•˜๋Š” ํ•˜์œ„ ์‹œ์Šคํ…œ๋งŒ ์ˆ˜์ •:

def on_pub_goal_implementation(side, Tsgoal):
    # 6์ถ• SCARA์šฉ ๊ตฌํ˜„ โ†’ 6์ถ• ์ž์œ ๋„์šฉ ๊ตฌํ˜„์œผ๋กœ ๋ณ€๊ฒฝ
    # ํ•˜์ง€๋งŒ teleoprator.py๋Š” ์ด ๊ตฌํ˜„์ฒด๋ฅผ ๋ชฐ๋ผ๋„ ๋จ
    
def on_get_current_eef_pose_implementation(side):
    # 6์ถ• SCARA์šฉ ๊ตฌํ˜„ โ†’ 6์ถ• ์ž์œ ๋„์šฉ ๊ตฌํ˜„์œผ๋กœ ๋ณ€๊ฒฝ
    # ํ•˜์ง€๋งŒ teleoprator.py๋Š” ์ด ๊ตฌํ˜„์ฒด๋ฅผ ๋ชฐ๋ผ๋„ ๋จ

ํ™•์ธํ•ด์•ผ ํ•  ๋ถ€๋ถ„

1. ArUco ๋งˆ์ปค ๋ฐฐ์น˜

# astra_teleop/process.py์˜ obj_points_map
# ๋กœ๋ด‡ํŒ” ํฌ๊ธฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ๋งˆ์ปค ์ขŒํ‘œ๋งŒ ์ˆ˜์ •
obj_points_map = {
    0: [(-15.00, -15.00, 48.28), ...],  # ๊ธฐ์กด ์ขŒํ‘œ
    # โ†’ ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ” ํฌ๊ธฐ์— ๋งž๋Š” ์ขŒํ‘œ๋กœ ๋ณ€๊ฒฝ
}

2. ๋ฌผ๋ฆฌ์  ํŒŒ๋ผ๋ฏธํ„ฐ (์„ ํƒ์ )

# teleoprator.py์˜ ์ƒ์ˆ˜๋“ค (ํ•„์š”์‹œ๋งŒ ์กฐ์ •)
GRIPPER_MAX = 0.055              # ๊ทธ๋ฆฌํผ ํฌ๊ธฐ๊ฐ€ ๋‹ค๋ฅด๋ฉด ์กฐ์ •
INITIAL_LIFT_DISTANCE = 0.8      # ์ž‘์—… ๋†’์ด๊ฐ€ ๋‹ค๋ฅด๋ฉด ์กฐ์ •
LIFT_DISTANCE_MIN = 0            # ๋ฆฌํ”„ํŠธ ๋ฒ”์œ„๊ฐ€ ๋‹ค๋ฅด๋ฉด ์กฐ์ •
LIFT_DISTANCE_MAX = 1.2          # ๋ฆฌํ”„ํŠธ ๋ฒ”์œ„๊ฐ€ ๋‹ค๋ฅด๋ฉด ์กฐ์ •

๊ฒฐ๋ก 

์›๊ฒฉ์กฐ์ข… ์‹œ์Šคํ…œ ์ž์ฒด๋Š” ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

  • โœ… ์œ ์ง€๋˜๋Š” ๋ถ€๋ถ„: teleoprator.py, webserver.py, ArUco ์ฒ˜๋ฆฌ ๋กœ์ง
  • ๐Ÿ”ง ์ˆ˜์ • ํ•„์š”ํ•œ ๋ถ€๋ถ„: astra_controller ํŒจํ‚ค์ง€์˜ ํ•˜์œ„ ์ œ์–ด๊ธฐ๋“ค
  • ๐Ÿ“ ์กฐ์ • ๊ฐ€๋Šฅํ•œ ๋ถ€๋ถ„: ArUco ๋งˆ์ปค ์ขŒํ‘œ, ๋ฌผ๋ฆฌ์  ํŒŒ๋ผ๋ฏธํ„ฐ (์„ ํƒ์ )

๊ถŒ์žฅ ์ž‘์—… ์ˆœ์„œ

  1. ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์˜ ArUco ๋งˆ์ปค ์ขŒํ‘œ ์ธก์ • ๋ฐ ์—…๋ฐ์ดํŠธ
  2. astra_controller ํŒจํ‚ค์ง€์˜ ํ•˜์œ„ ์ œ์–ด๊ธฐ ์ˆ˜์ •
  3. URDF ๋ฐ MoveIt ์„ค์ • ์—…๋ฐ์ดํŠธ
  4. ํ•„์š”์‹œ ๋ฌผ๋ฆฌ์  ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ • (๊ทธ๋ฆฌํผ ํฌ๊ธฐ, ์ž‘์—… ๋†’์ด ๋“ฑ)

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ธฐ์กด ์›๊ฒฉ์กฐ์ข… ์‹œ์Šคํ…œ์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์ƒˆ๋กœ์šด 6์ถ• ์ž์œ ๋„ ๋กœ๋ด‡ํŒ”์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

SCARA โ†’ ํ˜‘๋™๋กœ๋ด‡ ์ „ํ™˜ ํŠน๋ณ„ ๊ณ ๋ ค์‚ฌํ•ญ

1. ๊ธฐ๊ตฌํ•™์  ์ฐจ์ด์ 

1.1 SCARA vs ํ˜‘๋™๋กœ๋ด‡์˜ ์ฃผ์š” ์ฐจ์ด

  • SCARA: ํ‰๋ฉด ์šด๋™, ๊ณ ์ •๋œ Z์ถ•, ๋น ๋ฅธ ์†๋„, ์ •๋ฐ€ํ•œ ๋ฐ˜๋ณต ์ž‘์—…
  • ํ˜‘๋™๋กœ๋ด‡: 6์ถ• ์ž์œ ๋„, ์œ ์—ฐํ•œ ์ž‘์—… ๊ณต๊ฐ„, ์•ˆ์ „ ๊ธฐ๋Šฅ, ์‚ฌ๋žŒ๊ณผ์˜ ํ˜‘์—…

1.2 ๊ธฐ๊ตฌํ•™์  ์ˆ˜์ • ํ•„์š”์‚ฌํ•ญ

ํ˜„์žฌ SCARA ๊ธฐ๋ฐ˜ ์„ค์ •:

# teleoprator.py์˜ reset_arm() ํ•จ์ˆ˜
goal_pose = {
    "left": self.on_get_initial_eef_pose("left", [self.lift_distance, joint_bent, -joint_bent, 0, 0, 0]),
    "right": self.on_get_initial_eef_pose("right", [self.lift_distance, -joint_bent, joint_bent, 0, 0, 0]),
}

ํ˜‘๋™๋กœ๋ด‡์šฉ ์ˆ˜์ •:

# ํ˜‘๋™๋กœ๋ด‡์˜ 6์ถ• ์ž์œ ๋„์— ๋งž๋Š” ์ดˆ๊ธฐ ์ž์„ธ ์„ค์ •
def get_collaborative_robot_initial_pose(self, side):
    if side == "left":
        return [self.lift_distance, -math.pi/6, -math.pi/3, 0, math.pi/2, 0]  # ํ˜‘๋™๋กœ๋ด‡ ํŠน์„ฑ์— ๋งž๋Š” ์ž์„ธ
    else:  # right
        return [self.lift_distance, math.pi/6, math.pi/3, 0, -math.pi/2, 0]

2. ์•ˆ์ „์„ฑ ๊ด€๋ จ ์ˆ˜์ •

2.1 ์†๋„ ์ œํ•œ ๋ฐ ์•ˆ์ „ ํŒŒ๋ผ๋ฏธํ„ฐ

ํ˜„์žฌ SCARA ์„ค์ •:

LIFT_VEL_MAX = 0.5  # SCARA์šฉ ๋น ๋ฅธ ์†๋„
LINEAR_VEL_MAX = 1   # ๋ฒ ์ด์Šค ์ด๋™ ์†๋„
ANGULAR_VEL_MAX = 1  # ํšŒ์ „ ์†๋„

ํ˜‘๋™๋กœ๋ด‡์šฉ ์ˆ˜์ •:

# ํ˜‘๋™๋กœ๋ด‡์˜ ์•ˆ์ „ ์†๋„ ์ œํ•œ
COLLABORATIVE_LIFT_VEL_MAX = 0.2  # ๋” ๋‚ฎ์€ ์†๋„๋กœ ์•ˆ์ „์„ฑ ํ™•๋ณด
COLLABORATIVE_LINEAR_VEL_MAX = 0.5
COLLABORATIVE_ANGULAR_VEL_MAX = 0.5

# ์ถ”๊ฐ€ ์•ˆ์ „ ํŒŒ๋ผ๋ฏธํ„ฐ
MAX_FORCE_THRESHOLD = 50.0  # N, ํž˜ ์ œํ•œ
MAX_TORQUE_THRESHOLD = 10.0  # Nm, ํ† ํฌ ์ œํ•œ
EMERGENCY_STOP_DISTANCE = 0.3  # m, ๊ธด๊ธ‰ ์ •์ง€ ๊ฑฐ๋ฆฌ

2.2 ์ถฉ๋Œ ๊ฐ์ง€ ๋ฐ ํšŒํ”ผ

class CollaborativeRobotSafety:
    def __init__(self):
        self.force_sensor_active = True
        self.proximity_sensor_active = True
        self.emergency_stop_active = False
    
    def check_safety_limits(self, current_pose, target_pose):
        # ํž˜/ํ† ํฌ ์ œํ•œ ํ™•์ธ
        if self.detect_excessive_force():
            return False, "Force limit exceeded"
        
        # ๊ทผ์ ‘ ๊ฐ์ง€ ํ™•์ธ
        if self.detect_obstacle_proximity():
            return False, "Obstacle detected"
        
        return True, "Safe to move"
    
    def emergency_stop(self):
        self.emergency_stop_active = True
        # ๋ชจ๋“  ๋ชจํ„ฐ ์ •์ง€
        self.stop_all_motors()

3. ์ œ์–ด ๋ชจ๋“œ ์ˆ˜์ •

3.1 ํ˜‘๋™๋กœ๋ด‡ ์ „์šฉ ์ œ์–ด ๋ชจ๋“œ

# teleoprator.py์— ์ถ”๊ฐ€ํ•  ํ˜‘๋™๋กœ๋ด‡ ๋ชจ๋“œ
COLLABORATIVE_MODES = {
    "position_control": "์ •๋ฐ€ ์œ„์น˜ ์ œ์–ด",
    "force_control": "ํž˜ ์ œ์–ด ๋ชจ๋“œ", 
    "impedance_control": "์ž„ํ”ผ๋˜์Šค ์ œ์–ด",
    "gravity_compensation": "์ค‘๋ ฅ ๋ณด์ƒ ๋ชจ๋“œ",
    "manual_guidance": "์ˆ˜๋™ ๊ฐ€์ด๋˜์Šค"
}

def update_collaborative_mode(self, mode):
    if mode == "force_control":
        self.enable_force_control()
        self.set_force_limits(MAX_FORCE_THRESHOLD)
    elif mode == "impedance_control":
        self.enable_impedance_control()
        self.set_impedance_parameters()
    # ... ๊ธฐํƒ€ ๋ชจ๋“œ๋“ค

3.2 ํŽ˜๋‹ฌ ์ œ์–ด ์ˆ˜์ •

ํ˜‘๋™๋กœ๋ด‡์šฉ ํŽ˜๋‹ฌ ๋งคํ•‘:

def pedal_cb_collaborative(self, pedal_real_values):
    if self.teleop_mode == "arm":
        # ํ˜‘๋™๋กœ๋ด‡ ํŠนํ™” ์ œ์–ด
        values = self.process_pedal_input(pedal_real_values)
        
        # ํž˜ ์ œ์–ด ๋ชจ๋“œ
        if self.collaborative_mode == "force_control":
            force_x = values["force_x"] * MAX_FORCE_THRESHOLD
            force_y = values["force_y"] * MAX_FORCE_THRESHOLD
            force_z = values["force_z"] * MAX_FORCE_THRESHOLD
            self.on_pub_force_command(force_x, force_y, force_z)
        
        # ์ž„ํ”ผ๋˜์Šค ์ œ์–ด ๋ชจ๋“œ
        elif self.collaborative_mode == "impedance_control":
            stiffness = values["stiffness"] * MAX_STIFFNESS
            damping = values["damping"] * MAX_DAMPING
            self.on_pub_impedance_command(stiffness, damping)

4. ์„ผ์„œ ๋ฐ ํ”ผ๋“œ๋ฐฑ ์‹œ์Šคํ…œ

4.1 ํ˜‘๋™๋กœ๋ด‡ ์„ผ์„œ ํ†ตํ•ฉ

class CollaborativeRobotSensors:
    def __init__(self):
        self.force_sensor = None
        self.torque_sensor = None
        self.proximity_sensor = None
        self.safety_light_curtain = None
    
    def get_force_feedback(self):
        # 6์ถ• ํž˜/ํ† ํฌ ์„ผ์„œ ๋ฐ์ดํ„ฐ
        return {
            "fx": self.force_sensor.get_force_x(),
            "fy": self.force_sensor.get_force_y(), 
            "fz": self.force_sensor.get_force_z(),
            "tx": self.torque_sensor.get_torque_x(),
            "ty": self.torque_sensor.get_torque_y(),
            "tz": self.torque_sensor.get_torque_z()
        }
    
    def get_proximity_data(self):
        # ๊ทผ์ ‘ ์„ผ์„œ ๋ฐ์ดํ„ฐ
        return {
            "distance": self.proximity_sensor.get_distance(),
            "direction": self.proximity_sensor.get_direction(),
            "safety_zone": self.safety_light_curtain.get_status()
        }

4.2 ์•ˆ์ „ ํ”ผ๋“œ๋ฐฑ ์‹œ์Šคํ…œ

def safety_feedback_cb(self, sensor_data):
    # ์‹ค์‹œ๊ฐ„ ์•ˆ์ „ ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง
    force_data = sensor_data.get("force")
    proximity_data = sensor_data.get("proximity")
    
    # ์œ„ํ—˜ ์ƒํ™ฉ ๊ฐ์ง€
    if self.detect_dangerous_condition(force_data, proximity_data):
        self.trigger_emergency_stop()
        self.webserver.control_datachannel_log("EMERGENCY STOP: Safety limit exceeded")
    
    # ์‚ฌ์šฉ์ž์—๊ฒŒ ์•ˆ์ „ ์ƒํƒœ ์ „๋‹ฌ
    safety_status = self.calculate_safety_status(force_data, proximity_data)
    self.webserver.control_datachannel_log(f"Safety Status: {safety_status}")

5. ArUco ๋งˆ์ปค ์‹œ์Šคํ…œ ์ˆ˜์ •

5.1 ํ˜‘๋™๋กœ๋ด‡์šฉ ๋งˆ์ปค ๋ฐฐ์น˜

ํ˜‘๋™๋กœ๋ด‡์˜ ์œ ์—ฐํ•œ ์ž‘์—… ๊ณต๊ฐ„์„ ๊ณ ๋ คํ•œ ๋งˆ์ปค ๋ฐฐ์น˜:

# astra_teleop/process.py ์ˆ˜์ •
def get_collaborative_robot_marker_positions(self):
    # ํ˜‘๋™๋กœ๋ด‡์˜ ๋” ๋„“์€ ์ž‘์—… ๊ณต๊ฐ„์— ๋งž๋Š” ๋งˆ์ปค ๋ฐฐ์น˜
    obj_points_map_collaborative = {
        # ๋” ๋„“์€ ๋ฒ”์œ„์˜ ๋งˆ์ปค ๋ฐฐ์น˜
        0: [(-20.00, -20.00, 60.00), (-20.00, 20.00, 60.00), (20.00, 20.00, 60.00), (20.00, -20.00, 60.00)],
        # ... ํ˜‘๋™๋กœ๋ด‡ ์ž‘์—… ๊ณต๊ฐ„์— ๋งž๋Š” ๋งˆ์ปค๋“ค
    }
    return obj_points_map_collaborative

5.2 ๋™์  ๋งˆ์ปค ์ถ”์ 

def adaptive_marker_tracking(self, camera_matrix, distortion_coefficients, corners, ids):
    # ํ˜‘๋™๋กœ๋ด‡์˜ ๋™์  ์›€์ง์ž„์— ๋Œ€์‘ํ•˜๋Š” ์ ์‘ํ˜• ๋งˆ์ปค ์ถ”์ 
    if self.collaborative_mode == "manual_guidance":
        # ์ˆ˜๋™ ๊ฐ€์ด๋˜์Šค ๋ชจ๋“œ์—์„œ๋Š” ๋งˆ์ปค ์ถ”์ ์„ ๋ณด์กฐ์ ์œผ๋กœ ์‚ฌ์šฉ
        return self.get_manual_guidance_pose()
    else:
        # ์ผ๋ฐ˜์ ์ธ ๋งˆ์ปค ๊ธฐ๋ฐ˜ ์ถ”์ 
        return self.solve(camera_matrix, distortion_coefficients, corners, ids)

6. ์›น ์ธํ„ฐํŽ˜์ด์Šค ์ˆ˜์ •

6.1 ํ˜‘๋™๋กœ๋ด‡ ์ „์šฉ UI ์š”์†Œ

// static/index.html์— ์ถ”๊ฐ€ํ•  ํ˜‘๋™๋กœ๋ด‡ UI
const collaborativeRobotUI = {
    safetyStatus: document.getElementById('safety-status'),
    forceDisplay: document.getElementById('force-display'),
    modeSelector: document.getElementById('collaborative-mode'),
    emergencyStop: document.getElementById('emergency-stop-btn'),
    
    updateSafetyStatus(status) {
        this.safetyStatus.textContent = status;
        this.safetyStatus.className = `safety-${status.toLowerCase()}`;
    },
    
    updateForceDisplay(forceData) {
        this.forceDisplay.innerHTML = `
            Fx: ${forceData.fx.toFixed(2)}N
            Fy: ${forceData.fy.toFixed(2)}N  
            Fz: ${forceData.fz.toFixed(2)}N
        `;
    },
    
    enableEmergencyStop() {
        this.emergencyStop.addEventListener('click', () => {
            this.sendControlCommand('emergency_stop');
        });
    }
};

6.2 ์‹ค์‹œ๊ฐ„ ์•ˆ์ „ ๋ชจ๋‹ˆํ„ฐ๋ง

// ์‹ค์‹œ๊ฐ„ ์•ˆ์ „ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
function updateSafetyMonitoring() {
    setInterval(() => {
        fetch('/api/safety-status')
            .then(response => response.json())
            .then(data => {
                collaborativeRobotUI.updateSafetyStatus(data.status);
                collaborativeRobotUI.updateForceDisplay(data.force);
                
                if (data.status === 'DANGER') {
                    collaborativeRobotUI.emergencyStop.style.backgroundColor = 'red';
                }
            });
    }, 100); // 10Hz ์—…๋ฐ์ดํŠธ
}

7. ํ•˜๋“œ์›จ์–ด ์ธํ„ฐํŽ˜์ด์Šค ์ˆ˜์ •

7.1 ํ˜‘๋™๋กœ๋ด‡ ์ œ์–ด๊ธฐ ํ†ต์‹ 

# astra_controller/astra_controller/collaborative_robot_controller.py
class CollaborativeRobotController:
    def __init__(self, robot_ip, robot_port):
        self.robot_ip = robot_ip
        self.robot_port = robot_port
        self.safety_monitor = CollaborativeRobotSafety()
        self.sensors = CollaborativeRobotSensors()
    
    def send_position_command(self, joint_positions):
        # ํ˜‘๋™๋กœ๋ด‡ ์ œ์–ด๊ธฐ๋กœ ์œ„์น˜ ๋ช…๋ น ์ „์†ก
        command = {
            "type": "position_control",
            "joints": joint_positions,
            "velocity": self.calculate_safe_velocity(),
            "acceleration": self.calculate_safe_acceleration()
        }
        return self.send_command(command)
    
    def send_force_command(self, force_vector):
        # ํž˜ ์ œ์–ด ๋ช…๋ น ์ „์†ก
        command = {
            "type": "force_control", 
            "force": force_vector,
            "max_force": MAX_FORCE_THRESHOLD
        }
        return self.send_command(command)
    
    def emergency_stop(self):
        # ๊ธด๊ธ‰ ์ •์ง€ ๋ช…๋ น
        command = {
            "type": "emergency_stop",
            "timestamp": time.time()
        }
        return self.send_command(command)

8. ์ˆ˜์ • ์šฐ์„ ์ˆœ์œ„ (ํ˜‘๋™๋กœ๋ด‡ ์ „ํ™˜)

8.1 ๋†’์€ ์šฐ์„ ์ˆœ์œ„ (์•ˆ์ „ ๊ด€๋ จ)

  1. ์•ˆ์ „ ์‹œ์Šคํ…œ ํ†ตํ•ฉ: ํž˜/ํ† ํฌ ์„ผ์„œ, ๊ทผ์ ‘ ์„ผ์„œ, ๊ธด๊ธ‰ ์ •์ง€
  2. ์†๋„ ์ œํ•œ ์กฐ์ •: ํ˜‘๋™๋กœ๋ด‡ ์•ˆ์ „ ์†๋„๋กœ ๋ณ€๊ฒฝ
  3. ๊ธฐ๊ตฌํ•™ ์ˆ˜์ •: 6์ถ• ์ž์œ ๋„์— ๋งž๋Š” ์ œ์–ด ๋กœ์ง
  4. URDF ์—…๋ฐ์ดํŠธ: ํ˜‘๋™๋กœ๋ด‡ ๋ชจ๋ธ๋กœ ๋ณ€๊ฒฝ

8.2 ์ค‘๊ฐ„ ์šฐ์„ ์ˆœ์œ„ (๊ธฐ๋Šฅ ๊ด€๋ จ)

  1. ์ œ์–ด ๋ชจ๋“œ ์ถ”๊ฐ€: ํž˜ ์ œ์–ด, ์ž„ํ”ผ๋˜์Šค ์ œ์–ด, ์ˆ˜๋™ ๊ฐ€์ด๋˜์Šค
  2. ์„ผ์„œ ํ”ผ๋“œ๋ฐฑ: ์‹ค์‹œ๊ฐ„ ํž˜/ํ† ํฌ ๋ชจ๋‹ˆํ„ฐ๋ง
  3. UI ๊ฐœ์„ : ์•ˆ์ „ ์ƒํƒœ ํ‘œ์‹œ, ๊ธด๊ธ‰ ์ •์ง€ ๋ฒ„ํŠผ
  4. ArUco ๋งˆ์ปค: ํ˜‘๋™๋กœ๋ด‡ ์ž‘์—… ๊ณต๊ฐ„์— ๋งž๋Š” ๋ฐฐ์น˜

8.3 ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ (์ตœ์ ํ™”)

  1. ์„ฑ๋Šฅ ์ตœ์ ํ™”: ์‹ค์‹œ๊ฐ„ ์ œ์–ด ๋ฃจํ”„ ์ตœ์ ํ™”
  2. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜: ์ง๊ด€์ ์ธ ํ˜‘๋™๋กœ๋ด‡ ์ธํ„ฐํŽ˜์ด์Šค
  3. ๋กœ๊น… ์‹œ์Šคํ…œ: ์•ˆ์ „ ์ด๋ฒคํŠธ ๋กœ๊น…

์‹œ์Šคํ…œ ๊ตฌ์กฐ ๋ถ„์„

1. ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธ

1.1 Teleopoperator (teleoprator.py)

  • ์—ญํ• : ์›น ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ์ž…๋ ฅ์„ ๋กœ๋ด‡ ๋ช…๋ น์œผ๋กœ ๋ณ€ํ™˜
  • ์ฃผ์š” ๊ธฐ๋Šฅ:
    • ArUco ๋งˆ์ปค ๊ธฐ๋ฐ˜ ์† ์œ„์น˜ ์ถ”์ 
    • ํŽ˜๋‹ฌ ์ž…๋ ฅ ์ฒ˜๋ฆฌ (๊ทธ๋ฆฌํผ, ๋ฆฌํ”„ํŠธ, ๋ฒ ์ด์Šค ์ œ์–ด)
    • ์ œ์–ด ๋ชจ๋“œ ๊ด€๋ฆฌ (arm, base, none)

1.2 WebServer (webserver.py)

  • ์—ญํ• : ์›น ํด๋ผ์ด์–ธํŠธ์™€์˜ WebRTC ํ†ต์‹  ๊ด€๋ฆฌ
  • ์ฃผ์š” ๊ธฐ๋Šฅ:
    • ๋น„๋””์˜ค ์ŠคํŠธ๋ฆผ ์ œ๊ณต (head, wrist_left, wrist_right)
    • ๋ฐ์ดํ„ฐ ์ฑ„๋„ ๊ด€๋ฆฌ (hand, pedal, control)

1.3 ArUco ๋งˆ์ปค ์ฒ˜๋ฆฌ (astra_teleop/process.py)

  • ์—ญํ• : ์นด๋ฉ”๋ผ์—์„œ ArUco ๋งˆ์ปค๋ฅผ ๊ฐ์ง€ํ•˜๊ณ  3D ์œ„์น˜ ๊ณ„์‚ฐ
  • ์ฃผ์š” ๊ธฐ๋Šฅ: get_solve() ํ•จ์ˆ˜๋กœ ๋งˆ์ปค ์ขŒํ‘œ๋ฅผ ๋กœ๋ด‡ ์ขŒํ‘œ๊ณ„๋กœ ๋ณ€ํ™˜

์ˆ˜์ •์ด ํ•„์š”ํ•œ ๋ถ€๋ถ„๋“ค

1. ArUco ๋งˆ์ปค ์ขŒํ‘œ๊ณ„ (๊ฐ€์žฅ ์ค‘์š”)

1.1 astra_teleop/process.py์˜ obj_points_map

ํ˜„์žฌ ์ƒํƒœ:

obj_points_map = { # right part
    # top_left, top_right, bottom_right, bottom_left
    0:  [ (-15.00, -15.00,  48.28), (-15.00,  15.00,  48.28), ( 15.00,  15.00,  48.28), ( 15.00, -15.00,  48.28) ],
    # ... ๋” ๋งŽ์€ ๋งˆ์ปค ์ขŒํ‘œ๋“ค
}

์ˆ˜์ • ํ•„์š” ์‚ฌํ•ญ:

  • ๋งˆ์ปค ์œ„์น˜: ๋กœ๋ด‡ํŒ” ํฌ๊ธฐ๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ArUco ๋งˆ์ปค๋“ค์˜ ์‹ค์ œ 3D ์ขŒํ‘œ๊ฐ€ ๋ณ€๊ฒฝ๋จ
  • ์Šค์ผ€์ผ ํŒฉํ„ฐ: get_solve(scale=1.0)์˜ ์Šค์ผ€์ผ ๊ฐ’ ์กฐ์ • ํ•„์š”
  • ์ขŒํ‘œ๊ณ„ ๊ธฐ์ค€์ : ๋กœ๋ด‡ํŒ”์˜ ์ƒˆ๋กœ์šด ๊ธฐํ•˜ํ•™์  ์ค‘์‹ฌ์ ์— ๋งž์ถฐ ์ขŒํ‘œ๊ณ„ ์žฌ์ •์˜

์ˆ˜์ • ๋ฐฉ๋ฒ•:

  1. ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์—์„œ ArUco ๋งˆ์ปค๋“ค์˜ ์‹ค์ œ 3D ์œ„์น˜ ์ธก์ •
  2. obj_points_map์˜ ๋ชจ๋“  ์ขŒํ‘œ๊ฐ’ ์—…๋ฐ์ดํŠธ
  3. ํ•„์š”์‹œ scale ํŒŒ๋ผ๋ฏธํ„ฐ ์กฐ์ •

2. ๋กœ๋ด‡ํŒ” ๋ฌผ๋ฆฌ์  ํŒŒ๋ผ๋ฏธํ„ฐ

2.1 teleoprator.py์˜ ์ƒ์ˆ˜๊ฐ’๋“ค

ํ˜„์žฌ ์ƒํƒœ:

GRIPPER_MAX = 0.055  # ๊ทธ๋ฆฌํผ ์ตœ๋Œ€ ๊ฐœ๋ฐฉ ๊ฑฐ๋ฆฌ (๋ฏธํ„ฐ)
INITIAL_LIFT_DISTANCE = 0.8  # ์ดˆ๊ธฐ ๋ฆฌํ”„ํŠธ ๋†’์ด (๋ฏธํ„ฐ)
FAR_SEEING_HEAD_TILT = 0.26  # ์›๊ฑฐ๋ฆฌ ์‹œ์•ผ ํ—ค๋“œ ํ‹ธํŠธ (๋ผ๋””์•ˆ)

์ˆ˜์ • ํ•„์š” ์‚ฌํ•ญ:

  • GRIPPER_MAX: ์ƒˆ๋กœ์šด ๊ทธ๋ฆฌํผ์˜ ์ตœ๋Œ€ ๊ฐœ๋ฐฉ ๊ฑฐ๋ฆฌ
  • INITIAL_LIFT_DISTANCE: ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์˜ ์ ์ ˆํ•œ ์ž‘์—… ๋†’์ด
  • LIFT_DISTANCE_MIN/MAX: ๋ฆฌํ”„ํŠธ ๋ฒ”์œ„ ์กฐ์ •
  • LIFT_VEL_MAX: ๋ฆฌํ”„ํŠธ ์†๋„ ์กฐ์ •

2.2 ํ—ค๋“œ ํ‹ธํŠธ ๊ณ„์‚ฐ ํ•จ์ˆ˜

ํ˜„์žฌ ์ƒํƒœ:

def get_head_tilt(self, lift_distance):
    point0_lift = 0
    point0_tilt = 1.36
    point1_lift = 0.8
    point1_tilt = 1.06
    
    return point0_tilt + (point1_tilt - point0_tilt) * (lift_distance - point0_lift) / (point1_lift - point0_lift)

์ˆ˜์ • ํ•„์š” ์‚ฌํ•ญ:

  • ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์˜ ์ž‘์—… ๊ณต๊ฐ„์— ๋งž์ถฐ ํ—ค๋“œ ํ‹ธํŠธ ๊ฐ๋„ ์กฐ์ •
  • ์นด๋ฉ”๋ผ ์‹œ์•ผ๊ฐ๊ณผ ๋กœ๋ด‡ํŒ” ์ž‘์—… ์˜์—ญ์˜ ์ตœ์ ํ™”

3. ๋ฆฌ์…‹ ํฌ์ฆˆ ์„ค์ •

3.1 reset_arm() ํ•จ์ˆ˜์˜ ์ดˆ๊ธฐ ์ž์„ธ

ํ˜„์žฌ ์ƒํƒœ:

goal_pose = {
    "left": self.on_get_initial_eef_pose("left", [self.lift_distance, joint_bent, -joint_bent, 0, 0, 0]),
    "right": self.on_get_initial_eef_pose("right", [self.lift_distance, -joint_bent, joint_bent, 0, 0, 0]),
}

์ˆ˜์ • ํ•„์š” ์‚ฌํ•ญ:

  • ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์˜ ๊ธฐํ•˜ํ•™์— ๋งž๋Š” ์ดˆ๊ธฐ ์ž์„ธ ์„ค์ •
  • joint_bent ๊ฐ’ ์กฐ์ • (ํ˜„์žฌ ฯ€/4)
  • ์ขŒ์šฐ ํŒ”์˜ ๋Œ€์นญ์„ฑ ์œ ์ง€

4. ์›น ์ธํ„ฐํŽ˜์ด์Šค ๊ด€๋ จ

4.1 ๋น„๋””์˜ค ์ŠคํŠธ๋ฆผ ์„ค์ •

ํ˜„์žฌ ์ƒํƒœ (webserver.py):

image_height = 360
image_width = 640
frames_per_second = 30

์ˆ˜์ • ํ•„์š” ์‚ฌํ•ญ:

  • ์นด๋ฉ”๋ผ ํ•ด์ƒ๋„๊ฐ€ ๋กœ๋ด‡ํŒ” ํฌ๊ธฐ์— ์ ํ•ฉํ•œ์ง€ ํ™•์ธ
  • ํ•„์š”์‹œ ํ•ด์ƒ๋„ ์กฐ์ •

4.2 ArUco ๋งˆ์ปค ๊ฐ์ง€ ์„ค์ •

ํ˜„์žฌ ์ƒํƒœ:

min_aruco_thres=4  # ์ตœ์†Œ ๊ฐ์ง€ ๋งˆ์ปค ์ˆ˜

์ˆ˜์ • ํ•„์š” ์‚ฌํ•ญ:

  • ์ƒˆ๋กœ์šด ๋งˆ์ปค ๋ฐฐ์น˜์— ๋”ฐ๋ฅธ ์ตœ์  ๋งˆ์ปค ์ˆ˜ ์กฐ์ •
  • ๋งˆ์ปค ํฌ๊ธฐ์™€ ์นด๋ฉ”๋ผ ํ•ด์ƒ๋„์˜ ๊ด€๊ณ„ ๊ฒ€ํ† 

5. ํ•˜๋“œ์›จ์–ด ์ œ์–ด ๊ด€๋ จ

5.1 astra_controller ํŒจํ‚ค์ง€

์ˆ˜์ • ํ•„์š” ์‚ฌํ•ญ:

  • ๊ทธ๋ฆฌํผ ๊ธฐ์–ด ๋น„์œจ: GRIPPER_GEAR_R = 0.027 / 2
  • ๋ฆฌํ”„ํŠธ ๋ ˆ์ผ ํŒŒ๋ผ๋ฏธํ„ฐ: RAIL_MM_PER_REV = 75, RAIL_MAX_LENGTH_MM = 1190
  • ์Šคํ…Œํผ ๋ชจํ„ฐ ์„ค์ •: STEPPER_PULSE_PER_REV = 800

5.2 URDF ๋ฐ MoveIt ์„ค์ •

์ˆ˜์ • ํ•„์š” ์‚ฌํ•ญ:

  • astra_description ํŒจํ‚ค์ง€์˜ URDF ํŒŒ์ผ๋“ค
  • astra_moveit_config ํŒจํ‚ค์ง€์˜ MoveIt ์„ค์ •
  • ๋กœ๋ด‡ํŒ”์˜ ์ƒˆ๋กœ์šด ๊ธฐํ•˜ํ•™์  ํŒŒ๋ผ๋ฏธํ„ฐ ๋ฐ˜์˜

์ˆ˜์ • ์šฐ์„ ์ˆœ์œ„

1. ๋†’์€ ์šฐ์„ ์ˆœ์œ„ (ํ•„์ˆ˜)

  1. ArUco ๋งˆ์ปค ์ขŒํ‘œ๊ณ„ ์žฌ์ •์˜ (astra_teleop/process.py)
  2. ๊ทธ๋ฆฌํผ ์ตœ๋Œ€ ๊ฐœ๋ฐฉ ๊ฑฐ๋ฆฌ (GRIPPER_MAX)
  3. ์ดˆ๊ธฐ ๋ฆฌํ”„ํŠธ ๋†’์ด (INITIAL_LIFT_DISTANCE)
  4. URDF ํŒŒ์ผ ์—…๋ฐ์ดํŠธ

2. ์ค‘๊ฐ„ ์šฐ์„ ์ˆœ์œ„ (๊ถŒ์žฅ)

  1. ํ—ค๋“œ ํ‹ธํŠธ ๊ณ„์‚ฐ ํ•จ์ˆ˜ ์กฐ์ •
  2. ๋ฆฌํ”„ํŠธ ๋ฒ”์œ„ ๋ฐ ์†๋„ ์กฐ์ •
  3. ์ดˆ๊ธฐ ์ž์„ธ ์„ค์ • ์ตœ์ ํ™”
  4. ํ•˜๋“œ์›จ์–ด ์ œ์–ด ํŒŒ๋ผ๋ฏธํ„ฐ ์—…๋ฐ์ดํŠธ

3. ๋‚ฎ์€ ์šฐ์„ ์ˆœ์œ„ (์„ ํƒ)

  1. ๋น„๋””์˜ค ํ•ด์ƒ๋„ ์กฐ์ •
  2. ArUco ๊ฐ์ง€ ํŒŒ๋ผ๋ฏธํ„ฐ ์ตœ์ ํ™”
  3. UI ๊ฐœ์„  (ํ•„์š”์‹œ)

ํ…Œ์ŠคํŠธ ๋ฐฉ๋ฒ•

1. ArUco ๋งˆ์ปค ์‹œ์Šคํ…œ ํ…Œ์ŠคํŠธ

# astra_teleop/process.py์˜ get_solve ํ•จ์ˆ˜ ํ…Œ์ŠคํŠธ
from astra_teleop.process import get_solve
solve = get_solve(scale=1.0)
# ์‹ค์ œ ์นด๋ฉ”๋ผ๋กœ ๋งˆ์ปค ๊ฐ์ง€ ํ…Œ์ŠคํŠธ

2. ํ…”๋ ˆ์˜คํผ๋ ˆ์ด์…˜ ์‹œ์Šคํ…œ ํ…Œ์ŠคํŠธ

# teleoprator.py์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ๋“ค ํ…Œ์ŠคํŠธ
# - hand_cb: ๋งˆ์ปค ๊ธฐ๋ฐ˜ ์† ์ถ”์ 
# - pedal_cb: ๊ทธ๋ฆฌํผ ๋ฐ ๋ฆฌํ”„ํŠธ ์ œ์–ด
# - control_cb: ๋ชจ๋“œ ์ „ํ™˜

3. ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

  • ์›น ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ํ†ตํ•œ ์ „์ฒด ์‹œ์Šคํ…œ ํ…Œ์ŠคํŠธ
  • ์‹ค์ œ ๋กœ๋ด‡ํŒ”๊ณผ์˜ ์—ฐ๋™ ํ…Œ์ŠคํŠธ

์ฃผ์˜์‚ฌํ•ญ

  1. ์ขŒํ‘œ๊ณ„ ์ผ๊ด€์„ฑ: ๋ชจ๋“  ์ขŒํ‘œ๊ณ„๊ฐ€ ๋™์ผํ•œ ๊ธฐ์ค€์ ์„ ์‚ฌ์šฉํ•˜๋Š”์ง€ ํ™•์ธ
  2. ๋‹จ์œ„ ํ†ต์ผ: ๋ฏธํ„ฐ, ๋ผ๋””์•ˆ ๋“ฑ ๋‹จ์œ„์˜ ์ผ๊ด€์„ฑ ์œ ์ง€
  3. ์•ˆ์ „์„ฑ: ์ƒˆ๋กœ์šด ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ์•ˆ์ „ํ•œ ๋ฒ”์œ„ ๋‚ด์— ์žˆ๋Š”์ง€ ํ™•์ธ
  4. ๋ฐฑ์—…: ์ˆ˜์ • ์ „ ๊ธฐ์กด ์„ค์ •์˜ ๋ฐฑ์—… ํ•„์ˆ˜

๊ฒฐ๋ก 

๋กœ๋ด‡ํŒ”์˜ ํฌ๊ธฐ์™€ ํ˜•ํƒœ ๋ณ€๊ฒฝ ์‹œ, ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ ArUco ๋งˆ์ปค ์ขŒํ‘œ๊ณ„์˜ ์žฌ์ •์˜์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํ…”๋ ˆ์˜คํผ๋ ˆ์ด์…˜ ์‹œ์Šคํ…œ์˜ ํ•ต์‹ฌ์ธ ์† ์œ„์น˜ ์ถ”์ ์˜ ์ •ํ™•๋„์— ์ง์ ‘์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์นฉ๋‹ˆ๋‹ค. ๊ทธ ๋‹ค์Œ์œผ๋กœ๋Š” ๋กœ๋ด‡ํŒ”์˜ ๋ฌผ๋ฆฌ์  ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค(๊ทธ๋ฆฌํผ, ๋ฆฌํ”„ํŠธ ๋“ฑ)์„ ์ƒˆ๋กœ์šด ๊ธฐํ•˜ํ•™์— ๋งž๊ฒŒ ์กฐ์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

SCARA์—์„œ ํ˜‘๋™๋กœ๋ด‡์œผ๋กœ์˜ ์ „ํ™˜ ์‹œ์—๋Š” ์ถ”๊ฐ€์ ์œผ๋กœ ์•ˆ์ „ ์‹œ์Šคํ…œ ํ†ตํ•ฉ, ์†๋„ ์ œํ•œ ์กฐ์ •, ๊ทธ๋ฆฌ๊ณ  ํ˜‘๋™๋กœ๋ด‡ ํŠนํ™” ์ œ์–ด ๋ชจ๋“œ๋“ค์˜ ๊ตฌํ˜„์ด ํ•„์ˆ˜์ ์ž…๋‹ˆ๋‹ค.

์›๊ฒฉ์กฐ์ข… ์‹œ์Šคํ…œ ๊ด€์ ์—์„œ๋Š” 6์ถ• SCARA์—์„œ ์ž์œ 6์ถ• ๋กœ๋ด‡ํŒ”๋กœ์˜ ์ „ํ™˜์ด ์ถ”์ƒํ™”๋œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ ํ…”๋ ˆ์˜คํผ๋ ˆ์ด์…˜ ์ฝ”๋“œ ์ž์ฒด๋Š” ์ˆ˜์ •ํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ˆ˜์ •์„ ํ†ตํ•ด ์ƒˆ๋กœ์šด ๋กœ๋ด‡ํŒ”์—์„œ๋„ ์ •ํ™•ํ•˜๊ณ  ์•ˆ์ „ํ•œ ํ…”๋ ˆ์˜คํผ๋ ˆ์ด์…˜์ด ๊ฐ€๋Šฅํ•ด์ง‘๋‹ˆ๋‹ค.