Modified docs and communication interfaces to reflect change from A to Nm for motor control input.

Renamed vel_integrator_current_ to vel_integrator_torque_
Removed input_current_ from controller, added input_torque_
This commit is contained in:
pjohnson
2020-06-16 17:10:36 -04:00
parent e2527ec468
commit d78119e29f
15 changed files with 73 additions and 76 deletions
+4 -4
View File
@@ -306,7 +306,7 @@ bool Axis::run_closed_loop_control_loop() {
controller_.input_pos_ = *controller_.pos_estimate_src_;
// Avoid integrator windup issues
controller_.vel_integrator_current_ = 0.0f;
controller_.vel_integrator_torque_ = 0.0f;
set_step_dir_active(config_.enable_step_dir);
run_control_loop([this](){
@@ -344,7 +344,7 @@ bool Axis::run_homing() {
controller_.input_pos_ = 0.0f;
controller_.input_pos_updated();
controller_.input_vel_ = -controller_.config_.homing_speed;
controller_.input_current_ = 0.0f;
controller_.input_torque_ = 0.0f;
homing_.is_homed = false;
@@ -356,7 +356,7 @@ bool Axis::run_homing() {
controller_.pos_setpoint_ = *controller_.pos_estimate_src_;
// Avoid integrator windup issues
controller_.vel_integrator_current_ = 0.0f;
controller_.vel_integrator_torque_ = 0.0f;
run_control_loop([this](){
// Note that all estimators are updated in the loop prefix in run_control_loop
@@ -385,7 +385,7 @@ bool Axis::run_homing() {
controller_.input_pos_ = 0.0f;
controller_.input_pos_updated();
controller_.input_vel_ = 0.0f;
controller_.input_current_ = 0.0f;
controller_.input_torque_ = 0.0f;
run_control_loop([this](){
// Note that all estimators are updated in the loop prefix in run_control_loop
+9 -10
View File
@@ -13,7 +13,7 @@ Controller::Controller(Config_t& config) :
void Controller::reset() {
pos_setpoint_ = 0.0f;
vel_setpoint_ = 0.0f;
vel_integrator_current_ = 0.0f;
vel_integrator_torque_ = 0.0f;
torque_setpoint_ = 0.0f;
}
@@ -87,13 +87,13 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate)
float pos_err = input_pos_ - pos_estimate;
if (std::abs(pos_err) <= config_.anticogging.calib_pos_threshold &&
std::abs(vel_estimate) < config_.anticogging.calib_vel_threshold) {
config_.anticogging.cogging_map[std::clamp<uint32_t>(config_.anticogging.index++, 0, 3600)] = vel_integrator_current_;
config_.anticogging.cogging_map[std::clamp<uint32_t>(config_.anticogging.index++, 0, 3600)] = vel_integrator_torque_;
}
if (config_.anticogging.index < 3600) {
config_.control_mode = CONTROL_MODE_POSITION_CONTROL;
input_pos_ = config_.anticogging.index * axis_->encoder_.getCoggingRatio();
input_vel_ = 0.0f;
input_current_ = 0.0f;
input_torque_ = 0.0f;
input_pos_updated();
return false;
} else {
@@ -101,7 +101,7 @@ bool Controller::anticogging_calibration(float pos_estimate, float vel_estimate)
config_.control_mode = CONTROL_MODE_POSITION_CONTROL;
input_pos_ = 0.0f; // Send the motor home
input_vel_ = 0.0f;
input_current_ = 0.0f;
input_torque_ = 0.0f;
input_pos_updated();
anticogging_valid_ = true;
config_.anticogging.calib_anticogging = false;
@@ -292,9 +292,8 @@ bool Controller::update(float* torque_setpoint_output) {
// Anti-cogging is enabled after calibration
// We get the current position and apply a current feed-forward
// ensuring that we handle negative encoder positions properly (-1 == motor->encoder.encoder_cpr - 1)
// anticogging currently in units of [A], multiply by Kt to get back to torque.
if (anticogging_valid_ && config_.anticogging.anticogging_enabled) {
torque += config_.anticogging.cogging_map[std::clamp(mod((int)anticogging_pos, 3600), 0, 3600)] * axis_->motor_.config_.torque_constant;
torque += config_.anticogging.cogging_map[std::clamp(mod((int)anticogging_pos, 3600), 0, 3600)];
}
float v_err = 0.0f;
@@ -308,7 +307,7 @@ bool Controller::update(float* torque_setpoint_output) {
torque += (vel_gain * gain_scheduling_multiplier) * v_err;
// Velocity integral action before limiting
torque += vel_integrator_current_;
torque += vel_integrator_torque_;
}
// Velocity limiting in current mode
@@ -337,13 +336,13 @@ bool Controller::update(float* torque_setpoint_output) {
// Velocity integrator (behaviour dependent on limiting)
if (config_.control_mode < CONTROL_MODE_VELOCITY_CONTROL) {
// reset integral if not in use
vel_integrator_current_ = 0.0f;
vel_integrator_torque_ = 0.0f;
} else {
if (limited) {
// TODO make decayfactor configurable
vel_integrator_current_ *= 0.99f;
vel_integrator_torque_ *= 0.99f;
} else {
vel_integrator_current_ += ((vel_integrator_gain * gain_scheduling_multiplier) * current_meas_period) * v_err;
vel_integrator_torque_ += ((vel_integrator_gain * gain_scheduling_multiplier) * current_meas_period) * v_err;
}
}
+2 -3
View File
@@ -80,12 +80,11 @@ public:
float pos_setpoint_ = 0.0f;
float vel_setpoint_ = 0.0f;
// float vel_setpoint = 800.0f; <sensorless example>
float vel_integrator_current_ = 0.0f; // [A]
float torque_setpoint_ = 0.0f; // [Nm]
float vel_integrator_torque_ = 0.0f; // [Nm]
float torque_setpoint_ = 0.0f; // [Nm]
float input_pos_ = 0.0f;
float input_vel_ = 0.0f;
float input_current_ = 0.0f;
float input_torque_ = 0.0f;
float input_filter_kp_ = 0.0f;
float input_filter_ki_ = 0.0f;
+15 -15
View File
@@ -95,8 +95,8 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
// check incoming packet type
if (cmd[0] == 'p') { // position control
unsigned motor_number;
float pos_setpoint, vel_feed_forward, current_feed_forward;
int numscan = sscanf(cmd, "p %u %f %f %f", &motor_number, &pos_setpoint, &vel_feed_forward, &current_feed_forward);
float pos_setpoint, vel_feed_forward, torque_feed_forward;
int numscan = sscanf(cmd, "p %u %f %f %f", &motor_number, &pos_setpoint, &vel_feed_forward, &torque_feed_forward);
if (numscan < 2) {
respond(response_channel, use_checksum, "invalid command format");
} else if (motor_number >= AXIS_COUNT) {
@@ -108,15 +108,15 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
if (numscan >= 3)
axis->controller_.input_vel_ = vel_feed_forward;
if (numscan >= 4)
axis->controller_.input_current_ = current_feed_forward;
axis->controller_.input_torque_ = torque_feed_forward;
axis->controller_.input_pos_updated();
axis->watchdog_feed();
}
} else if (cmd[0] == 'q') { // position control with limits
unsigned motor_number;
float pos_setpoint, vel_limit, current_lim;
int numscan = sscanf(cmd, "q %u %f %f %f", &motor_number, &pos_setpoint, &vel_limit, &current_lim);
float pos_setpoint, vel_limit, torque_lim;
int numscan = sscanf(cmd, "q %u %f %f %f", &motor_number, &pos_setpoint, &vel_limit, &torque_lim);
if (numscan < 2) {
respond(response_channel, use_checksum, "invalid command format");
} else if (motor_number >= AXIS_COUNT) {
@@ -128,15 +128,15 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
if (numscan >= 3)
axis->controller_.config_.vel_limit = vel_limit;
if (numscan >= 4)
axis->motor_.config_.current_lim = current_lim;
axis->motor_.config_.torque_lim = torque_lim;
axis->controller_.input_pos_updated();
axis->watchdog_feed();
}
} else if (cmd[0] == 'v') { // velocity control
unsigned motor_number;
float vel_setpoint, current_feed_forward;
int numscan = sscanf(cmd, "v %u %f %f", &motor_number, &vel_setpoint, &current_feed_forward);
float vel_setpoint, torque_feed_forward;
int numscan = sscanf(cmd, "v %u %f %f", &motor_number, &vel_setpoint, &torque_feed_forward);
if (numscan < 2) {
respond(response_channel, use_checksum, "invalid command format");
} else if (motor_number >= AXIS_COUNT) {
@@ -146,22 +146,22 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
axis->controller_.config_.control_mode = Controller::CONTROL_MODE_VELOCITY_CONTROL;
axis->controller_.input_vel_ = vel_setpoint;
if (numscan >= 3)
axis->controller_.input_current_ = current_feed_forward;
axis->controller_.input_torque_ = torque_feed_forward;
axis->watchdog_feed();
}
} else if (cmd[0] == 'c') { // current control
} else if (cmd[0] == 'c') { // torque control
unsigned motor_number;
float current_setpoint;
int numscan = sscanf(cmd, "c %u %f", &motor_number, &current_setpoint);
float torque_setpoint;
int numscan = sscanf(cmd, "c %u %f", &motor_number, &torque_setpoint);
if (numscan < 2) {
respond(response_channel, use_checksum, "invalid command format");
} else if (motor_number >= AXIS_COUNT) {
respond(response_channel, use_checksum, "invalid motor %u", motor_number);
} else {
Axis* axis = axes[motor_number];
axis->controller_.config_.control_mode = Controller::CONTROL_MODE_CURRENT_CONTROL;
axis->controller_.input_current_ = current_setpoint;
axis->controller_.config_.control_mode = Controller::CONTROL_MODE_TORQUE_CONTROL;
axis->controller_.input_torque_ = torque_setpoint;
axis->watchdog_feed();
}
@@ -200,7 +200,7 @@ void ASCII_protocol_process_line(const uint8_t* buffer, size_t len, StreamSink&
respond(response_channel, use_checksum, "Position: q axis pos vel-lim I-lim");
respond(response_channel, use_checksum, "Position: p axis pos vel-ff I-ff");
respond(response_channel, use_checksum, "Velocity: v axis vel I-ff");
respond(response_channel, use_checksum, "Current: c axis I");
respond(response_channel, use_checksum, "Torque: c axis T");
respond(response_channel, use_checksum, "");
respond(response_channel, use_checksum, "Properties start at odrive root, such as axis0.requested_state");
respond(response_channel, use_checksum, "Read: r property");
+6 -6
View File
@@ -82,8 +82,8 @@ void CANSimple::handle_can_message(can_Message_t& msg) {
case MSG_SET_INPUT_VEL:
set_input_vel_callback(axis, msg);
break;
case MSG_SET_INPUT_CURRENT:
set_input_current_callback(axis, msg);
case MSG_SET_INPUT_TORQUE:
set_input_torque_callback(axis, msg);
break;
case MSG_SET_CONTROLLER_MODES:
set_controller_modes_callback(axis, msg);
@@ -281,17 +281,17 @@ void CANSimple::get_encoder_count_callback(Axis* axis, can_Message_t& msg) {
void CANSimple::set_input_pos_callback(Axis* axis, can_Message_t& msg) {
axis->controller_.input_pos_ = can_getSignal<int32_t>(msg, 0, 32, true);
axis->controller_.input_vel_ = can_getSignal<int16_t>(msg, 32, 16, true, 0.1f, 0);
axis->controller_.input_current_ = can_getSignal<int16_t>(msg, 48, 16, true, 0.01f, 0);
axis->controller_.input_torque_ = can_getSignal<int16_t>(msg, 48, 16, true, 0.01f, 0);
axis->controller_.input_pos_updated();
}
void CANSimple::set_input_vel_callback(Axis* axis, can_Message_t& msg) {
axis->controller_.input_vel_ = can_getSignal<int32_t>(msg, 0, 32, true, 0.01f, 0.0f);
axis->controller_.input_current_ = can_getSignal<int16_t>(msg, 32, 16, true, 0.01f, 0.0f);
axis->controller_.input_torque_ = can_getSignal<int16_t>(msg, 32, 16, true, 0.01f, 0.0f);
}
void CANSimple::set_input_current_callback(Axis* axis, can_Message_t& msg) {
axis->controller_.input_current_ = can_getSignal<int32_t>(msg, 0, 32, true, 0.01f, 0);
void CANSimple::set_input_torque_callback(Axis* axis, can_Message_t& msg) {
axis->controller_.input_torque_ = can_getSignal<int32_t>(msg, 0, 32, true, 0.01f, 0);
}
void CANSimple::set_controller_modes_callback(Axis* axis, can_Message_t& msg) {
+2 -2
View File
@@ -20,7 +20,7 @@ class CANSimple {
MSG_SET_CONTROLLER_MODES,
MSG_SET_INPUT_POS,
MSG_SET_INPUT_VEL,
MSG_SET_INPUT_CURRENT,
MSG_SET_INPUT_TORQUE,
MSG_SET_VEL_LIMIT,
MSG_START_ANTICOGGING,
MSG_SET_TRAJ_VEL_LIMIT,
@@ -51,7 +51,7 @@ class CANSimple {
static void get_encoder_count_callback(Axis* axis, can_Message_t& msg);
static void set_input_pos_callback(Axis* axis, can_Message_t& msg);
static void set_input_vel_callback(Axis* axis, can_Message_t& msg);
static void set_input_current_callback(Axis* axis, can_Message_t& msg);
static void set_input_torque_callback(Axis* axis, can_Message_t& msg);
static void set_controller_modes_callback(Axis* axis, can_Message_t& msg);
static void set_vel_limit_callback(Axis* axis, can_Message_t& msg);
static void start_anticogging_callback(Axis* axis, can_Message_t& msg);
+2 -3
View File
@@ -461,13 +461,12 @@ interfaces:
InvalidEstimate:
input_pos: {type: float32, c_setter: set_input_pos}
input_vel: float32
input_current: float32
input_torque: float32
pos_setpoint: readonly float32
vel_setpoint: readonly float32
torque_setpoint: readonly float32
trajectory_done: readonly bool
vel_integrator_current: float32
vel_integrator_torque: float32
anticogging_valid: bool
config:
c_is_class: False
@@ -681,7 +680,7 @@ valuetypes:
# Note: these should be sorted from lowest level of control to
# highest level of control, to allow "<" style comparisons.
VoltageControl:
CurrentControl:
TorqueControl:
VelocityControl:
PositionControl:
+1 -1
View File
@@ -85,7 +85,7 @@ For more information, see [input_modes](input_modes.md).
# Control Commands
* `<axis>.controller.input_pos = <encoder_counts>`
* `<axis>.controller.input_vel = <encoder_counts/s>`
* `<axis>.controller.input_current = <current_in_A>`
* `<axis>.controller.input_torque = <torque in Nm>`
### Input Mode
To modify the way the control command affects the motor, you can use the input mode. The default input mode is pass through.
+2 -2
View File
@@ -346,9 +346,9 @@ Set the velocity ramp rate (acceleration): `axis.controller.config.vel_ramp_rate
Activate the ramped velocity mode: `axis.controller.config.input_mode = INPUT_MODE_VEL_RAMP`.<br>
You can now control the velocity with `axis.controller.input_vel = 5000` [count/s].
### Current control
### Torque control
Set `axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL`.<br>
You can now control the current with `axis.controller.input_current = 3` [A].
You can now control the torque with `axis.controller.input_torque = 3` [Nm].
Note: If you exceed `vel_limit` in current control mode, the current is reduced. To disable this, set `axis.controller.enable_current_mode_vel_limit = False`.
+3 -3
View File
@@ -4,7 +4,7 @@ As of version ###, ODrive now intercepts the incoming commands and can apply fil
* `<axis>.controller.config.input_mode`
* `<axis>.controller.input_pos`
* `<axis>.controller.input_vel`
* `<axis>.controller.input_current`
* `<axis>.controller.input_torque`
The Input Modes currently valid are:
* `INPUT_MODE_INACTIVE`
@@ -27,7 +27,7 @@ Pass `input_xxx` through to `xxx_setpoint` directly.
### Valid Inputs:
* `input_pos`
* `input_vel`
* `input_current`
* `input_torque`
### Valid Control modes:
* `CONTROL_MODE_VOLTAGE_CONTROL`
@@ -92,7 +92,7 @@ Ramp a current command from the current value to the target value.
* `<axis>.controller.config.current_ramp_rate`
### Valid Inputs:
* `input_current`
* `input_torque`
### Valid Control Modes:
* `CONTROL_MODE_CURRENT_CONTROL`
+1 -1
View File
@@ -9,7 +9,7 @@
"type": "python",
"request": "launch",
"stopOnEntry": true,
"pythonPath": "${config:python.pythonPath}",
"pythonPath": "${config:python.interpreterPath}",
"program": "${file}",
"cwd": "${workspaceRoot}",
"env": {},
+7 -7
View File
@@ -26,7 +26,7 @@ command_set = {
'set_controller_modes': (0x00b, [('control_mode', 'i', 1), ('input_mode', 'i', 1)]), # tested
'set_input_pos': (0x00c, [('input_pos', 'i', 1), ('vel_ff', 'h', 0.1), ('cur_ff', 'h', 0.01)]), # tested
'set_input_vel': (0x00d, [('input_vel', 'i', 0.01), ('cur_ff', 'h', 0.01)]), # tested
'set_input_current': (0x00e, [('input_current', 'i', 0.01)]), # tested
'set_input_torque': (0x00e, [('input_torque', 'i', 0.01)]), # tested
'set_velocity_limit': (0x00f, [('velocity_limit', 'f', 1)]), # tested
'start_anticogging': (0x010, []), # untested
'set_traj_vel_limit': (0x011, [('traj_vel_limit', 'f', 1)]), # tested
@@ -174,23 +174,23 @@ class TestSimpleCAN():
axis.controller.input_pos = 1234
axis.controller.input_vel = 1234
axis.controller.input_current = 1234
axis.controller.input_torque = 1234
my_cmd('set_input_pos', input_pos=1, vel_ff=2, cur_ff=3)
fence()
test_assert_eq(axis.controller.input_pos, 1.0, range=0.1)
test_assert_eq(axis.controller.input_vel, 2.0, range=0.01)
test_assert_eq(axis.controller.input_current, 3.0, range=0.001)
test_assert_eq(axis.controller.input_torque, 3.0, range=0.001)
axis.controller.config.control_mode = CONTROL_MODE_VELOCITY_CONTROL
my_cmd('set_input_vel', input_vel=-10.0, cur_ff=30.1234)
fence()
test_assert_eq(axis.controller.input_vel, -10.0, range=0.01)
test_assert_eq(axis.controller.input_current, 30.1234, range=0.01)
test_assert_eq(axis.controller.input_torque, 30.1234, range=0.01)
axis.controller.config.control_mode = CONTROL_MODE_CURRENT_CONTROL
my_cmd('set_input_current', input_current=3.1415)
my_cmd('set_input_torque', input_torque=3.1415)
fence()
test_assert_eq(axis.controller.input_current, 3.1415, range=0.01)
test_assert_eq(axis.controller.input_torque, 3.1415, range=0.01)
my_cmd('set_velocity_limit', velocity_limit=23456.78)
fence()
@@ -210,7 +210,7 @@ class TestSimpleCAN():
test_assert_eq(axis.controller.config.inertia, 55.086, range=0.0001)
# any CAN cmd will feed the watchdog
test_watchdog(axis, lambda: my_cmd('set_input_current', input_current=0.0), logger)
test_watchdog(axis, lambda: my_cmd('set_input_torque', input_torque=0.0), logger)
logger.debug('testing heartbeat...')
# note that this will include the heartbeats that were received during the
+12 -12
View File
@@ -246,19 +246,19 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase):
# Abort immediately if the absolute limits are exceeded
test_assert_within(current_setpoint, -max_current, max_current)
test_assert_within(velocity, -absolute_max_vel, absolute_max_vel)
return input_current, velocity, current_setpoint, get_expected_setpoint(input_current, velocity)
return input_torque, velocity, current_setpoint, get_expected_setpoint(input_torque, velocity)
axis_ctx.handle.controller.input_current = input_current = 0.0
axis_ctx.handle.controller.input_torque = input_torque = 0.0
request_state(axis_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL)
# Move the system around its operating envelope
axis_ctx.handle.controller.input_current = input_current = 2.0
axis_ctx.handle.controller.input_torque = input_torque = 2.0
dataA = record_log(data_getter, duration=1.0)
axis_ctx.handle.controller.input_current = input_current = -2.0
axis_ctx.handle.controller.input_torque = input_torque = -2.0
dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)])
axis_ctx.handle.controller.input_current = input_current = 4.0
axis_ctx.handle.controller.input_torque = input_torque = 4.0
dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)])
axis_ctx.handle.controller.input_current = input_current = -4.0
axis_ctx.handle.controller.input_torque = input_torque = -4.0
dataA = np.concatenate([dataA, record_log(data_getter, duration=1.0)])
# Shrink the operating envelope while motor is moving faster than the envelope allows
@@ -267,22 +267,22 @@ class TestVelLimitInCurrentControl(TestClosedLoopControlBase):
axis_ctx.handle.controller.config.vel_limit = max_vel
# Move the system around its operating envelope
axis_ctx.handle.controller.input_current = input_current = 2.0
axis_ctx.handle.controller.input_torque = input_torque = 2.0
dataB = record_log(data_getter, duration=1.0)
axis_ctx.handle.controller.input_current = input_current = -2.0
axis_ctx.handle.controller.input_torque = input_torque = -2.0
dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)])
axis_ctx.handle.controller.input_current = input_current = 4.0
axis_ctx.handle.controller.input_torque = input_torque = 4.0
dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)])
axis_ctx.handle.controller.input_current = input_current = -4.0
axis_ctx.handle.controller.input_torque = input_torque = -4.0
dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)])
# Try the shrink maneuver again at positive velocity
axis_ctx.handle.controller.config.vel_limit = 20.0 * float(enc_ctx.yaml['cpr'])
axis_ctx.handle.controller.input_current = 4.0
axis_ctx.handle.controller.input_torque = 4.0
time.sleep(0.5)
axis_ctx.handle.controller.config.vel_limit = max_vel
axis_ctx.handle.controller.input_current = input_current = 2.0
axis_ctx.handle.controller.input_torque = input_torque = 2.0
dataB = np.concatenate([dataB, record_log(data_getter, duration=1.0)])
test_assert_no_error(axis_ctx)
+1 -1
View File
@@ -581,7 +581,7 @@ class TestVelCtrlVsPosCtrl(DualAxisTest):
# Set up viscous fluid load
logger.debug("activating load on {}...".format(load_ctx.name))
load_ctx.handle.controller.config.vel_integrator_gain = 0
load_ctx.handle.controller.vel_integrator_current = 0
load_ctx.handle.controller.vel_integrator_torque = 0
set_limits(load_ctx, logger, vel_limit=100000, current_limit=50)
load_ctx.handle.controller.set_vel_setpoint(0, 0)
request_state(load_ctx, AXIS_STATE_CLOSED_LOOP_CONTROL)
+6 -6
View File
@@ -101,28 +101,28 @@ class TestUartAscii():
# Test 'c', 'v', 'p', 'q' and 'f' commands
odrive.handle.axis0.controller.input_current = 0
odrive.handle.axis0.controller.input_torque = 0
ser.write(b'c 0 12.5\n')
test_assert_eq(ser.readline(), b'')
test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001)
test_assert_eq(odrive.handle.axis0.controller.input_torque, 12.5, accuracy=0.001)
test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_CURRENT_CONTROL)
odrive.handle.axis0.controller.input_vel = 0
odrive.handle.axis0.controller.input_current = 0
odrive.handle.axis0.controller.input_torque = 0
ser.write(b'v 0 567.8 12.5\n')
test_assert_eq(ser.readline(), b'')
test_assert_eq(odrive.handle.axis0.controller.input_vel, 567.8, accuracy=0.001)
test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001)
test_assert_eq(odrive.handle.axis0.controller.input_torque, 12.5, accuracy=0.001)
test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_VELOCITY_CONTROL)
odrive.handle.axis0.controller.input_pos = 0
odrive.handle.axis0.controller.input_vel = 0
odrive.handle.axis0.controller.input_current = 0
odrive.handle.axis0.controller.input_torque = 0
ser.write(b'p 0 123.4 567.8 12.5\n')
test_assert_eq(ser.readline(), b'')
test_assert_eq(odrive.handle.axis0.controller.input_pos, 123.4, accuracy=0.001)
test_assert_eq(odrive.handle.axis0.controller.input_vel, 567.8, accuracy=0.001)
test_assert_eq(odrive.handle.axis0.controller.input_current, 12.5, accuracy=0.001)
test_assert_eq(odrive.handle.axis0.controller.input_torque, 12.5, accuracy=0.001)
test_assert_eq(odrive.handle.axis0.controller.config.control_mode, CONTROL_MODE_POSITION_CONTROL)
odrive.handle.axis0.controller.input_pos = 0