Python API¶
servolab 1.0.0. Everything here is also in Python itself: help(servolab.connect), help(m.move_to) ... and on the documentation page of ServoScope. All values are in SI units (rad, rad/s, A, V, °C), see Modes & units.
Module¶
connect()¶
servolab.connect(port=None, scan=True, sim_motor_ids=(1, 2, 3), show=True)
Open a bus and (normally) find all motors on it.
port: None -> find the Waveshare adapter automatically
"COM5" -> this serial port (Windows), "/dev/ttyACM0" (Linux),
"/dev/cu.usbmodem..." (macOS)
"sim" -> simulated motors, no hardware needed
"sim:so102" -> a simulated robot (needs: pip install "servolab[sim]")
"tcp://emr-mobile-3.local" -> motors on another computer, e.g. the
Raspberry Pi of a mobile robot (it runs servolab-server)
scan: False -> don't look for motors (use bus.scan() later)
sim_motor_ids: IDs of the simulated motors (only for port="sim")
show: False -> no 3D window (only for simulated robots)
Returns a Bus. Examples:
bus = servolab.connect() # USB adapter, found automatically
bus = servolab.connect("sim") # three simulated motors
bus = servolab.connect("sim:so102") # the simulated SO102 arm
bus = servolab.connect("tcp://192.168.4.1") # a robot over WiFi (in its WiFi network)
print(bus.motors)
list_ports()¶
servolab.list_ports()
All serial ports of this computer, as a list of (name, description).
Example: [('COM5', 'USB-SERIAL CH343 (Waveshare adapter?)')]
Bus¶
A connection to a chain of motors (and sensors).
motors¶
bus.motors
Dict of all motors found by the last scan: {ID: Motor}.
sensors¶
bus.sensors
Dict of all sensors (IMUs) found by the last scan: {ID: Imu}.
bus[…]¶
bus[device_id]
bus[1] gives you the motor (or sensor) with ID 1 (an ID, not a list index).
scan()¶
bus.scan(ids=range(0, 253))
Ask every ID whether a device is there. Returns the list of found IDs.
Motors end up in bus.motors, IMUs in bus.sensors.
Takes about 2.5 s for all 253 IDs. If you know your IDs, scan fewer:
bus.scan(range(1, 7))
Raises DuplicateIdError if two devices answer to the same ID.
read_feedback()¶
bus.read_feedback(ids=None)
Read the state of all motors and sensors with ONE message. Returns a Feedback.
ids: only read these devices, e.g. [1, 2]. None = all motors and sensors.
Example:
fb = bus.read_feedback()
fb.position # array, in rad, sorted like fb.ids
fb.sensors[100].acceleration # the IMU with ID 100, in m/s^2
write_positions()¶
bus.write_positions(positions, ids=None)
Move several motors at once.
positions: {motor_id: rad, ...}, or an array/list in the same order
as read_feedback().ids (or as `ids`, if given).
Example:
bus.write_positions({1: 0.5, 2: -0.5})
fb = bus.read_feedback()
bus.write_positions(fb.position + 0.1)
write_velocities()¶
bus.write_velocities(velocities, ids=None)
Set the speed of several motors at once.
velocities: {motor_id: rad/s, ...}, or an array/list like write_positions().
write_pwms()¶
bus.write_pwms(pwms, ids=None)
Set the PWM of several motors at once.
pwms: {motor_id: -1.0 ... 1.0, ...}, or an array/list like write_positions().
move_to()¶
bus.move_to(positions, ids=None, speed=None, acceleration=None, wait=False, timeout=10.0)
Move several motors at once, optionally with speed and acceleration limits.
positions: {motor_id: rad, ...}, or an array/list like write_positions()
speed: maximum speed in rad/s, the same for every motor (None = as fast as possible)
acceleration: in rad/s^2, the same for every motor (None = as fast as possible)
wait: True = only return when all motors have arrived
timeout: give up waiting after this many seconds
All motors start at the same time, but each one moves at `speed`, so
a motor with a shorter way arrives earlier.
Example: bus.move_to([0.5, -0.3], ids=[1, 2], speed=1.0, wait=True)
read_register()¶
bus.read_register(motor_id, name)
Read one register of one motor by name. Returns the raw value (int).
write_register()¶
bus.write_register(motor_id, name, value, save=False)
Write one register of one motor by name. `value` is raw (int).
save=True: also keep the value after power-off (EEPROM registers only).
write_registers()¶
bus.write_registers(motor_id, values)
Write several registers that lie next to each other with ONE message.
values = {name: raw value, ...}, in any order. The motor gets all of
them at the same moment, e.g. speed and goal of a move together.
sync_write_register()¶
bus.sync_write_register(name, raw_values)
Write the same register of several motors with ONE message.
raw_values = {motor_id: raw value, ...}. All motors react at the same time.
sync_write_registers()¶
bus.sync_write_registers(values_per_id)
Write several registers next to each other, of several motors, with ONE message.
values_per_id = {motor_id: {name: raw value, ...}, ...}. Every motor
needs the same register names.
change_id()¶
bus.change_id(old_id, new_id)
Give a motor (or sensor) a new ID. It is saved permanently in the device.
Usually you just write bus[1].id = 2 which calls this function.
factory_reset()¶
bus.factory_reset(device_id, keep_zero=True)
Put a motor (or IMU) back to its factory settings. Saved permanently.
Uses the device's own reset instruction, so you get exactly the values
it came with: limits, gains, mode, protection ...
The device keeps its ID. A motor also keeps its zero position
(see m.set_zero()), unless keep_zero=False.
Usually you just write bus[1].factory_reset() which calls this function.
torque_off_all()¶
bus.torque_off_all()
Switch every motor off (they become free to turn by hand).
disconnect()¶
bus.disconnect()
Switch all motors off and close the port. Safe to call twice.
Motor¶
One Feetech motor on the bus.
id¶
m.id (read / write)
ID of the motor on the bus.
Writing m.id = 2 gives the motor a new ID (saved permanently in the
motor). No other motor on the bus may already have this ID.
position¶
m.position (read / write)
Present position in rad. 0 = middle (or the zero of set_zero()), range -pi ... +pi.
The same angle in every mode. Writing m.position = x sets the goal
position (only in position mode).
velocity¶
m.velocity (read / write)
Present velocity in rad/s.
Writing m.velocity = x sets the goal velocity (only in velocity mode).
pwm¶
m.pwm (read / write)
PWM output, -1.0 ... 1.0 (1.0 = full voltage). Only in pwm mode.
PWM means: the motor gets this fraction of the supply voltage.
There is no controller in between - that's your job.
current¶
m.current (read only)
Motor current in A.
load¶
m.load (read only)
How hard the motor works, -1.0 ... 1.0 (1.0 = 100 %).
voltage¶
m.voltage (read only)
Supply voltage in V.
temperature¶
m.temperature (read only)
Temperature inside the motor in degrees Celsius.
moving¶
m.moving (read only)
True while the motor is moving.
status¶
m.status (read only)
List of active problems, e.g. ['overheat']. Empty list = all fine.
torque_enabled¶
m.torque_enabled (read / write)
True = motor is powered and holds/moves. False = motor is free.
mode¶
m.mode (read / write)
Operating mode: "position", "velocity" or "pwm".
pid¶
m.pid (read / write)
Gains (P, I, D) of the controller inside the motor (position mode).
info¶
m.info (read only)
Overview of the motor, as a dict.
move_to()¶
m.move_to(rad, speed=None, acceleration=None, wait=False, timeout=10.0)
Go to a position, optionally with speed and acceleration limits.
rad: goal position in rad
speed: maximum speed in rad/s (None = as fast as possible)
acceleration: in rad/s^2 (None = as fast as possible)
wait: True = only return when the motor has arrived
timeout: give up waiting after this many seconds
Example: m.move_to(1.0, speed=2.0, wait=True)
wait_until_arrived()¶
m.wait_until_arrived(rad, timeout=10.0, tolerance=0.02)
Wait until the motor is within `tolerance` rad of `rad` and stands still.
set_zero()¶
m.set_zero()
Make the current position the new 0 rad. Saved permanently in the motor.
Torque is switched off for this, so the motor does not jump.
factory_reset()¶
m.factory_reset(keep_zero=True)
Put all settings back to how the motor came from the factory. Saved permanently.
Limits, gains, mode, protection ... all go back to the stock values.
The motor keeps its ID and (unless keep_zero=False) its zero position.
Afterwards the motor is in position mode (the factory mode).
read_register()¶
m.read_register(name)
Read any register by name (see registers.py). Returns the raw value.
write_register()¶
m.write_register(name, value, save=False)
Write any register by name (see registers.py). `value` is raw.
save=True keeps an EEPROM value after power-off. Don't do that in
loops: EEPROM wears out after many writes.
save_to_eeprom()¶
m.save_to_eeprom()
Keep all current settings (mode, limits, gains ...) after power-off.
Imu¶
One IMU on the bus.
id¶
imu.id (read / write)
ID of the IMU on the bus.
Writing imu.id = 101 gives the IMU a new ID (saved permanently).
No other device on the bus may already have this ID.
read()¶
imu.read()
Read all measurements with ONE message. Returns an ImuFeedback.
Use this in loops: acceleration, angular velocity and orientation then
belong to the same moment.
acceleration¶
imu.acceleration (read only)
Acceleration [x, y, z] in m/s^2 (includes gravity, see the top of this file).
angular_velocity¶
imu.angular_velocity (read only)
Angular velocity [x, y, z] in rad/s (from the gyro).
quaternion¶
imu.quaternion (read only)
Orientation as a quaternion [w, x, y, z], from the IMU's own filter.
gyro_range¶
imu.gyro_range (read / write)
Largest angular velocity the gyro can measure, in deg/s: 250, 500, 1000 or 2000.
acc_range¶
imu.acc_range (read / write)
Largest acceleration the accelerometer can measure, in g: 2, 4, 8 or 16.
sample_rate¶
imu.sample_rate (read / write)
Measurements per second, in Hz: 26, 52, 104, 208, 416 or 833.
info¶
imu.info (read only)
Overview of the IMU, as a dict.
read_register()¶
imu.read_register(name)
Read any register by name (see registers.py). Returns the raw value.
write_register()¶
imu.write_register(name, value, save=False)
Write any register by name (see registers.py). `value` is raw.
save=True keeps an EEPROM value after power-off.
save_to_eeprom()¶
imu.save_to_eeprom()
Keep all current settings (ranges, sample rate ...) after power-off.
factory_reset()¶
imu.factory_reset()
Put all settings (ranges, sample rate ...) back to the factory values.
Saved permanently. The IMU keeps its ID.
Feedback¶
State of all motors (and sensors), read in one go.
of()¶
fb.of(motor_id)
All values of one motor (or sensor) as a dict. Example: fb.of(1)['position']
Recorder¶
Collects values in a loop. Look at them after the loop.
add()¶
rec.add(time, **values)
Store the values of one moment.
Example: rec.add(time=fb.time, position=0.5, goal=1.0)
You can choose the names yourself ("**values" means: any names you
like). Use the same names every time.
rec[…]¶
rec[name]
rec["position"] gives all recorded positions as a numpy array.
plot()¶
rec.plot(*names)
Draw the recorded values over time, all in one diagram.
rec.plot() -> everything
rec.plot("position", "goal") -> only these
save()¶
rec.save(filename)
Save everything as a CSV file: one column per name, one row per moment.
LivePlot¶
Like Recorder, but also shows the values live in a separate window.
add()¶
live.add(time, **values)
Store the values of one moment, like Recorder.add(), and show them in the window.
Example: live.add(time=fb.time, position=fb.position[0], goal=goal)
Rate¶
Helps a loop run at a fixed rate (like a metronome).
sleep()¶
rate.sleep()
Wait until the next period starts. Call it once at the end of every loop pass.