DDS Low-Level Motion Reference Example
The low-level motion development example implements resetting the robot from any initial joint positions to ready position, then controlling the ankle and finger joints of robot using two different modes. The source code is located in pnd_sdk_python/example/low_level and can be accessed here. For running instructions, see Quick Development. The source directory is organized by robot model:
├── adam_lite
│ └── adam_lite_low_level_example.py
├── adam_sp
│ └── adam_sp_low_level_example.py
├── adam_pro
│ └── adam_pro_low_level_example.py
└── adam_u
└── adam_u_low_level_example.py
Code Analysis
The following code is for the Adam Pro version. Other versions follow a similar structure, with differences only in joint index definitions.
Core Types and Functions
| Type Name | Description |
|---|---|
| ADAMJointIndex | Index of all joints sorted by name, also used as indices in LowCmd.MotorCmd array |
| LowCmd | Motor command structure |
| LowState | Motor state structure |
| HandCmd | Hand command structure |
| Custom | Core control logic class |
| ChannelPublisher | DDS publisher |
| ChannelSubscriber | DDS subscriber |
The main program is defined in adam_pro_low_level_example.py and instantiates the Custom class. In the Custom constructor, two publishers and one subscriber are created along with corresponding DDS message structures:
- Defines the positions of 12 fingers when closed in
Custom.close_hand - Creates
rt/lowcmdtopic publisher for motor positions, andrt/handcmdtopic publisher for hand positions - Receives low-level state from
rt/lowstatetopic callback, handled byCustom.LowStateHandler
Example code:
class Custom:
def __init__(self):
self.time_ = 0.0
self.control_dt_ = 0.0025 # [2.5ms]
self.duration_ = 3.0 # [3 s]
self.counter_ = 0
self.low_cmd = pnd_adam_msg_dds__LowCmd_(ADAM_SP_NUM_MOTOR)
self.low_state = pnd_adam_msg_dds__LowState_(ADAM_SP_NUM_MOTOR)
self.hand_cmd = pnd_adam_msg_dds__HandCmd_()
self.close_hand = np.array([500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500, 500], dtype=int)
self.getstate_flag = False
def Init(self):
self.lowcmd_publisher_ = ChannelPublisher("rt/lowcmd", LowCmd_)
self.lowcmd_publisher_.Init()
self.hand_pub = ChannelPublisher("rt/handcmd", HandCmd_)
self.hand_pub.Init()
# create subscriber #
self.lowstate_subscriber = ChannelSubscriber("rt/lowstate", LowState_)
self.lowstate_subscriber.Init(self.LowStateHandler, 10)
Create Thread for Periodic Motor Command Publishing
After the program starts, a thread is launched to call Custom.LowCmdWrite at intervals defined by Custom.control_dt_:
def Start(self):
self.lowCmdWriteThreadPtr = RecurrentThread(
interval=self.control_dt_, target=self.LowCmdWrite, name="control"
)
self.lowCmdWriteThreadPtr.Start()
Low-Level State Callback Function
When the rt/lowstate topic is received, the Custom.LowStateHandler function is automatically called. Refer to DDS Message Definition. It extracts the motor states and implements:
- Recording the current joint positions for closed-loop control
- Printing IMU Euler angles periodically
def LowStateHandler(self, msg: LowState_):
self.low_state = msg
self.getstate_flag = True
self.counter_ +=1
if (self.counter_ % 500 == 0) :
self.counter_ = 0
print(self.low_state.imu_state.ypr)
Low-Level Command Sending Function
Custom.LowCmdWrite periodically sends low-level commands:
- Stage 1: Reset Adam robot from any initial joint positions to ready position (first 3 seconds)
- Stage 2: Swing ankle joints using PR control mode for 3 seconds
- Stage 3: Open fingers until program termination
def LowCmdWrite(self):
if(self.getstate_flag):
self.time_ += self.control_dt_
if self.time_ < self.duration_ :
for i in range(ADAM_PRO_NUM_MOTOR):
ratio = np.clip(self.time_ / self.duration_, 0.0, 1.0)
self.low_cmd.motor_cmd[i].mode = 1 # 1:Enable, 0:Disable
self.low_cmd.motor_cmd[i].tau = 0.
self.low_cmd.motor_cmd[i].q = (1.0 - ratio) * self.low_state.motor_state[i].q
self.low_cmd.motor_cmd[i].dq = 0.
self.low_cmd.motor_cmd[i].kp = Kp[i]
self.low_cmd.motor_cmd[i].kd = Kd[i]
for i in range(12):
self.hand_cmd.position[i] = self.close_hand[i]
elif self.time_ < self.duration_ * 2 :
max_P = np.pi * 30.0 / 180.0
max_R = np.pi * 10.0 / 180.0
t = self.time_ - self.duration_
L_P_des = max_P * np.sin(2.0 * np.pi * t)
L_R_des = max_R * np.sin(2.0 * np.pi * t)
R_P_des = max_P * np.sin(2.0 * np.pi * t)
R_R_des = -max_R * np.sin(2.0 * np.pi * t)
self.low_cmd.motor_cmd[ADAMJointIndex.LeftAnklePitch].q = L_P_des
self.low_cmd.motor_cmd[ADAMJointIndex.LeftAnkleRoll].q = L_R_des
self.low_cmd.motor_cmd[ADAMJointIndex.RightAnklePitch].q = R_P_des
self.low_cmd.motor_cmd[ADAMJointIndex.RightAnkleRoll].q = R_R_des
elif self.time_ < self.duration_ * 3 :
for i in range(ADAM_PRO_NUM_MOTOR):
self.low_cmd.motor_cmd[i].mode = 1 # 1:Enable, 0:Disable
self.low_cmd.motor_cmd[i].tau = 0.
self.low_cmd.motor_cmd[i].q = 0
self.low_cmd.motor_cmd[i].dq = 0.
self.low_cmd.motor_cmd[i].kp = Kp[i]
self.low_cmd.motor_cmd[i].kd = Kd[i]
for i in range(12):
self.hand_cmd.position[i] = self.open_hand[i]
self.hand_pub.Write(self.hand_cmd)
self.lowcmd_publisher_.Write(self.low_cmd)