
    j                        U d Z ddlZddlZddlZddlZddlZddlmZ ddl	m
Z
mZmZmZ e
rddlmZ ddlZddlmZ ddlZddlZddlZddlZddlZddlmZ ej                            dej                            ej                            e           dddd	                     dd
l!m"Z" ddl#m$Z$ ddl%m&Z& ddl'm(Z( ddl)m*Z+ ddl)m,Z, ddl-m.Z. ddlm/Z/ e"j0        Z1dDdZ2e2e"_0        g dZ3 ej4        g d          Z5 ej4        g d          Z6 ej4        g d          Z7 ej4        g d          Z8dZ9dZ: e; ee+          <                    d                    Z= e; ee+          j>        dz            Z? ej4        g dg dg dg d gej@        !          ZAejB        eCd"<    ej4        g d#g d$g dg d gej@        !          ZDejB        eCd%<   daEd& ZFd' ZGd(eHfd)ZI G d* d+          ZJ	 	 	 	 dEd.e(d/ejB        d0eejB                 d1eHd2eKd3eejL                 d4dfd5ZMd4eNe;         fd6ZOdFd8e;d9eKd4ePfd:ZQd8e;d4ePfd;ZR	 dGd=ejB        d>eHd?eHd@ePd4ejB        f
dAZS G dB dC          ZTdS )Hz6Modular robot controller for YAM bimanual robot system    N)Path)TYPE_CHECKINGDictOptionalTuple)	FootPedal)Rotationz..third_partyi2rt)CanInterface)get_yam_robot)MotorChainRobot)Robot)ARM_YAM_XML_PATH)GripperType)jparse_step)try_open_footpedalPCAN_USBBUS1c                 Z    t          | |g|R i | t          | d          s	|| _        d S d S )Nchannel)_orig_can_interface_inithasattrr   )selfr   argskwargss       >/home/robot-lab/cameron/raiden_fork/raiden/robot/controller.py_patched_can_interface_initr   +   sI    T7<T<<<V<<<4##      )r   YAMLeaderRobotRobotControllersmooth_move_jointsFOLLOWER_HOME_POSLEADER_HOME_POS)        r$   r$   r$   r$   r$         ?)r$   r$   r$   r$   r$   r$   )      T@r&   r&   g      D@      $@r'   g      4@)      @r(   r(         ?r)   r)         ?gH@?r%   z.urdfassets)r$   r%   r$   r$   )      r$   r$   r$   )r$   r$   r%   r$   )r$   r$   r$   r%   dtype_T_LINK6_TO_TCP)r$   r,   r$   r$   r%   r$   r$   r$   _T_TCP_TO_LINK6c                  T    t           t          j        t          d          a t           S )z8Return (and lazily create) the JIT-compiled jparse_step.Nmethodstatic_argnames)_jparse_step_jitjaxjitr    r   r   _get_jparse_step_jitr;   w   s%     7;LLLr   c                  Z    dd} t           j                            t          | dd          S )zALoad the YAM URDF via yourdfpy, resolving package:// asset paths.Nc                     t          | t                    r3|                     d          r|                     dt          dz             S | S )Nzpackage://assets//)
isinstancestr
startswithreplace_YAM_ASSETS_DIR)fnamedirs     r   _pkg_handlerz$_load_yam_urdf.<locals>._pkg_handler   sK    eS!! 	Me&6&67J&K&K 	M==!4o6KLLLr   FT)filename_handlerload_meshesload_collision_meshesN)yourdfpyURDFload_YAM_URDF_PATH)rF   s    r   _load_yam_urdfrO      sC       
 =%"	    r   dtc           
         t                      }t          j                            |          }t	          |j        j                                      d          }t          j	        t          d          }t          j        dt          j                  } ||||t          j        dt          j                  t          j        g dt          j                  d| |	          \  }}t          j        |           |||fS )
a  Load YAM robot with pyroki, JIT-compile jparse_step, and run warmup.

    Args:
        dt: Control loop period in seconds.

    Returns:
        (pk_robot, link6_idx, step_jit) where step_jit is the JIT-compiled
        IK step function ready for real-time calls.
    link_6r3   r5      r-      r0   jparserobotcfgtarget_link_indextarget_positiontarget_wxyzr4   rP   home_cfg)rO   pkr   	from_urdflistlinksnamesindexr8   r9   r   npzerosfloat64arrayblock_until_ready)rP   urdfpk_robot	link6_idxstep_jit_dummyresult_s           r   _setup_pyrokiro      s     Dx!!$''HX^)**00::Iw{K@@@H Xarz***F#"*555H111DDD	 	 	IFA &!!!Y((r   c                       e Zd ZdZdefdZdeej        ej        f         fdZ	de
dej        fdZdej        fdZd	ej        dd
fdZdej        dej        dd
fdZd
S )r   z-Wrapper for leader robot with teaching handlerW   c                 ,    || _         |j        | _        d S rJ   )_robotmotor_chain_motor_chain)r   rW   s     r   __init__zYAMLeaderRobot.__init__   s    !-r   returnc                    | j                                         d         }| j                                        }t	          j        d           d|d         j        z
  }t          j        ||gg          }||d         j	        fS )z1Get joint positions with gripper and button state	joint_pos{Gz?   r   )
rr   get_observationsrt   get_same_bus_device_statestimesleeppositionrc   concatenate	io_inputs)r   qposencoder_obsgripper_cmdqpos_with_grippers        r   get_infozYAMLeaderRobot.get_info   sx    {++--k:'BBDD
4+a.11ND;-+@AA +a.":::r   encoder_idxc                 N    | j                                         }||         j        S )z/Get io_inputs for the encoder at *encoder_idx*.)rt   r|   r   )r   r   r   s      r   get_encoder_iozYAMLeaderRobot.get_encoder_io   s$    'BBDD;'11r   c                 4    | j                                         S )zGet joint positions (6 DoF))rr   get_joint_posr   s    r   r   zYAMLeaderRobot.get_joint_pos   s    {((***r   rx   Nc                 `    |j         d         dk    sJ | j                            |           dS )z0Command joint positions (6 DoF only, no gripper)r   rS   N)shaperr   command_joint_pos)r   rx   s     r   r   z YAMLeaderRobot.command_joint_pos   s6    q!Q&&&&%%i00000r   kpkdc                 <    | j                             ||           dS )zUpdate PD gainsN)rr   update_kp_kd)r   r   r   s      r   r   zYAMLeaderRobot.update_kp_kd   s       R(((((r   )__name__
__module____qualname____doc__r   ru   r   rc   ndarrayr   intr   r   r   r   r:   r   r   r   r      s        77.o . . . .;%
BJ 67 ; ; ; ;2# 2"* 2 2 2 2
+rz + + + +12: 1$ 1 1 1 1
)rz )rz )d ) ) ) ) ) )r   r   r(      rW   target_joint_positionsstart_joint_positionstime_interval_ssteps
stop_eventrv   c                    |!t          j        |t           j                  }n|                                 }t	          |          t	          |          k    sJ t          |dz             D ]Z}||                                r dS ||z  }d|z
  |z  ||z  z   }	|                     |	           t          j	        ||z             [dS )a  Move the robot to target joint positions with smooth interpolation.

    Args:
        start_joint_positions: Starting configuration for the interpolation.
            Pass the previous *commanded* target (not the measured position) so
            the trajectory matches what was executed during data collection.
            Falls back to ``robot.get_joint_pos()`` when ``None`` (e.g. first step).
    Nr-   rz   )
