Pulsar Python SDK
Examples
The maintained examples repository will be published as:
github.com/PulsarHRI/pulsar-sdk-examples
It is organized by user goal and will contain Python examples plus future Rust examples.
First-release example set
Physical actuator over USB
Use the complete Physical actuator over direct USB with Pulsar Python SDK.
Scan and use CAN
Use Multiple physical actuators over CAN FD with Pulsar Python SDK and CAN.
Virtual actuator
Use Virtual actuator with Pulsar Python SDK.
Telemetry logging
import csv
from time import sleep
from pulsar_sdk import (
ControlConfiguration,
Mode,
PcpOverUsb,
PulsarActuatorReal,
TelemetryItems,
)
items = (TelemetryItems.POSITION, TelemetryItems.VELOCITY)
actuator = PulsarActuatorReal(PcpOverUsb(), address=0)
with actuator, open("telemetry.csv", "w", newline="") as output:
writer = csv.writer(output)
writer.writerow(("position_rad", "velocity_rad_s"))
def log_sample(address, telemetry):
writer.writerow((telemetry[items[0]], telemetry[items[1]]))
actuator.configure(ControlConfiguration(Mode.VELOCITY, items, 100))
actuator.set_telemetry_callback(log_sample)
actuator.enable()
actuator.set_command(0.0)
sleep(2)
actuator.disable()
actuator.set_telemetry_callback(None)
Multiple actuators in one group
arm.set_commands({
"shoulder": {ControlParams.COMMAND: 0.2},
"elbow": {ControlParams.COMMAND: -0.2},
})
Platform callback
def controller(metadata, telemetry):
return {
name: {ControlParams.COMMAND: 0.0}
for name in telemetry
}
robot.set_control_callback(controller)
The callback runs independently for each group. Use metadata.group_name when behavior differs by group.
Diagnostics and recovery
status = arm.status
if status.last_host_error is not None:
# Fix the reported cause first.
if arm.clear_faults():
arm.resume()
If diagnostic safety entered INIT_CONFIG, use recover() after the physical condition is clear.
Two CAN domains in one platform
Use a separate PcpOverCan domain for each USB-to-CAN adapter. The platform coordinates lifecycle and safety, while each group keeps its own telemetry stream and callback timing.
from pulsar_sdk import (
ActuatorGroup,
ControlConfiguration,
ControlParams,
Mode,
PcpOverCan,
Platform,
PulsarActuatorReal,
TelemetryItems,
)
right_can = PcpOverCan("<right-adapter-port>")
left_can = PcpOverCan("<left-adapter-port>")
right_arm = ActuatorGroup(
"right_arm",
{
"shoulder": PulsarActuatorReal(right_can, address=23),
"elbow": PulsarActuatorReal(right_can, address=24),
},
)
left_arm = ActuatorGroup(
"left_arm",
{
"shoulder": PulsarActuatorReal(left_can, address=23),
"elbow": PulsarActuatorReal(left_can, address=24),
},
)
robot = Platform({"right_arm": right_arm, "left_arm": left_arm})
configuration = ControlConfiguration(
mode=Mode.IMPEDANCE,
telemetry_items=(
TelemetryItems.POSITION,
TelemetryItems.VELOCITY,
),
telemetry_rate_hz=500,
)
def controller(metadata, telemetry):
return {
name: {ControlParams.COMMAND: values[TelemetryItems.POSITION]}
for name, values in telemetry.items()
}
with robot:
robot.configure({
"right_arm": configuration,
"left_arm": configuration,
})
robot.zero_position()
robot.set_control_callback(controller)
robot.enable()
# Run the application control loop here.
robot.disable()
The same actuator addresses may be reused because the adapters are separate domains. Replace the port placeholders with values returned by PcpOverCan.list_ports().
Mixed physical and virtual platform
from pulsar_sdk import (
ActuatorGroup,
ControlConfiguration,
ControlParams,
Mode,
PcpOverUsb,
Platform,
PulsarActuatorReal,
PulsarActuatorVirtual,
TelemetryItems,
VirtualWorld,
)
physical = ActuatorGroup(
"physical",
{"joint": PulsarActuatorReal(PcpOverUsb(), address=0)},
)
world = VirtualWorld()
virtual = ActuatorGroup(
"virtual",
{"joint": PulsarActuatorVirtual(world, model="PULSE115-60G", address=1)},
)
robot = Platform({"physical": physical, "virtual": virtual})
configuration = ControlConfiguration(
Mode.VELOCITY,
(TelemetryItems.VELOCITY,),
100,
)
def controller(metadata, telemetry):
return {
"joint": {ControlParams.COMMAND: 0.0}
}
with robot:
robot.configure({"physical": configuration, "virtual": configuration})
robot.set_control_callback(controller)
robot.enable()
# Physical telemetry arrives from its serial domain. The application must
# explicitly advance the independent virtual simulation domain.
for _ in range(100):
world.step()
robot.disable()
A platform coordinates lifecycle and safety across these groups, but it does not merge their telemetry or synchronize their independent clocks.
Warning
Motion examples must be validated on the intended hardware and mechanism before publication or reuse. Start with conservative commands and limits.