RSI-PI/src/RSIPI/rsi_cli.py
Adam fbd8dfacdc Fix send/receive variable inversion and network loop performance
The variable naming follows KUKA convention (robot's perspective) where
send_variables = what the robot sends us (RIst, RSol) and
receive_variables = what the robot receives from us (RKorr, DiO).
All APIs were using them backwards — writing corrections to
send_variables and building response XML from them, meaning the robot
never received actual corrections.

- network_handler: parse incoming XML into send_variables, build
  response XML from receive_variables, use local dict snapshots
  to avoid per-key Manager IPC within the 4ms cycle
- motion_api: check receive_variables for RKorr/AKorr
- tools_api: write user corrections to receive_variables
- monitoring_api: read robot state from send_variables
- io_api: read digital inputs from send_variables
- krl_api: read Tech.T params from send_variables
- rsi_cli/rsi_graphing: add --config arg, remove hardcoded paths
- main.py: test runner with all examples and multiprocessing guard
2026-04-17 18:54:10 +01:00

203 lines
8.8 KiB
Python

from RSIPI.rsi_api import RSIAPI
class RSICommandLineInterface:
"""Command-Line Interface for controlling RSI Client."""
def __init__(self, input_config_file):
self.client = RSIAPI(input_config_file)
self.running = True
def run(self):
print("RSI Command-Line Interface Started. Type 'help' for commands.")
while self.running:
try:
command = input("RSI> ").strip()
self.process_command(command)
except KeyboardInterrupt:
self.exit()
def process_command(self, command):
parts = command.split()
if not parts:
return
cmd = parts[0].lower()
args = parts[1:]
try:
match cmd:
case "start":
print(self.client.start_rsi())
case "stop":
print(self.client.stop_rsi())
case "exit":
self.exit()
case "set":
var, val = args[0], args[1]
print(self.client.update_variable(var, val))
case "show":
print("📤 Send Variables:")
self.client.show_variables()
case "reset":
print(self.client.reset_variables())
case "status":
print(self.client.show_config_file())
case "ipoc":
print(f"🛰 IPOC: {self.client.get_ipoc()}")
case "watch":
duration = float(args[0]) if args else None
self.client.watch_network(duration)
case "reconnect":
print(self.client.reconnect())
case "alerts":
state = args[0].lower()
self.client.enable_alerts(state == "on")
case "set_alert_threshold":
alert_type, value = args[0], float(args[1])
self.client.set_alert_threshold(alert_type, value)
case "toggle":
group, name, value = args
print(self.client.toggle_digital_io(group, name, value))
case "move_external":
axis, value = args
print(self.client.move_external_axis(axis, value))
case "correct":
corr_type, axis, value = args
print(self.client.correct_position(corr_type, axis, value))
case "speed":
tech_param, value = args
print(self.client.adjust_speed(tech_param, value))
case "override":
state = args[0]
self.client.override_safety(state in ["on", "true", "1"])
case "log":
subcmd = args[0]
if subcmd == "start":
print(f"✅ Logging to {self.client.start_logging()}")
elif subcmd == "stop":
print(self.client.stop_logging())
elif subcmd == "status":
print("📋", "ACTIVE" if self.client.is_logging_active() else "INACTIVE")
case "graph":
sub = args[0]
if sub == "show":
self.client.visualise_csv_log(args[1])
elif sub == "compare":
print(self.client.compare_test_runs(args[1], args[2]))
case "plot":
plot_type, csv_path = args[0], args[1]
overlay = args[2] if len(args) > 2 else None
print(self.client.generate_plot(csv_path, plot_type, overlay))
case "move_cartesian":
start = self.parse_pose(args[0])
end = self.parse_pose(args[1])
steps = self.extract_value(args, "steps", 50, int)
rate = self.extract_value(args, "rate", 0.04, float)
self.client.move_cartesian_trajectory(start, end, steps, rate)
case "move_joint":
start = self.parse_pose(args[0])
end = self.parse_pose(args[1])
steps = self.extract_value(args, "steps", 50, int)
rate = self.extract_value(args, "rate", 0.04, float)
self.client.move_joint_trajectory(start, end, steps, rate)
case "queue_cartesian":
start = self.parse_pose(args[0])
end = self.parse_pose(args[1])
steps = self.extract_value(args, "steps", 50, int)
rate = self.extract_value(args, "rate", 0.04, float)
self.client.queue_cartesian_trajectory(start, end, steps, rate)
case "queue_joint":
start = self.parse_pose(args[0])
end = self.parse_pose(args[1])
steps = self.extract_value(args, "steps", 50, int)
rate = self.extract_value(args, "rate", 0.04, float)
self.client.queue_joint_trajectory(start, end, steps, rate)
case "execute_queue":
self.client.execute_queued_trajectories()
case "clear_queue":
self.client.clear_trajectory_queue()
case "show_queue":
print(self.client.get_trajectory_queue())
case "export_movement_data":
print(self.client.export_movement_data(args[0]))
case "compare_test_runs":
print(self.client.compare_test_runs(args[0], args[1]))
case "generate_report":
print(self.client.generate_report(args[0], args[1]))
case "safety-stop":
self.client.safety_stop()
case "safety-reset":
self.client.safety_reset()
case "safety-status":
print(self.client.safety_status())
case "safety-set-limit":
var, lo, hi = args
self.client.safety_set_limit(var, lo, hi)
case "krlparse":
self.client.parse_krl_to_csv(args[0], args[1], args[2])
case "inject_rsi":
input_krl = args[0]
output_krl = args[1] if len(args) > 1 else None
rsi_cfg = args[2] if len(args) > 2 else "RSIGatewayv1.rsi"
self.client.inject_rsi(input_krl, output_krl, rsi_cfg)
case "visualize":
self.client.visualise_csv_log(args[0], export="export" in args)
case "help":
self.show_help()
case _:
print("❌ Unknown command. Type 'help'.")
except Exception as e:
print(f"❌ Error: {e}")
def parse_pose(self, pose_string):
return dict(item.split("=") for item in pose_string.split(","))
def extract_value(self, args, key, default, cast_type):
for arg in args[2:]:
if arg.startswith(f"{key}="):
try:
return cast_type(arg.split("=")[1])
except ValueError:
return default
return default
def exit(self):
print("🛑 Exiting RSI CLI...")
self.client.stop_rsi()
self.running = False
def show_help(self):
print("""
Available Commands:
start, stop, exit
set <var> <value>
show, status, ipoc, watch, reset, reconnect
alerts on/off, set_alert_threshold <type> <value>
toggle <group> <name> <state>
move_external <axis> <value>, correct <RKorr/AKorr> <axis> <value>
speed <TechParam> <value>
log start|stop|status
graph show <csv> | graph compare <csv1> <csv2>
plot <type> <csv> [overlay]
move_cartesian, move_joint, queue_cartesian, queue_joint
execute_queue, clear_queue, show_queue
export_movement_data <file>
compare_test_runs <file1> <file2>
generate_report <file> <format>
safety-stop, safety-reset, safety-status, safety-set-limit
krlparse <src> <dat> <output>
inject_rsi <input> [output] [rsi_config]
visualize <csv> [export]
help
""")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="RSI Command-Line Interface")
parser.add_argument("--config", type=str, default="RSI_EthernetConfig.xml",
help="Path to RSI config XML file (default: RSI_EthernetConfig.xml)")
args = parser.parse_args()
cli = RSICommandLineInterface(args.config)
cli.run()