rc   asarrayre   r   lenrangeis_setr   r}   r~   )
rW   r   r   r   r   r   current_posialpha
target_poss
             r   r!   r!      s      (j!6bjIII))++{s#9::::::519 , ,!j&7&7&9&9!FFE	%i;.9O1OO

+++
?U*++++, ,r   c                     t          j        g dddd          } g }| j                                        D ]}|                    d          }t          |          dk    r]|d                                                             d          d	         }|                    d
          r|                    |           |S )z1Return all CAN interface names visible to the OS.)ipz-olinkshowTFcapture_outputtextcheck:   rz   @r   can)	
subprocessrunstdout
splitlinessplitr   striprA   append)rm   
interfaceslinepartsnames        r   list_can_interfacesr      s    ^$$$	  F J((** ( (

3u::??8>>##))#..q1Du%% (!!$'''r   @B 	interfacebitratec                    t          j                    dk    rg ndg}|ddd| dgz   |ddd| ddd	d
t          |          g	z   fD ]}}t          j        |ddd          }|j        dk    rXt          dd                    |                      |j        r)t          d|j        	                                             dS ~dS )zrBring a CAN interface down then back up at the given bitrate.

    Returns True on success, False on failure.
    r   sudor   r   setdownuptyper   r   TFr   u     ✗  z    )
osgeteuidr@   r   r   
returncodeprintjoinstderrr   )r   r   r   r   rm   s        r   reset_can_interfacer     s    
 ""22DfeY77LL

	
   TERRR!!+388D>>++,,,} 64V]00224455555	 "
 4r   c                    	 t          j        ddd| gddd          }|j        dk    rdS d|j        v s	d	|j        v rdS t	          d
|  d           dS # t
          $ r }t	          d|  d|            Y d}~dS d}~ww xY w)z0Check if a CAN interface exists and is availabler   r   r   TFr   r   zstate UPzstate UNKNOWNzWarning: CAN interface z exists but is not UPzError checking CAN interface z: N)r   r   r   r   r   	Exception)r   rm   es      r   check_can_interfacer   $  s    669-	
 
 
 !!5&&/V]*J*J4LILLLMMM5   >i>>1>>???uuuuus!   'A A A 
A>A99A>F	T_current	vel_scale	rot_scaleinvert_rotationc                 \   | j         |z  }| j         |z  }| j        |z  }| j        |z  }| j        |z  }	| j         |z  }
|                                }|dddfxx         |||gz  cc<   t          j        d||	|
g          	                                |ddddf         z  |ddddf<   |S )u  Convert a SpaceMouse state into a target EE pose.

    Translation and rotation are both applied in the world (Cartesian) frame.
    Axis mapping (tuned in test_spacemouse_sim.py):

        SpaceMouse y  →  world +x
        SpaceMouse x  →  world −y
        SpaceMouse z  →  world +z
        roll/pitch/yaw → world-frame roll/pitch/yaw

    Args:
        state:            pyspacemouse state object (has .x .y .z .roll .pitch .yaw).
        T_current:        4×4 current EE pose in the arm base frame.
        vel_scale:        Max translational speed in m/s at full deflection.
        rot_scale:        Max rotational speed in rad/s at full deflection.
        invert_rotation:  If True, negate all rotation axes.

    Returns:
        4×4 target EE pose.
    NrT   xyz)
yxzrollpitchyawcopyr	   
from_euler	as_matrix)stater   r   r   r   dxdydzdrxdrydrzT_targets               r   spacemouse_to_target_poser   ;  s    : 
9	B
'I	B	9	B
*y
 C
+	
!C9*y
 C~~HRaRUOOOB|#OOOECc?33==??)BQBPRQRPRFBSS RaR!V Or   c                      e Zd ZdZ	 	 	 	 d?dedededefdZdefd	Zd@deddfdZdAdZdAdZ	dBdeddfdZ
deeeeej        f         f         fdZdeeeej                 f         fdZdeeej        f         fdZdee         fdZdee         fdZdefdZdefdZdAdZdAdZdCdeddfdZdAdZedefd            Zd efd!Zd" Zd# Z 	 	 dDd&ed'eddfd(Z!dEd*eddfd+Z"	 	 	 	 dFd.ed/ed*ed0eddf
d1Z#dAd2Z$	 d@d ed.ed/ed*ed0eddfd3Z%dAd4Z&	 	 	 dGd5ee         d6ed7ee'         ddfd8Z(d9 Z)dAd:Z*dAd;Z+d< Z,d= Z-dAd>Z.dS )Hr    z?Manages YAM robot initialization, state access, and terminationTuse_right_leaderuse_left_leaderuse_right_followeruse_left_followerc                 8   || _         || _        || _        || _        d| _        d| _        d| _        d| _        i | _        i | _	        i | _
        i | _        i | _        d| _        d| _        d| _        t!          j                    | _        d| _        ddd| _        dS )a  Initialize robot controller

        Args:
            use_right_leader: Initialize right leader arm
            use_left_leader: Initialize left leader arm
            use_right_follower: Initialize right follower arm
            use_left_follower: Initialize left follower arm
        Nr$   Frightleft)r   r   r   r   leader_rleader_l
