import argparse import statistics import time import erpc from servo_service import client DEFAULT_PORT = "COM8" DEFAULT_BAUD = 115200 DEFAULT_ITERATIONS = 100 DEFAULT_JOINT_COUNT = 26 DEFAULT_WARMUP = 5 def positive_int(value): parsed = int(value) if parsed <= 0: raise argparse.ArgumentTypeError("value must be > 0") return parsed def non_negative_float(value): parsed = float(value) if parsed < 0: raise argparse.ArgumentTypeError("value must be >= 0") return parsed def percentile(values, ratio): if not values: raise ValueError("values must not be empty") ordered = sorted(values) index = int(ratio * (len(ordered) - 1)) return ordered[index] def measure_movej_latency(api, angles, iterations, warmup): for index in range(warmup): result = api.moveJ(angles) # if result != 0: # raise RuntimeError(f"Warmup moveJ failed at iteration {index}: {result}") costs_ms = [] for index in range(iterations): t0 = time.perf_counter() result = api.moveJ(angles) elapsed_ms = (time.perf_counter() - t0) * 1000.0 # if result != 0: # raise RuntimeError(f"moveJ failed at iteration {index}: {result}") costs_ms.append(elapsed_ms) return costs_ms def print_summary(costs_ms): print(f"count = {len(costs_ms)}") print(f"avg = {statistics.mean(costs_ms):.3f} ms") print(f"min = {min(costs_ms):.3f} ms") print(f"max = {max(costs_ms):.3f} ms") print(f"p50 = {statistics.median(costs_ms):.3f} ms") print(f"p95 = {percentile(costs_ms, 0.95):.3f} ms") def build_parser(): parser = argparse.ArgumentParser(description="Measure moveJ RPC latency.") parser.add_argument("--port", default=DEFAULT_PORT, help=f"Serial port, default: {DEFAULT_PORT}") parser.add_argument("--baud", type=positive_int, default=DEFAULT_BAUD, help=f"Serial baud, default: {DEFAULT_BAUD}") parser.add_argument( "--iterations", type=positive_int, default=DEFAULT_ITERATIONS, help=f"Measured moveJ calls, default: {DEFAULT_ITERATIONS}", ) parser.add_argument( "--warmup", type=positive_int, default=DEFAULT_WARMUP, help=f"Warmup moveJ calls before measuring, default: {DEFAULT_WARMUP}", ) parser.add_argument( "--joint-count", type=positive_int, default=DEFAULT_JOINT_COUNT, help=f"Length of angles_rad list, default: {DEFAULT_JOINT_COUNT}", ) parser.add_argument( "--angle-rad", type=non_negative_float, default=0.0, help="Angle value written into every joint slot, default: 0.0", ) return parser def main(): args = build_parser().parse_args() angles = [args.angle_rad] * args.joint_count transport = erpc.transport.SerialTransport(args.port, args.baud) try: client_mgr = erpc.client.ClientManager(transport, erpc.basic_codec.BasicCodec) api = client.servo_serviceClient(client_mgr) print(f"port = {args.port}") print(f"baud = {args.baud}") print(f"iterations = {args.iterations}") print(f"warmup = {args.warmup}") print(f"joint_count = {args.joint_count}") print(f"angle_rad = {args.angle_rad}") costs_ms = measure_movej_latency(api, angles, args.iterations, args.warmup) print_summary(costs_ms) finally: if hasattr(transport, "close"): transport.close() if __name__ == "__main__": main()