No Rotation on CAN Bus, S1 and Micro. Velocity Mode

I am new to odrive and can bus.
I have two motors and drivers on the can bus:
id 0: odrive S1
id 1: odrive micro

The motors are identical. Custom built motors with US digital 4096 incremental encoders (I have used these for years with traditional bldc controllers). I can walk through the odrive gui setup, save the configurations, do index search, etc., and run them via the gui with no issues. The work great.

My understanding is that the configuration saved in the gui is saved on the drivers, and I can go directly to contolling them over can. The only reason to send the configuration via can would be if I had changes, and did not want to use the gui.

With the code below, based on the simple example, id1 will spin, as if it is doing an index search, proceed to the velocity setting, but not spin. Power increases for 5 seconds, but no movement.

id0 will not do the index search, at all. If I remove the index search, it will set the velocity, draw high current, but not spin.

As noted, these both run GREAT via the gui. What am I doing wrong?

"""
Minimal example for controlling an ODrive via the CANSimple protocol.

Puts the ODrive into closed loop control mode, sends a velocity setpoint of 1.0
and then prints the encoder feedback.

Assumes that the ODrive is already configured for velocity control.

See https://docs.odriverobotics.com/v/latest/manual/can-protocol.html for protocol
documentation.
"""

import can
import struct
import time

node_id = 1 # must match `<odrv>.axis0.config.can.node_id`. The default is 0.

bus = can.interface.Bus("can0", bustype="socketcan")

print('#### print status currently')
cmd_id = 0x01 # heartbeat command ID
message_id = (node_id << 5 | cmd_id)
for msg in bus:
  if msg.arbitration_id == message_id:
      error, state, result, traj_done = struct.unpack('<IBBB', bytes(msg.data[:7]))
      break
print(error, state, result, traj_done)

print('#### flushing buffer')
# Flush CAN RX buffer so there are no more old pending messages
while not (bus.recv(timeout=0) is None): pass

# ----- ADD: run encoder index search -----
print('#### index search')
bus.send(can.Message(
    arbitration_id=(node_id << 5 | 0x07), # 0x07: Set_Axis_State
    data=struct.pack('<I', 6), # 6 = ENCODER_INDEX_SEARCHL
    is_extended_id=False
))
# wait until state != 6 (watch heartbeat)
while True:
	msg = bus.recv(timeout=0.5)
	print(msg)
	if msg and msg.arbitration_id == ((0x01 << 5) | node_id):	# Heartbeat
		error, state, result, trajDone = struct.unpack('<IBBB', bytes(msg.data[:7]))
		if error != 0:
			raise RuntimeError(f'Axis error 0x{error:08X}')
		if state != 6:
			break
print('#### index search done')

time.sleep(1)


# Put axis into closed loop control state
print('#### put in closed loop')
bus.send(can.Message(
    arbitration_id=(node_id << 5 | 0x07), # 0x07: Set_Axis_State
    data=struct.pack('<I', 8), # 8: AxisState.CLOSED_LOOP_CONTROL
    is_extended_id=False
))

# Wait for axis to enter closed loop control by scanning heartbeat messages
print('#### wait for close loop to take effect')
for msg in bus:
	print(msg)
	if msg.arbitration_id == (node_id << 5 | 0x01): # 0x01: Heartbeat
		error, state, result, traj_done = struct.unpack('<IBBB', bytes(msg.data[:7]))
		if state == 8: # 8: AxisState.CLOSED_LOOP_CONTROL
			print('worked, set to 8, close loop')
			break

# Set velocity to 1.0 turns/s
vSet=30.0
print('#### set velocity')
bus.send(can.Message(
    arbitration_id=(node_id << 5 | 0x0d), # 0x0d: Set_Input_Vel
    data=struct.pack('<ff', vSet, 0.0), # 1.0: velocity, 0.0: torque feedforward
    is_extended_id=False
))

'''
# Print encoder feedback
print('#### Encoder feedback')
for msg in bus:
	if msg.arbitration_id == (node_id << 5 | 0x09): # 0x09: Get_Encoder_Estimates
		pos, vel = struct.unpack('<ff', bytes(msg.data))
		print(f"pos: {pos:.3f} [turns], vel: {vel:.3f} [turns/s]")
'''


time.sleep(5)

vSet=0.0
print('#### turning off')
bus.send(can.Message(
    arbitration_id=(node_id << 5 | 0x0d), # 0x0d: Set_Input_Vel
    data=struct.pack('<ff', vSet, 0.0), # 1.0: velocity, 0.0: torque feedforward
    is_extended_id=False
))

It looks like you’re not specifically waiting for the node to report index search before you then wait again for it to exit. So if a heartbeat message is sent immediately before your message to enter ENCODER_INDEX_SEARCH (thus with a state of IDLE), your first while loop will immediately exit, and you’ll put the ODrive in closed loop. Instead, you should command the index search state, then wait for the ODrive to report being in index search, and then wait for it to report being in idle.

That being said, the ODrive should report NOT_CALIBRATED if the index search fails / is exited early. Could you share your whole config with odrivetool backup-config config.json?