follower_r
follower_llast_button_state_last_verdict_state_last_failure_button_statekp_gainskd_gains_pause_until_pause_timer_estop_enabled	threadingEvent_session_estop_event
_footpedal_last_commanded_pos)r   r   r   r   r   s        r   ru   zRobotController.__init__j  s     !1."4!2 3726+/+/ 4657 <>' 02/1 $'7; %* %.O$5$5! 26 E
 E
   r   rv   c                    g }| j         r|                    d           | j        r|                    d           | j        r|                    d           | j        r|                    d           g }|D ]&}t          |          s|                    |           '|r%t          dd                    |                     t          d           dS )	z.Check if required CAN interfaces are availablecan_follower_rcan_follower_lcan_leader_rcan_leader_lz'Missing or unavailable CAN interfaces: z, u-   ✓ All required CAN interfaces are availableT)	r   r   r   r   r   r   RuntimeErrorr   r   )r   required_interfacesmissing_interfacesr   s       r   check_can_interfacesz$RobotController.check_can_interfaces  s    " 	9&&'7888! 	9&&'7888  	7&&~666 	7&&~666, 	5 	5I&y11 5")))444 	Y$))DV:W:WYY   	=>>>tr   Fgravity_comp_modeNc                   	 t          d           i 	i dt          dt          ddf	fd}g }| j        r7|                    t	          j        |ddt          j        fd	
                     | j        r7|                    t	          j        |ddt          j        fd	
                     | j	        r7|                    t	          j        |ddt          j
        fd	
                     | j        r7|                    t	          j        |ddt          j
        fd	
                     |D ]}|                                 |D ]}|                                 rt          d           | j        ru	d         | _        | j                            t"          t$                     t"                                          | j        d<   t$                                          | j        d<   | j        ru	d         | _        | j                            t"          t$                     t"                                          | j        d<   t$                                          | j        d<   d}| j	        r	d         }||_        t1          |          | _        d| j        d<   d| j        d<   d| j        d<   d| j        d<   | j        j        j                                        | j        d<   | j        j        j                                        | j        d<   | j        r	d         }||_        t1          |          | _         d| j        d<   d| j        d<   d| j        d<   d| j        d<   | j         j        j                                        | j        d<   | j         j        j                                        | j        d<   t          d           t          dt"                      t          dt$                      |r| !                                 dS dS ) z|Initialize robots

        Args:
            gravity_comp_mode: If True, disable position control for free movement
        z
Initializing robots...r   r   rv   Nc                     t          d|  d           	 t          ||d          | <   d S # t          $ r}|| <   Y d }~d S d }~ww xY w)Nz  - Initializing ...F)r   gripper_typezero_gravity_mode)r   r   r   )r   r   r  excerrorsresultss       r   _initz0RobotController.initialize_robots.<locals>._init  s}    /d///000# -#!-&+! ! !
  # # #"t#s   - 
AAAzright followerr	  Ttargetr   daemonzleft followerr
  zright leaderr  zleft leaderr  zRobot initialization failed: r   r   r   r   g?r$   r   leader_r_topleader_r_bottomr   leader_l_topleader_l_bottomu   ✓ All robots initializedz  Follower kp: z  Follower kd: )"r   r@   r   r   r  Threadr   LINEAR_4310r   r   YAM_TEACHING_HANDLEr   startr   r  r   r   FOLLOWER_KPFOLLOWER_KDr   r   r   r   gravity_comp_factorr   r   r   r   r   rr   _kp_kdr   enable_gravity_compensation)
r   r  r  threadst_LEADER_GRAVITY_COMP_FACTORleader_r_baseleader_l_baser  r  s
           @@r   initialize_robotsz!RobotController.initialize_robots  sp    	())) &(')		# 		#c 		#D 		# 		# 		# 		# 		# 		# 		# " 	NN  *,<k>UV     ! 	NN  )+;[=TU       	NN  &&#7
    
 
 
  	NN  %&#7
    
 
 
  	 	AGGIIII 	 	AFFHHHH 	IGvGGHHH" 	=%&67DOO((KK(HHH*5*:*:*<*<DM,'*5*:*:*<*<DM,'! 	=%o6DOO((KK(HHH*5*:*:*<*<DM,'*5*:*:*<*<DM,' '*#  		H#N3M0KM-*=99DM14D":.7:D$^4:=D$%67:=D+J7(,(<(@(E(E(G(GDM*%(,(<(@(E(E(G(GDM*% 		H#M2M0KM-*=99DM14D":.7:D$^4:=D$%67:=D+J7(,(<(@(E(E(G(GDM*%(,(<(@(E(E(G(GDM*%*+++---...---...  	/,,.....	/ 	/r   c                 z   t          d           | j        r@| j                            t          j        d          t          j        d                     | j        r@| j                            t          j        d          t          j        d                     | j        r@| j                            t          j        d          t          j        d                     | j        r@| j                            t          j        d          t          j        d                     t          d           dS )zDEnable gravity compensation mode (free movement with zero stiffness)z)  - Enabling gravity compensation mode...rS   r     uC   ✓ Gravity compensation mode enabled (robots free to move by hand)N)r   r   r   rc   rd   r   r   r   r   s    r   r,  z+RobotController.enable_gravity_compensation4  s    9:::= 	GM&&"(1++"(1++&FFF= 	GM&&"(1++"(1++&FFF? 	IO((BHQKKBHQKK(HHH? 	IO((BHQKKBHQKK(HHHSTTTTTr   c                 R   t          d           | j        r;d| j        v r2| j                            | j        d         | j        d                    | j        r;d| j        v r2| j                            | j        d         | j        d                    | j        r;d| j        v r2| j                            | j        d         | j        d                    | j        r;d| j        v r2| j                            | j        d         | j        d                    t          d           dS )	z<Restore position control (disable gravity compensation mode)z!  - Restoring position control...r   r  r   r   r   u   ✓ Position control restoredN)r   r   r   r   r   r   r   r   r   s    r   disable_gravity_compensationz,RobotController.disable_gravity_compensationC  sN   1222= 	Z4=88M&&=,z1J '    = 	Z4=88M&&=,z1J '    ? 	|t}<<O((=.4=3N )    ? 	|t}<<O((=.4=3N )    	-.....r   simultaneousc                 H   t          d           |r6g }d }| j        r6|                    t          j        || j        t
          df                     | j        r6|                    t          j        || j        t
          df                     | j        r;|                    t          j        || j        j        t          df                     | j
        r;|                    t          j        || j
        j        t          df                     |D ]}|                                 |D ]}|                                 n| j        r)t          d           t          | j        t
                     | j        r)t          d	           t          | j        t
                     | j        r.t          d
           t          | j        j        t                     | j
        r.t          d           t          | j
        j        t                     t          d           dS )zMove all robots to home positions

        Args:
            simultaneous: If True, move all arms simultaneously using threads
        z%
Moving all arms to home positions...c                 x    t          d| d           t          | |dd           t          d| d           d S )Nz  - Moving r  g       @r   r   r     - z reached home)r   r!   )rW   r   r   s      r   move_to_homez<RobotController.move_to_home_positions.<locals>.move_to_homee  sT    -D---..."5*cQTUUUU0T00011111r   right_follower)r  r   left_followerright_leaderleft_leaderz  - Moving right follower...z  - Moving left follower...z  - Moving right leader...z  - Moving left leader...u   ✓ All arms at home positionsN)r   r   r   r  r#  r"   r   r   rr   r#   r   r&  r   r!   )r   r7  r-  r<  threads        r   move_to_home_positionsz&RobotController.move_to_home_positionsZ  sc    	6777 6	JG2 2 2
  $+"o/@BRS      $+"o/@/R     } $+"m2O^T     } $+"m2O]S     "  !    G4555"4?4EFFF G3444"4?4EFFF} J2333"4=#7III} J1222"4=#7III./////r   c                 6   i }| j         r!| j         j                                        |d<   | j        r!| j        j                                        |d<   | j        r| j                                        |d<   | j        r| j                                        |d<   |S )zGet full observations (joint_pos, joint_vel, joint_torque) from all robots.

        Returns:
            Dict mapping robot name to observation dict.
            Leaders return 6-DoF arrays; followers return 7-DoF (arm + gripper).
        r   r   r   r   )r   rr   r{   r   r   r   )r   obss     r   get_all_observationsz$RobotController.get_all_observations  s     13= 	F"m2CCEEC
O= 	F"m2CCEEC
O? 	C $ @ @ B BC? 	C $ @ @ B BC
r   c                     | j         d         | j         d                                         nd| j         d         | j         d                                         nddS )a7  Return the last 7-DOF position commanded to each follower arm.

        Returns a dict with keys ``"follower_r"`` and ``"follower_l"``, each
        mapping to a ``(7,)`` float32 array or ``None`` if no command has been
        issued yet (e.g. at the start of an episode before the first teleop step).
        r   Nr   )r   r   )r  r   r   s    r   get_last_commanded_positionsz,RobotController.get_last_commanded_positions  sm     '0< 27;@@BBB'/; 26:??AAA
 
 	
