Skip to content
Software

Python library

3 min readFirmware v2 · protocol v2

TercioBridge.py is a single file with one dependency, pyserial. It speaks protocol v2 through the Tercio FD, tracks telemetry from every node in the background, and converts units for you.

Live telemetryA background thread keeps the latest state of every node, so motor.position is always fresh.
Your unitsDegrees, radians or turns per motor. The wire always carries turns.
Errors are exceptionsA refused command raises TercioError with the driver’s status.
  1. Python 3.8 or newer, and pyserial:

    Terminal window
    pip install pyserial
  2. Download TercioBridge.py and put it next to your script.

from TercioBridge import Bus, Stepper, Unit
with Bus("COM5") as bus: # or "/dev/ttyACM0", "/dev/cu.usbmodem…"
for info in bus.discover(): # every node on the bus
print(info)
motor = Stepper(bus, 1, unit=Unit.DEGREES)
motor.enable()
motor.move_to(180, wait=True)
print(f"at {motor.position:.2f}°")

Bus() without a port opens the first serial port it finds, which is only safe when the adapter is the only one. Only one program can hold the port: close Tercio Control first.

Bus(port=None, baud=115200, timeout=0.2) Opens on open() or with. A background thread reads everything the adapter sends. timeout is how long a command waits for its reply.
bus.discover(wait=0.15) Broadcasts GetInfo and returns a NodeInfo per answer: node ID, firmware, protocol, hardware revision, encoder and 12-byte uid. Two drivers sharing an ID both show up.
bus.nodes() Node IDs that sent telemetry in the last second.
bus.assign_node_id(uid, new_id) Gives the driver with this serial a new ID. Then Stepper(bus, new_id).save().
bus.stop_all(), bus.emergency_stop_all() Broadcast Stop or Emergency stop.
bus.sync() Starts every move queued with deferred=True.
bus.telemetry(node) Latest Telemetry of a node, or None.
bus.on_fault callback(node, faults, warnings, state), called on every fault event.
bus.send(can_id, opcode, payload) Raw frame, for anything the helpers don’t cover.

Stepper(bus, node_id, unit=Unit.DEGREES). Positions and speeds are in unit: Unit.DEGREES (default), Unit.RADIANS or Unit.TURNS.

Method Does
enable(), disable() Bridge on (hold) or off.
move_to(pos, velocity=None, acceleration=None, deferred=False, wait=False, timeout=30, ack=True) Absolute move. velocity and acceleration can only lower the limits. wait=True blocks until settled. ack=False streams without waiting for a reply.
move_by(delta, …) Relative move, same options.
set_velocity(v, acceleration=None) Continuous rotation; 0 stops and holds.
stop(), emergency_stop() Decelerate and hold, or bridge off at once.
set_zero(pos=0.0) The current position becomes pos.
calibrate(wait=True), home(wait=True) Run the procedure; by default block until it ends and raise if it failed.
auto_tune(minimum, maximum) Find the speed limits between two positions; returns (velocity, acceleration).
get_param(Param.X), set_param(Param.X, value) Any parameter, in its wire units (turns). set_param returns the value as applied.
set_limits(velocity, acceleration), set_current(run_ma, hold_percent), set_microsteps(n), set_pid(kp, ki, kd) Shortcuts; limits in unit.
set_node_id(new_id, persist=True) Change the ID (and save).
save(), factory_reset(), reboot(), clear_faults(), info() System commands.
telemetry, position, velocity Latest values from the background thread.
wait_settled(timeout=30) Block until holding and settled; raises on a fault or stall.

motor.telemetry is the latest frame, or None until the first one arrives (at 100 Hz by default, so give it a moment after opening the bus):

t = motor.telemetry
t.state # AxisState.HOLDING, MOVING, …
t.flags # Flag.ENABLED | Flag.CALIBRATED | …
t.calibrated, t.settled, t.enabled
t.faults, t.warnings # Fault / Warning_ flags
t.position, t.target # turns
t.velocity, t.following_error # turns/s, turns
t.temperature_c, t.supply_v
t.control_load_pct, t.sequence

Telemetry values are in turns; motor.position and motor.velocity convert to the motor’s unit.

A refused command raises TercioError with .status set to the status the driver answered:

from TercioBridge import TercioError, Status
try:
motor.move_to(90)
except TercioError as e:
if e.status == Status.NOT_CALIBRATED:
motor.calibrate()
else:
raise

No reply within timeout raises TercioError("timeout").

x = Stepper(bus, 1)
y = Stepper(bus, 2)
for m in (x, y):
m.enable()
x.move_to(90, deferred=True)
y.move_to(-90, deferred=True)
bus.sync() # both start on the same instant
x.wait_settled(); y.wait_settled()
bus.on_fault = lambda node, faults, warnings, state: print(f"node {node}: {faults!r}")

on_fault runs on the reader thread: keep it short, and don’t send commands that wait for replies from inside it.

print(bus.adapter_info()) # firmware, hardware revision, uid, boot options
s = bus.adapter_status # refreshed every 250 ms
print(s.bus_state.name, s.tx_error_count, s.dropped_to_can)
bus.reset_adapter_counters()
bus.enter_adapter_bootloader() # USB update mode; closes the bus
from TercioBridge import Bus, Imu, ZeroMode, FusionMode
imu = Imu(bus) # node 100 by default
imu.zero() # or imu.zero_heading()
t = imu.telemetry # None until the first frame
print(t.roll, t.pitch, t.yaw, t.quaternion)
print(t.acceleration, t.rate, t.temperature_c, t.calibration)
imu.wait_calibrated() # blocks until gyro and accel (and mag in NDOF) are calibrated
imu.save_calibration() # restored at every power-up
imu.set_fusion_mode(FusionMode.NDOF)
imu.save() # settings and zero to flash
Imu(bus, node_id=100) One Tercio IMU. bus.imus() lists the IMUs reporting; bus.discover() marks each node with device.
zero(mode=ZeroMode.ALL), zero_heading(), clear_zero() Set or clear the zero reference.
save_calibration(), wait_calibrated(timeout) Store the calibration in flash; wait for full calibration.
get_param(ImuParam.X), set_param(ImuParam.X, value), set_fusion_mode(mode) Node ID, telemetry rate, fusion mode, axis mapping.
save(), factory_reset(), reboot(), clear_faults(), info() System commands.
telemetry ImuTelemetry: state, flags, faults, warnings, calibration, quaternion, roll, pitch, yaw, acceleration, rate, temperature_c.

See Tercio IMU.