r   c                 "   i }| j         r| j                                         |d<   | j        r| j                                        |d<   | j        r| j                                        |d<   | j        r| j                                        |d<   |S )zGet current joint positions of all robots

        Returns:
            Dictionary mapping robot names to joint positions
        r   r   r   r   )r   r   r   r   r   )r   	positionss     r   get_joint_positionsz#RobotController.get_joint_positions  s     	? 	F&*o&C&C&E&EIl#? 	F&*o&C&C&E&EIl#= 	B$(M$?$?$A$AIj!= 	B$(M$?$?$A$AIj!r   c                 f   | j         rQ| j                                         \  }}|d         }|dk    r| j        d         dk     r|| j        d<   dS || j        d<   | j        rQ| j                                        \  }}|d         }|dk    r| j        d         dk     r|| j        d<   dS || j        d<   dS )zCheck if any leader button was pressed (rising edge detection)

        Returns:
            Name of leader that was pressed ("leader_r" or "leader_l"), or None
        r   r*   r   r   N)r   r   r   r   )r   rn   button_statecurrent_buttons       r   check_button_pressz"RobotController.check_button_press  s     = 		@"m4466OA|)!_N ##(>z(JS(P(P5C&z2!z1?D":.= 		@"m4466OA|)!_N ##(>z(JS(P(P5C&z2!z1?D":.tr   c                    d| j         fd| j        ffD ]\  }}|	 |                                \  }}t          |          dk    rt	          |d                   nd}t          |          dk    rt	          |d                   nd}| j                            | dd          }| j                            | dd          }|| j        | d<   || j        | d<   |d	k    r	|d	k     r d
S |d	k    r	|d	k     r dS # t          $ r Y w xY wdS )a&  Check if a verdict button was pressed on any leader arm.

        The top button    (io_inputs[0]) signals "success".
        The bottom button (io_inputs[1]) signals "failure".
        Uses rising-edge detection per button.

        Returns:
            "success", "failure", or None.
        r   r   Nr   r$   rz   _top_bottomr*   successfailure)r   r   r   r   floatr   getr   )	r   r   leaderrn   r   topbottomprev_topprev_bottoms	            r   check_verdict_buttonz$RobotController.check_verdict_button  sX    )$-8:t}:UV 	 	LD&~%009-0^^a-?-?eIaL)))S03I0B0By|,,,3774sKK"6::d;K;K;KSQQ:=(D7=C(D)9)9)9: 99C$99C<<K#$5$5$99   ts   CC<,C<<
D	D	c                 F   d| j         fd| j        ffD ]\  }}|	 |                    d          }t          |          dk    rt	          |d                   nd}| j                            |d          }|| j        |<   |dk    r	|dk     r d	S ~# t          $ r Y w xY wd
S )zCheck if the failure button (encoder_obs[0].io_inputs[1]) was pressed.

        Returns True on the rising edge so the caller can immediately stop
        recording and mark the demonstration as failure.
        r   r   Nr   )r   rz   r$   r*   TF)r   r   r   r   rT  r   rU  r   )r   r   rV  r   currentprevs         r   check_failure_buttonz$RobotController.check_failure_button  s     )$-8:t}:UV 	 	LD&~"11a1@@	14Y!1C1C%	!---6::4EE8?/5S==TCZZ44   us   A1B
BBc                 J    | j         dup| j        dup| j        dup| j        duS )zCheck if any robots are initialized

        Returns:
            True if at least one robot is initialized, False otherwise
        N)r   r   r   r   r   s    r   
has_robotszRobotController.has_robots&  sB     O4' )d*)}D() }D(		
r   c                     d| _         dS )zIAllow the footpedal to trigger soft_pause().  Call when recording starts.TN)r  r   s    r   enable_estopzRobotController.enable_estop3  s    "r   c                 f    d| _         | j        "| j                                         d| _        dS dS )zJPrevent footpedal from triggering soft_pause().  Call when recording ends.FN)r  r  cancelr   s    r   disable_estopzRobotController.disable_estop7  s>    #($$&&& $D )(r   r(   durationc                 >   | j         sdS t          j                    |z   | _        t	          d|dd           | j        | j                                         t          j        || j	                  | _        d| j        _
        | j                                         dS )aq  Freeze all arms at their current positions for *duration* seconds,
        then call return_to_home().

        Safe to call from any thread (e.g. the footpedal callback thread).
        Pressing the pedal again while paused cancels the running timer and
        resets the countdown.

        No-op when recording is not active (i.e. _estop_enabled is False).
        Nu3   
  [FootPedal] Soft pause — holding all arms for z.0fzs, then returning to homeT)r  r}   	monotonicr   r   r  re  r  Timer_on_pause_expiredr  r&  )r   rg  s     r   
soft_pausezRobotController.soft_pause>  s     " 	F N,,x7%8T % % %	
 	
 	

 ($$&&&%OHd6LMM#' !!!!!r   c                 d    d| _         t          d           | j                                         dS )u  Called by the soft-pause timer thread after the hold duration elapses.

        Only signals the main thread to exit — robot operations (return_to_home,
        close) are intentionally left to the main thread to avoid racing on the
        CAN bus with check_button_press().
        Nu9   
  [FootPedal] Hold complete — signalling session stop.)r  r   r  r   r   s    r   rk  z!RobotController._on_pause_expiredV  s6     !JKKK!%%'''''r   c                 4    | j                                         S )zTrue after the footpedal has triggered a soft-pause and the arms
        have returned home.  Signals the recording loop to exit cleanly.)r  r   r   s    r   session_estop_requestedz'RobotController.session_estop_requesteda  s     (//111r   sidec                 x   |dk    r| j         }| j        }d}n| j        }| j        }d}|r|sdS d}d}d}|                                d         }| j                                        s	 t          j                    | j	        k     }	|	r|s`|                                }|                                }|| j
        v r-|                    | j
        |         | j        |                    d}|                    |           |                    |           t          j        d	           | j                                        rdS |                                \  }
}|                    |
dd                    t#          t%          j        |
d         d
d                    }t%          j        |
dd         |          }|                    |           || j        |<   t          j        d	           n-# t,          $ r }t/          d| d|            Y d}~dS d}~ww xY w| j                                        dS dS )z+Run teleoperation control loop for one sider   r   r   NFrS   r  Try   r$   r%   z  - Error in z teleoperation: )r   r   r   r   r   _teleop_shutdownr   r}   ri  r   r   r   r   r   r~   r  r   rT  rc   clipr   r  r   r   )r   rp  rV  followerleader_namehold_leader_poshold_follower_pos
was_pausedfollower_gripper_cmd
now_paused
leader_posrn   follower_cmdr   s                 r   _teleoperation_control_loopz+RobotController._teleoperation_control_loopg  s   7??]FH$KK]FH$K 	X 	F0426
'5577:'..00 )	(!^--0AA
 % 
**0*>*>*@*@,4,B,B,D,D)&$-77"//#'=#=#'=#= 0    &*
,,_===../@AAAJt$$$ ,3355 E !' 1 1
A((BQB888 (-RWZ]C-M-M'N'N$!yBQB9MNN**<8881=(.
4       ?d??A??@@@O '..00 )	 )	 )	 )	 )	s&   )B>G1 (G1 B-G1 1
H;HHc                     t          j                    | _        g | _        | j        rR| j        rKt          j        | j        ddd          }|                                 | j        	                    |           | j
        rR| j        rKt          j        | j        ddd          }|                                 | j        	                    |           | j        r%t          j        d           t          d           d	S d	S )
z=Start teleoperation control loops (followers follow leaders).r   zteleop-rightTr  r   r   r  r   zteleop-left皙?u8   ✓ Teleoperation active (followers will follow leaders)N)r  r  rr  _teleop_threadsr   r   r#  r}  r&  r   r   r   r}   r~   r   r   rA  s     r   start_teleoperationz#RobotController.start_teleoperation  s    !* 1 1!= 	0T_ 	0%7#	  F LLNNN ''///= 	0T_ 	0%7"	  F LLNNN ''/// 	NJsOOOLMMMMM	N 	Nr   c                    t          | d          rb| j                                         t          | d          r9| j        D ]}|                    d           | j                                         |                                  dS )zFStop all teleoperation control loops (leader-follower and SpaceMouse).rr  r  r%   timeoutN)r   rr  r   r  r   clearstop_spacemouse_teleopr  s     r   stop_teleoperationz"RobotController.stop_teleoperation  s    4+,, 	-!%%'''t.// -"2 - -FKKK,,,,$**,,,##%%%%%r   /dev/hidraw4/dev/hidraw5path_rpath_lc                     t          d           | j        ,t          j        |          | _        t          d| d           | j        .t          j        |          | _        t          d| d           dS dS )a  Open two SpaceMice for Cartesian velocity teleop.

        Uses ``open_by_path`` so each handle unambiguously refers to one
        physical device regardless of how many HID interfaces each exposes.
        Run ``uv run scripts/test_spacemouse_read.py --list`` to see the
        hidraw paths for your connected devices.

        Args:
            path_r: hidraw path for the right-arm SpaceMouse (e.g. /dev/hidraw4).
            path_l: hidraw path for the left-arm SpaceMouse (e.g. /dev/hidraw5).
        zOpening SpaceMice...Nu     ✓ SpaceMouse R ()u     ✓ SpaceMouse L ()r   r   pyspacemouseopen_by_path_spacemouse_rr   _spacemouse_l)r   r  r  s      r   attach_spacemicez RobotController.attach_spacemice  s      	$%%%?&!-!:6!B!BD2222333?&!-!:6!B!BD222233333 '&r   ry   rP   c                     i  _         i t          j                    dt          ddf fd}g } j        *|                    t          j        |dd                      j        *|                    t          j        |dd                     |D ]}|                                 |D ]}|	                                 rt          d	           dS )
a  Pre-warm the pyroki/J-PARSE JIT for both SpaceMouse arms.

        Runs ``_setup_pyroki`` for each arm in parallel background threads and
        blocks until both finish.  Call this *before* showing the READY prompt
        so that no JIT compilation happens after the user starts recording.

        Stores results in ``self._prewarmed_ik`` keyed by side ("right"/"left").
        rp  rv   Nc                     	 t                    }5  |j        | <   d d d            d S # 1 swxY w Y   d S # t          $ r0}5  || <   d d d            n# 1 swxY w Y   Y d }~d S Y d }~d S d }~ww xY wrJ   )ro   _prewarmed_ikr   )rp  rm   r  rP   r  lockr   s      r   _warmz3RobotController.warmup_spacemouse_ik.<locals>._warm  sK   '&r** 6 6/5D&t,6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 ' ' ' ' '#&F4L' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ' ''sQ   9 ,9 09 09 
A3A.AA.A	A.A	A..A3r  Tr  r  zSpaceMouse IK warmup failed: )r  r  Lockr@   r   r   r#  r   r&  r   r  )r   rP   r  r-  r.  r  r  s   ``   @@r   warmup_spacemouse_ikz$RobotController.warmup_spacemouse_ik  s&    $&~	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' ?&NN9+5zRVWWWXXX?&NN9+5yQUVVVWWW 	 	AGGIIII 	 	AFFHHHH 	IGvGGHHH	I 	Ir   皙?r*   r   r   r   c           
      *    t           d          st           d          st          d          t          j                     _        g  _        ddd _        t          j                     _        dt           dd          fdt           dd          ffD ]Y\  }}|||fd fd	}t          j
        |d
| d          }|                                  j                            |           Zdt           dd           j        fdt           dd           j        ffD ]_\  }}}	|	|t          j
         j        ||||||fd| d          }|                                  j                            |           `t!          d           dS )uk  Start SpaceMouse Cartesian velocity teleop threads.

        SpaceMouse axis values (−1 to 1) are treated as velocities: each step
        the EE pose is advanced by ``axis_value * scale * dt`` in the current
        EE frame, then differential IK maps the new target pose to joint angles.

        Args:
            vel_scale:        Max translational speed in m/s at full deflection.
            rot_scale:        Max rotational speed in rad/s at full deflection.
            dt:               Control loop period in seconds (default 50 Hz).
            invert_rotation:  If True, negate all rotation axes.
        r  r  z9Call attach_spacemice() before start_spacemouse_teleop().Nr   r   r   rv   c                     j                                         sX|                                }j        5  |j        | <   d d d            n# 1 swxY w Y   j                                         Vd S d S rJ   )_spacemouse_shutdownr   read_spacemouse_state_lock_spacemouse_states)sdr   r   s      r   _readerz8RobotController.start_spacemouse_teleop.<locals>._reader3  s    3::<< ;FFHHE4 ; ;5:/2; ; ; ; ; ; ; ; ; ; ; ; ; ; ; 3::<< ; ; ; ; ;s   AAAzspacemouse-reader-T)r  r   r  zspacemouse-r  u   ✓ SpaceMouse teleop activerv   N)r   r  r  r  r  _spacemouse_threadsr  r  r  getattrr#  r&  r   r   r   _spacemouse_control_loopr   )
r   r   r   rP   r   rp  devicer  r.  rt  s
   `         r   start_spacemouse_teleopz'RobotController.start_spacemouse_teleop  s   & t_-- 	gdO6T6T 	K   %.O$5$5!)+ 26(E(E&/n&6&6# gdOT::;WT?D99:
 	/ 	/LD& ~ & ; ; ; ; ; ; ;  %@$%@%@  A GGIII$++A.... gdOT::DOLWT?D994?K'
 	/ 	/"D&( 6> 4FIy"oN)4))	  A GGIII$++A....,-----r   c                     t          | d          rd| j                                         t          | d          r=| j        D ]}|                    d           | j                                         dS dS dS )zStop SpaceMouse teleop threads.r  r  r%   r  N)r   r  r   r  r   r  )r   r.  s     r   r  z&RobotController.stop_spacemouse_teleopP  s    4/00 	1%))+++t233 11 ( (AFF3F''''(..00000	1 	11 1r   c                 \   |dk    r| j         n| j        }t          | di                               |          }||\  n=t	          d| dd           t                    \  t	          d| dd           t          j        d	t          j        
          dt          j	        dt          j	        ffd}	dt          j	        dt          j	        dt          j	        ffd}
d}d}|
                                }|dd	         ddd                                         }|d	         }| j                                        s	 t          j                    }t          j                    | j        k     }|rT|s(|
                                                                }d}|                    |           t          j                   | j                                        rdS |rA|
                                }|dd	         ddd                                         }|d	         }d}| j        5  | j                            |          }ddd           n# 1 swxY w Y   |t          j                   Q|
                                d	         } |	|          }t-          |||||          } |
||          }|}t          |dg           }t/          |          dk    r$|d         rt1          d|dt2          z  z
            }n6t/          |          dk    r#|d         rt5          d|dt2          z  z             }t          j        |ddd         |          }|                    |           |                                | j        |<   t          j                    |z
  }|z
  }|dk    rt          j        |           n@# t:          $ r3}t	          d| d|            t          j                   Y d}~nd}~ww xY w| j                                        dS dS )u  Velocity integration + J-PARSE IK + command loop for one arm.

        Delegates axis mapping and target pose computation to
        ``spacemouse_to_target_pose()`` (world-frame translation, EE-frame rotation).

        Uses pyroki + J-PARSE for singularity-aware velocity IK. The JIT-compiled
        step function is warmed up here before the loop starts so the first command
        is not delayed by JAX tracing.

        Gripper: Button 0 → close (0.0), Button 1 → open (1.0).
        r   r  Nz  [SpaceMouse z'] Setting up J-PARSE IK (JIT warmup)...T)flushz] IK ready.rS   r-   qrv   c                                          t          j        |                     }t          j        t          j        |                                                             }|t          z  S )uB   FK of the grasp_site: link_6 pose × fixed offset → 4×4 matrix.)	forward_kinematicsjnpr   rc   rf   jaxlieSE3r   r/   )r  posesT_link6rj   ri   s      r   _fk_tcpz9RobotController._spacemouse_control_loop.<locals>._fk_tcp|  sS    //A??Ehvz%	*:;;EEGGHHG_,,r   T_target_tcpc           
         |t           z  }|dddf         }t          j        |ddddf                                                   }t	          j        |d         |d         |d         |d         g          } |                     t          j                  
||d	          \  }}t	          j        |          S )u9   One J-PARSE step: grasp_site target → new joint config.NrT   r   rz   r   rU   rV   )	r1   r	   from_matrixas_quatrc   rf   astypere   r   )r  r  T_target_link6r   xyzwr[   q_newrn   	_home_cfgrP   rj   ri   rk   s           r   _ik_stepz:RobotController._spacemouse_control_loop.<locals>._ik_step  s     *O;N'A.J'rr2A2v(>??GGIID(DGT!Wd1gtAw#GHHKxHHRZ(("+ *'"	 	 	HE1 :e$$$r   Fbuttonsr   r$   g?rz   r%   z  SpaceMouse z loop error: )r   r   r  rU  r   ro   rc   rd   re   r   r   r   r  r   r}   ri  r   r   r~   r  r  r  r   r   max_GRIPPER_SAFETY_THRESHOLDminr   r  r   ) r   rp  r  r   r   rP   r   rt  	prewarmedr  r  hold_posrx  q_full_initq_armgripper
loop_startrz  q_full_resyncr   gripper_actualr   r   r  cmdelapsed	remainingr   r  rj   ri   rk   s         `                      @@@@r   r  z(RobotController._spacemouse_control_loopY  s   ( '+goo4??4?D/266::4@@	 ,5)HiNNNN    -:",=,=)Hi44444DAAAAHQbj111		-rz 	-bj 	- 	- 	- 	- 	- 	- 	-	%
 	%"* 	% 	% 	% 	% 	% 	% 	% 	% 	% 	% 	%& *.
 ,,..BQB"%**,,a.+2244 F	E!^--
 "^--0AA
 % *#+#9#9#;#;#@#@#B#B%)
..x888JrNNN,3355 E /$,$:$:$<$<M)"1"-ddd388::E+A.G"
 0 > > 377==E> > > > > > > > > > > > > > >=JrNNN "*!7!7!9!9!!<#GENN	49iO 
 !11 )!%B77w<<!##
#!^c4M.MM GG \\A%%'!*%!^c4M.MM G idddW55**3///14(. .**Z7L	q==Jy)))   <d<<<<===
2I +2244 F	 F	 F	 F	 F	sQ   BO O 1AO =I$O $I((O +I(,O EO 
P)PPc                    t          d           t                                          dd<   dt          ddffd}g }| j        r0|                    t          j        || j        fd	                     | j        r0|                    t          j        || j        fd	                     |D ]}|	                                 |D ]}|
                                 dS )
zFClose then re-open both follower grippers once to signal system ready.z5  - Signalling ready: closing and opening grippers...r$   rS   rW   rv   Nc                     t          d          D ]-}t          | dd           t          | t          dd           .d S )Nr   r  
   r:  )r   r!   r"   )rW   rn   
closed_poss     r   cyclez9RobotController.signal_ready_with_grippers.<locals>.cycle  s`    1XX  "5*cQSTTTT",c     r   Tr  )r   r"   r   r   r   r   r  r#  r   r&  r   )r   r  r-  r.  r  s       @r   signal_ready_with_grippersz*RobotController.signal_ready_with_grippers  s   EFFF&++--

1	 	4 	 	 	 	 	 	 ? 	NN T_4FtTTT   ? 	NN T_4FtTTT    	 	AGGIIII 	 	AFFHHHH	 	r   device_pathdirect_estopcallbackc                 D    ddl m t          |           _         j        { j                            fd           n?|r j                             fd           n j                             fd            j                                         dS dS )u  Attach a footpedal for soft e-stop.  Optional — warns and continues if absent.

        Only the LEFT pedal triggers the e-stop action.  The middle and right
        pedals are reserved for success/failure verdict marking in recorder.py
        and must not trigger a robot e-stop.

        Args:
            device_path: explicit /dev/input/eventN.  Auto-detected if None.
            direct_estop: if True, pressing the left pedal calls
                ``emergency_stop()`` immediately (skips the 5-second
                ``soft_pause`` timer).  Use this in contexts that have no
                teleoperation loop (e.g. the policy server).
            callback: if provided, called (with no arguments) when the left
                pedal is pressed, overriding both ``direct_estop`` and
                ``soft_pause`` behaviour.  Use this to inject pre-estop
                bookkeeping (e.g. setting a server-level flag) before the
                robot stop sequence runs.
        r   )
PEDAL_LEFTNc                 (    | k    r
             nd S rJ   r:   )coder  r  s    r   <lambda>z2RobotController.attach_footpedal.<locals>.<lambda>&  s    tz/A/At r   c                 <    | k    r                                 nd S rJ   )emergency_stopr  r  r   s    r   r  z2RobotController.attach_footpedal.<locals>.<lambda>*  s#    $*:L:L!4!4!6!6!6RV r   c                 @    | k    r                     d          nd S )Nr(   )rg  )rl  r  s    r   r  z2RobotController.attach_footpedal.<locals>.<lambda>.  s)    9=9K9K555QU r   )raiden.robot.footpedalr  r   r  on_pressr&  )r   r  r  r  r  s   `  `@r   attach_footpedalz RobotController.attach_footpedal  s    0 	655555,[99?&#((KKKKK     	((VVVVV    ((      
 O!!##### '&r   c                 j   t          d           |                                  |                     d           |                     d           t          d           | j        r@| j                            t          j        d          t          j        d                     | j        r@| j                            t          j        d          t          j        d                     t          d	           | 	                                 t          d
           | 
                                 dS )zKComplete setup for teleoperation-based recording: init -> home -> grav compzChecking CAN interfaces...F)r  Tr7  zJ  - Disabling position control on leaders for gravity compensation mode...rS   r  u(   ✓ Leaders in gravity compensation modezInitializing footpedal...N)r   r  r2  rB  r   r   rc   rd   r   r  r  r   s    r   setup_for_teleop_recordingz*RobotController.setup_for_teleop_recording4  s(    	*+++!!### 	777 	###666 	X	
 	
 	
 = 	GM&&"(1++"(1++&FFF= 	GM&&"(1++"(1++&FFF8999 	''))) 	)***r   c                    | j          | j                                          d| _         | j         | j                                         d| _        | j        r| j                                         | j        r| j                                         | j        r| j        j                                         | j        r | j        j                                         dS dS )zMClose all robot instances, stopping their server threads and CAN connections.N)	r  re  r  closer   r   r   rr   r   r   s    r   r  zRobotController.closeQ  s    ($$&&& $D?&O!!###"DO? 	$O!!###? 	$O!!###= 	)M &&(((= 	)M &&(((((	) 	)r   c                 <   d| _         | j                                         t          d           |                                  t          d           |                                  |                     d           t          d           | j        r@| j                            t          j
        d          t          j
        d                     | j        rB| j                            t          j
        d          t          j
        d                     d	S d	S )
u   Stop teleop, move to home, re-enable leader gravity comp.

        Does NOT close robot connections — safe to call between recording
        episodes when the session should continue.
        Fz
Stopping teleoperation...z&
Moving arms back to home positions...Tr  u   ✓ All arms returned to homerS   r  N)r  r  r  r   r  r6  rB  r   r   rc   rd   r   r   s    r   return_to_homezRobotController.return_to_homeb  s	    $!'')))+,,,!!!7888))+++###666-... = 	GM&&"(1++"(1++&FFF= 	GM&&"(1++"(1++&FFFFF	G 	Gr   c                     t          j        d           |                                  |                                  t          j        d           dS )zLComplete shutdown: stop teleop -> restore control -> go home -> close robotsr%   N)r}   r~   r  r  r   s    r   shutdownzRobotController.shutdowny  s?    
3


3r   c           	      z   t          d           t          d           t          d           t          d           |                                  t          d           t          d           |                                 }|                                D ]#\  }}t          d| d|d	d
          d           $t          d           t	          j                    }d}t	          j                    |z
  dk     r,| j        r$d|v r | j                            |d                    | j        r$d|v r | j                            |d                    | j        r)d|v r%| j        j	                            |d                    | j
        r)d|v r%| j
        j	                            |d                    dt          t	          j                    |z
            z
  }||k     r|dk    rt          d| d           |}t	          j        d           t	          j                    |z
  dk     ,t          d           |                                  |                     d           d	| _        |                                  t          d           t          d           t          d           t#          j        d           d	S )zDEmergency stop: hold current positions for 5s, then move all to homez=
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!z  EMERGENCY STOP ACTIVATEDz<!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!z,
  Step 1: Stopping teleoperation threads...z  Teleoperation stopped.z5
  Step 2: Recording current positions of all arms...z  - Recorded z position: NrT   r  zA
  Step 3: Holding all arms at current positions for 5 seconds...   r(   r   r   r   r   r   r;  ry   z>
  Step 4: Moving all arms to home positions simultaneously...Tr  z"
  All arms reached home position.z  System will now shut down.z=!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
)r   r  rJ  itemsr}   r   r   r   r   rr   r   r   r~   r6  rB  r  r  r   _exit)r   hold_positionsr   pos
start_timecountdown_printedr  s          r   r  zRobotController.emergency_stop  s   o*+++h 	=>>>!!!())) 	FGGG1133'--// 	A 	AID#?$??3rr7???@@@@ 	RSSSY[[
ikkJ&,, P<>#A#A11.2NOOO P<>#A#A11.2NOOO} S~!=!=$66~j7QRRR} S~!=!=$66~j7QRRR C	j 8999I,,,Q+Y+++,,,$-!Jt# ikkJ&,,( 	OPPP))+++###666
 

3444,---o
r   c                 ~    |                                   |                                  t          j        d           dS )z0Cleanup resources and stop robot control threadsr   N)r  r  r   r  r   s    r   cleanupzRobotController.cleanup  s2    !!!


r   )TTTTFr  )T)r(   )r  r  )ry   )r  r*   ry   F)NFN)/r   r   r   r   boolru   r  r2  r,  r6  rB  r   r@   rc   r   rE  r   rG  rJ  rN  r[  r_  ra  rc  rf  rT  rl  rk  propertyro  r}  r  r  r  r  r  r  r  r  callabler  r  r  r  r  r  r  r:   r   r   r    r    g  s       II "& $#'"&9
 9
9
 9
 !	9

  9
 9
 9
 9
vd    4s/ s/4 s/D s/ s/ s/ s/jU U U U/ / / /.@0 @04 @04 @0 @0 @0 @0Dd3S"*_0E+E&F    (
d38L3L.M 
 
 
 
 T#rz/%:    &HSM    <hsm    Bd    (
D 
 
 
 
# # # #% % % %" "5 "4 " " " "0	( 	( 	( 	( 2 2 2 2 X2
< < < < <|N N N<& & &  %$4 44 4 
	4 4 4 40"I "Iu "I "I "I "I "IL   %A. A.A. A. 	A.
 A. 
A. A. A. A.F1 1 1 1  !&P PP 	P
 P P P 
P P P Pd   > &*"'+	*$ *$c]*$ *$ 8$	*$
 
*$ *$ *$ *$X     :) ) ) )"G G G G.  7 7 7r     r   r    )r   )Nr(   r   N)r   r  )Ur   r   r   sysr  r}   pathlibr   _Pathtypingr   r   r   r   r  r   r8   	jax.numpynumpyr  r  rc   pyrokir]   r  rK   scipy.spatial.transformr	   pathinsertr   dirname__file__ i2rt.motor_drivers.can_interfacer   i2rt.robots.get_robotr   i2rt.robots.motor_chain_robotr   i2rt.robots.robotr   i2rt.robots.utilsr   _ARM_YAM_XML_PATHr   raiden.robot._jparser   r   ru   r   r   __all__rf   r"   r#   r'  r(  r  _GRIPPER_SPEEDr@   with_suffixrN   parentrC   re   r/   r   __annotations__r1   r7   r;   rO   rT  ro   r   r   r  r!   r_   r   r  r   r   r   r    r:   r   r   <module>r     s_   < < < 				     



      ! ! ! ! ! ! 7 7 7 7 7 7 7 7 7 7 7 7 1000000 



                     , , , , , , rw||BGOOH--tT=&QQ   : 9 9 9 9 9 / / / / / / 9 9 9 9 9 9 # # # # # # C C C C C C ) ) ) ) ) ) , , , , , , 5 5 5 5 5 5 (0     4    BH@@@AA "(999:: bhAAABBbh:::;; '   UU,--99'BBCC#ee-..5@AA 'bh	 *      'bh	 *            )e ) ) ) )D )  )  )  )  )  )  )  )L 37 ,0, ,,J, $BJ/, 	,
 , ), 
, , , ,>T#Y    & 3  4    <3 4    8 ") )z) ) 	)
 ) Z) ) ) )XV V V V V V V V V Vr   