From 154b24f759914186a6b2990f34aed187fe1d2d54 Mon Sep 17 00:00:00 2001 From: "komment-ai[bot]" <122626893+komment-ai[bot]@users.noreply.github.com> Date: Sat, 7 Sep 2024 15:53:04 +0000 Subject: [PATCH 1/4] Added comments to 82 items across 9 files --- examples/LBRJointSineOverlay.py | 114 ++++++++++++++++++ examples/LBRTorqueSineOverlay.py | 106 +++++++++++++++++ examples/LBRWrenchSineOverlay.py | 102 ++++++++++++++++ examples/hand_guide.py | 70 +++++++++++ examples/joint_teleop.py | 114 ++++++++++++++++++ examples/task_teleop.py | 126 ++++++++++++++++++++ pyfri/tools/filters.py | 93 +++++++++++++++ pyfri/tools/state_estimators.py | 196 +++++++++++++++++++++++++++++++ setup.py | 49 ++++++++ 9 files changed, 970 insertions(+) diff --git a/examples/LBRJointSineOverlay.py b/examples/LBRJointSineOverlay.py index 8fc10e3..1a24425 100644 --- a/examples/LBRJointSineOverlay.py +++ b/examples/LBRJointSineOverlay.py @@ -10,7 +10,57 @@ class LBRJointSineOverlayClient(fri.LBRClient): + """ + Extends the `fri.LBRClient` to add a sine wave overlay to joint positions, + with customizable frequency, amplitude, and filter coefficients. It synchronizes + with the robot's state changes and commands new joint positions based on the + calculated offset values. + + Attributes: + joint_mask (npndarray[bool]): Used to specify which joints of a robot are + masked for the sine overlay operation. It filters out unwanted joint + positions from being modified by the sine wave. + freq_hz (float): Used to specify a frequency value in Hertz, representing + the rate at which the sine wave is generated. It is stored as a class + instance variable. + ampl_rad (float): Used to calculate the amplitude of a sine wave that will + be applied to specific joints of a robot, with units of radians. + filter_coeff (float): Used as a coefficient for a low-pass filter in the + generation of the sine wave offset values. + It appears to be used to smoothly ramp up or down the amplitude of the + sine wave over time. + offset (float|npfloat32): Updated using a low-pass filter, with its current + value being mixed (filtered) with a new calculated offset value based + on sine wave amplitude. + phi (float): 0 by default, representing a phase angle incremented over + time to generate sine wave joint position offsets. It resets when the + session state changes to MONITORING_READY. + step_width (float|None): Calculated based on the frequency (freq_hz), + sample time, and 2 * pi in radians. It represents a small angle increment + for sine wave generation. + + """ def __init__(self, joint_mask, freq_hz, ampl_rad, filter_coeff): + """ + Initializes its attributes, including joint mask, frequency, amplitude, + filter coefficient, and phase offsets. It also calls the parent class's + constructor through super(). + + Args: + joint_mask (bool | np.ndarray): 2-dimensional, where each value + represents whether a joint should be considered or not for frequency + filtering. It is typically used to select which joints are relevant + for analysis. + freq_hz (float): Associated with frequency measurement, indicating a + signal's oscillation rate measured in Hertz (Hz). + ampl_rad (float): Named `ampl_rad`. It represents an amplitude value + expressed in radians, suggesting it will be used for calculations + involving rotational or circular quantities. + filter_coeff (float): Used as a coefficient for filtering purposes, + likely related to a low-pass filter or a similar mathematical + operation involving signal processing. + + """ super().__init__() self.joint_mask = joint_mask self.freq_hz = freq_hz @@ -24,6 +74,20 @@ def monitor(self): pass def onStateChange(self, old_state, new_state): + """ + Resets certain parameters when the robot session enters the MONITORING_READY + state, preparing it for sine wave motion generation. It also updates a + step width calculation based on frequency and sample time. + + Args: + old_state (float | int): An enumeration member from fri.ESessionState. + It represents the previous state of the session before it was + changed to the new state passed as `new_state`. + new_state (EnumMember[fri.ESessionState] | None): Implicitly a state + variable representing a session state, updated from an old state, + indicating a change in the system's operational status. + + """ print(f"Changed state from {old_state} to {new_state}") if new_state == fri.ESessionState.MONITORING_READY: self.offset = 0.0 @@ -33,11 +97,23 @@ def onStateChange(self, old_state, new_state): ) def waitForCommand(self): + """ + Sets the joint positions of a robot to match current Ipo joint positions + using numpy float32 data type, presumably preparing the robot for a commanded + action or synchronization with a sine wave motion. + + """ self.robotCommand().setJointPosition( self.robotState().getIpoJointPosition().astype(np.float32) ) def command(self): + """ + Updates the joint position of a robot by applying a sinusoidal offset to + each specified joint, filtered using an exponential decay coefficient. The + updated joint positions are then sent as a command to the robot. + + """ new_offset = self.ampl_rad * math.sin(self.phi) self.offset = (self.offset * self.filter_coeff) + ( new_offset * (1.0 - self.filter_coeff) @@ -51,7 +127,35 @@ def command(self): def args_factory(): + """ + Defines a command-line interface for an application using the `argparse` + library. It creates arguments for various parameters, such as hostnames, ports, + joint masks, and filter coefficients, along with their data types and default + values. + + Returns: + argparseNamespace: A collection of arguments parsed from command-line + input, including their values and help information. This object can be + accessed like an attribute, e.g., `args.hostname`. + + """ def cvt_joint_mask(value): + """ + Converts an input value to an integer and checks if it falls within a + specified range of 0 to 6. If the value is valid, it returns the corresponding + integer; otherwise, it raises an error with a descriptive message. + + Args: + value (Union[int, str]): Required as an argument when this function + is called from the command line using argparse. It represents a + joint mask value to be converted. + + Returns: + int: A representation of the joint position within a predefined range + of [0, 7) if it falls within this range. Otherwise, it raises an error. + The returned integer corresponds to one of seven possible positions. + + """ int_value = int(value) if 0 <= int_value < 7: return int_value @@ -112,6 +216,16 @@ def cvt_joint_mask(value): def main(): + """ + Initializes and controls a robot client application, allowing it to connect + to a KUKA Sunrise controller and perform an LBR Joint Sine Overlay test while + collecting data for analysis and plotting after successful completion. + + Returns: + int: 0 if the program completes normally and 1 if a connection to the KUKA + Sunrise controller fails. + + """ print("Running FRI Version:", fri.FRI_CLIENT_VERSION) args = args_factory() diff --git a/examples/LBRTorqueSineOverlay.py b/examples/LBRTorqueSineOverlay.py index 7ad3204..81e968c 100644 --- a/examples/LBRTorqueSineOverlay.py +++ b/examples/LBRTorqueSineOverlay.py @@ -8,7 +8,49 @@ class LBRTorqueSineOverlayClient(fri.LBRClient): + """ + Monitors and controls a robot arm by applying sinusoidal torques to specific + joints, synchronized with the robot's sampling frequency. It updates torque + values based on the current phase of the sine wave and sends these commands + to the robot. + + Attributes: + joint_mask (numpyndarray|int): Used to specify a subset of joints on which + the torque overlay should be applied. It filters the joints for which + the torques will be calculated. + freq_hz (float): Used to represent a frequency value in Hertz. It appears + to be used as a factor for generating sinusoidal torques in the robot's + joints. + torque_ampl (float): Initialized during object creation with a value + specified by the user, representing the amplitude of the torque signal + to be applied to the joints. + phi (float): Initialized to zero. It represents a phase variable used for + generating sine wave offsets applied to joint torques during + torque-controlled operations. + step_width (float): 0 by default. It is set to a value that represents the + step width for the sine wave function, calculated as twice pi times + the frequency in Hz times the sample time of the robot state. + torques (npndarray[float32]): Initialized as a NumPy array with zeros. It + stores the torques to be applied to specific joints based on a sine + wave pattern. + + """ def __init__(self, joint_mask, freq_hz, torque_amplitude): + """ + Initializes the object's attributes: joint mask, frequency, torque amplitude, + and state variables (phi, step width, and torques) with default values or + those provided as arguments. It also calls the parent class's `__init__` + method using super(). + + Args: + joint_mask (numpy.ndarray | List[bool]): 1D array-like object containing + boolean values, where each value corresponds to a joint and indicates + whether it should be included or excluded from the simulation. + freq_hz (float): Used to specify the frequency in Hertz of the torques + applied by the system. + torque_amplitude (float): 3 times larger than `freq_hz`. + + """ super().__init__() self.joint_mask = joint_mask self.freq_hz = freq_hz @@ -21,6 +63,21 @@ def monitor(self): pass def onStateChange(self, old_state, new_state): + """ + Monitors state changes and resets internal variables when transitioning + to the MONITORING_READY state. It initializes torques, phase angle, and + step width based on the robot's sample time and frequency. + + Args: + old_state (fri.ESessionState): Used to record the state of the session + before the current change, which is then printed by the function + as it changes. + new_state (Union[fri.ESessionState, str]): Not fully specified in this + code snippet, indicating that it may contain different values from + the fri.ESessionState enum, which represents various states in the + robotics session. + + """ print(f"State changed from {old_state} to {new_state}") if new_state == fri.ESessionState.MONITORING_READY: @@ -31,12 +88,24 @@ def onStateChange(self, old_state, new_state): ) def waitForCommand(self): + """ + Sets joint positions and torques based on current state before sending new + commands to the robot, ensuring a smooth transition between commands when + client command mode is torque control. + + """ self.robotCommand().setJointPosition(self.robotState().getIpoJointPosition()) if self.robotState().getClientCommandMode() == fri.EClientCommandMode.TORQUE: self.robotCommand().setTorque(self.torques) def command(self): + """ + Sends torque commands to a robot, applying a sine wave pattern to selected + joints. It updates joint positions and torques based on the current client + command mode and phase of the sine wave. + + """ self.robotCommand().setJointPosition(self.robotState().getIpoJointPosition()) if self.robotState().getClientCommandMode() == fri.EClientCommandMode.TORQUE: @@ -52,7 +121,34 @@ def command(self): def args_factory(): + """ + Initializes an argument parser for a command-line interface, validating user + input and converting it into a format suitable for further processing by a + KUKA Sunrise Controller. It returns parsed arguments as namespace objects. + + Returns: + argparseNamespace: A container object that stores all arguments passed to + it, along with their default values if provided. It encapsulates all parsed + command-line arguments into a structured format for further processing and + use within the application. + + """ def cvt_joint_mask(value): + """ + Converts a given value to an integer within the range 0 to 6 and returns + it if valid. It checks for values outside this range, raising an error + with an informative message if encountered. + + Args: + value (Union[int, str]): Implicitly defined by argparse which typically + expects it to be a string that represents an integer. + + Returns: + int|None: An integer value within the range from 0 to 6 (inclusive), + if the input is valid; otherwise, it raises a TypeError with a message + describing the invalid input. + + """ int_value = int(value) if 0 <= int_value < 7: return int_value @@ -99,6 +195,16 @@ def cvt_joint_mask(value): def main(): + """ + Initializes a KUKA Sunrise controller client, connects to it, and enters a + loop where it steps the application until the session state becomes idle or a + keyboard interrupt occurs. + + Returns: + int: 1 if a connection to KUKA Sunrise controller fails, and 0 otherwise, + indicating successful execution. + + """ print("Running FRI Version:", fri.FRI_CLIENT_VERSION) args = args_factory() diff --git a/examples/LBRWrenchSineOverlay.py b/examples/LBRWrenchSineOverlay.py index 2fdfc69..d621259 100644 --- a/examples/LBRWrenchSineOverlay.py +++ b/examples/LBRWrenchSineOverlay.py @@ -8,7 +8,59 @@ class LBRWrenchSineOverlayClient(fri.LBRClient): + """ + Generates a sine wave pattern for joint positions and wrench values to be sent + to a robot arm during monitoring or commanding periods. It adjusts the phase + of the sine waves based on time steps and resets them at each cycle. + + Attributes: + frequencyX (float): Initialized with a value received during object creation, + which represents the frequency of the sine wave to be applied along + the x-axis. + frequencyY (float): Stored as a private instance variable of the class. + It represents the frequency of the sinusoidal component along the + Y-axis in the wrench command generated by this client. + amplitudeX (float): Set to a user-provided value during initialization. + It determines the amplitude of the sine wave applied to axis X of the + wrench. + amplitudeY (float): 1:1 associated with a sine wave's maximum displacement + from its equilibrium position along the Y-axis. + stepWidthX (float): 0.0 by default, initially not being used but later + calculated based on the frequency of a sine wave applied to a robot's + joint. + stepWidthY (float): 0 by default. It stores the time interval between two + sine wave samples along the y-axis, calculated as `2.0 * pi * frequencyY + * sampleTime`. + phiX (float): 0 by default. It represents a phase offset used to calculate + a sine wave for the x-axis in wrench commands. + phiY (float): Initialized to 0. It accumulates a value that increases at + each call of the `command` method, representing the phase shift in + radians for the sine wave applied along the Y-axis of the wrench command. + wrench (npndarray|List[float]): 6 elements long. It represents a set of + six force/torque values to be applied to a robot in three-dimensional + space: forces along the x, y, z axes and torques around these axes. + + """ def __init__(self, frequencyX, frequencyY, amplitudeX, amplitudeY): + """ + Initializes its attributes, setting default values for certain variables + and importing np to create a 6-element array representing a wrench. It + also calls the parent class's `__init__` method using super(). + + Args: + frequencyX (float): Used to represent the frequency in the X direction + of an oscillating signal or wave. It is initialized with a provided + value within the function's call. + frequencyY (float): Initialized with user-provided value `frequencyY`. + It represents the frequency of oscillation in the y-direction of + an object or system, likely used for modeling or simulation purposes. + amplitudeX (float): Used to specify the amplitude (magnitude) of the + x-component of a signal, such as a wave or oscillation. It represents + the maximum displacement from equilibrium. + amplitudeY (float): Used to initialize an instance variable representing + the amplitude along the Y-axis of the object's movement or oscillation. + + """ super().__init__() self.frequencyX = frequencyX self.frequencyY = frequencyY @@ -24,6 +76,20 @@ def monitor(self): pass def onStateChange(self, old_state, new_state): + """ + Resets certain attributes when the robot enters the MONITORING_READY state, + likely indicating a change in operational mode or readiness for motion + control. It also calculates step widths based on frequency and sample time. + + Args: + old_state (fri.ESessionState): Referenced when determining whether a + state change has occurred from a previous state to the new state, + which is specified by `new_state`. + new_state (EnumMember[fri.ESessionState]): Compared to + fri.ESessionState.MONITORING_READY to check if the session state + has changed into monitoring ready state. + + """ if new_state == fri.ESessionState.MONITORING_READY: self.phiX = 0.0 self.phiY = 0.0 @@ -35,11 +101,23 @@ def onStateChange(self, old_state, new_state): ) def waitForCommand(self): + """ + Sends the current joint position to the robot and sets the wrench value + if the client command mode is WRENCH. This ensures synchronization between + the robot's state and the client's commands. + + """ self.robotCommand().setJointPosition(self.robotState().getIpoJointPosition()) if self.robotState().getClientCommandMode() == fri.EClientCommandMode.WRENCH: self.robotCommand().setWrench(self.wrench) def command(self): + """ + Sets the joint position and wrench commands for a robotic arm. The wrench + command is generated based on sine wave amplitudes that increase over time, + simulating an oscillating force applied to the arm in both X and Y directions. + + """ self.robotCommand().setJointPosition(self.robotState().getIpoJointPosition()) if self.robotState().getClientCommandMode() == fri.EClientCommandMode.WRENCH: self.wrench[0] = self.amplitudeX * math.sin(self.phiX) @@ -58,6 +136,18 @@ def command(self): def args_factory(): + """ + Parses command-line arguments using the `argparse` module, allowing for + configuration of application settings such as hostname, port, and + frequencies/amplitudes of sine waves. The parsed arguments are returned as an + object. + + Returns: + argparseNamespace: An object containing all arguments parsed from command + line input, such as hostname, port, frequencyX, frequencyY, amplitudeX, + and amplitudeY. + + """ parser = argparse.ArgumentParser(description="LRBJointSineOverlay example.") parser.add_argument( "--hostname", @@ -105,6 +195,18 @@ def args_factory(): def main(): + """ + Initializes a client application for interacting with a KUKA Sunrise controller, + connects to it, runs an infinite loop where it steps through the application + and checks the robot's state until idle or interrupted by a keyboard signal, + then disconnects from the controller. + + Returns: + int|None: 0 when connection to KUKA Sunrise controller is successful and + 1 otherwise, depending on whether the function was able to connect to the + controller successfully or not. + + """ print("Running FRI Version:", fri.FRI_CLIENT_VERSION) args = args_factory() diff --git a/examples/hand_guide.py b/examples/hand_guide.py index d56a529..066fc55 100644 --- a/examples/hand_guide.py +++ b/examples/hand_guide.py @@ -19,7 +19,45 @@ class HandGuideClient(fri.LBRClient): + """ + Initializes and manages a robotic arm's state, commanding its position based + on estimated joint states and external wrenches. It updates the robot's pose, + checks command mode, and filters wrench estimates to ensure smooth motion control. + + Attributes: + controller (AdmittanceController|None): Initialized with an instance of + AdmittanceController in the constructor. It represents a controller + used for controlling the robot's joints based on estimated wrenches + and other factors. + joint_state_estimator (JointStateEstimator|None): Initialized with the + instance itself as its argument, indicating it's responsible for + estimating joint states based on current conditions. + external_torque_estimator (FRIExternalTorqueEstimator|None): Initialized + with its constructor being called on self, which suggests it estimates + external torques acting on the robot. + wrench_estimator (WrenchEstimatorTaskOffset|JointStateEstimator|FRIExternalTorqueEstimator): + Associated with a robot, ee link, and joint state estimator. It estimates + task-space forces and torques from joint sensor readings and external + torque estimations. + wrench_filter (ExponentialStateFilter): Used to filter wrench data from + the `WrenchEstimatorTaskOffset`. It appears to be a state estimation + method to provide more accurate results. + + """ def __init__(self, lbr_ver): + """ + Initializes objects for admittance control, joint state estimation, external + torque estimation, wrench estimation, and exponential state filtering based + on provided parameters. It also establishes relationships between these + components for coordinated functioning. + + Args: + lbr_ver (str | int): Used to specify the version or identifier of the + robot. This value is passed to the AdmittanceController class + during initialization. It appears to be related to the robotic + arm's model. + + """ super().__init__() self.controller = AdmittanceController(lbr_ver) self.joint_state_estimator = JointStateEstimator(self) @@ -43,6 +81,12 @@ def onStateChange(self, old_state, new_state): print(f"State changed from {old_state} to {new_state}") def waitForCommand(self): + """ + Waits for a command from the client to be executed. It checks if the current + client mode matches POSITION, updates the wrench estimator, retrieves the + current joint position, and executes the command position. + + """ if self.robotState().getClientCommandMode() != POSITION: print( f"[ERROR] hand guide example requires {POSITION.name} client command mode" @@ -54,6 +98,12 @@ def waitForCommand(self): self.command_position() def command(self): + """ + Updates robot position commands by filtering and processing wrench data + from the wrench estimator, then calls the `controller` function to compute + new joint positions. It also sets the joint position command of the robot. + + """ if not self.wrench_estimator.ready(): self.wrench_estimator.update() self.robotCommand().setJointPosition(self.q.astype(np.float32)) @@ -74,6 +124,16 @@ def command(self): def args_factory(): + """ + Creates an instance of the ArgumentParser class and defines arguments for + parsing command-line input. It returns a Namespace object containing user-specified + values for hostname, port, and LBR Med version number. + + Returns: + argparseNamespace: An object containing arguments parsed from the command + line. This object holds attributes for `hostname`, `port`, and `lbr_ver`. + + """ parser = argparse.ArgumentParser(description="LRBJointSineOverlay example.") parser.add_argument( "--hostname", @@ -101,6 +161,16 @@ def args_factory(): def main(): + """ + Initializes a connection to a KUKA Sunrise controller, attempts to establish + communication, and repeatedly polls the robot's state until idle or an error + occurs. It also handles keyboard interrupts and system exits before disconnecting + from the controller and terminating. + + Returns: + int: 1 when connection to KUKA Sunrise controller fails, and 0 otherwise. + + """ print("Running FRI Version:", fri.FRI_CLIENT_VERSION) args = args_factory() diff --git a/examples/joint_teleop.py b/examples/joint_teleop.py index cc176ba..557a8da 100644 --- a/examples/joint_teleop.py +++ b/examples/joint_teleop.py @@ -13,7 +13,35 @@ class Keyboard: + """ + Initializes a pygame display and sets up event handling for keyboard input. + It manages joint activation and direction control using keys 1-7 and left/right + arrow keys, respectively, with velocity limits. + + Attributes: + max_joint_velocity (float): 5 degrees in radians converted to a radian + measure using numpy's `deg2rad` function, limiting the maximum velocity + of the joint when controlled by keyboard input. + joint_index (Optional[int]|NoneType): Initialized to None. It keeps track + of the index of a joint that has been turned on, allowing the program + to control multiple joints with different keys. + joint_velocity (float|NoneType): 0.0 by default. It represents the current + velocity of a mechanical joint, initially at rest (0.0), which can be + changed based on user input to control the joint's movement speed. + key_to_dir_map (Dict[pygameK_LEFT,float]|Dict[pygameK_RIGHT,float]): Used + to map keyboard keys (LEFT, RIGHT) to direction values (1.0, -1.0). + It determines how movement is controlled when using these keys. + key_joint_map (List[int]): Mapped to a list of Pygame key constants, + representing the keys on the keyboard that control the joints. + + """ def __init__(self): + """ + Initializes Pygame's display mode and sets up various variables for handling + keyboard input, including joint velocity limits, direction mappings, and + key associations with joints. + + """ pygame.display.set_mode((300, 300)) self.max_joint_velocity = np.deg2rad(5) @@ -37,6 +65,19 @@ def __init__(self): ] def __call__(self): + """ + Handles events from the pygame library, allowing the user to control joints + and change their velocity through keyboard input. It also allows the user + to quit the application by pressing the ESC key or closing the window. + + Returns: + Tuple[int,float]: A tuple containing two values: + + 1/ The index of the currently active joint (or None if no joint is active). + 2/ A scalar value representing the current velocity of all joints, + scaled by a factor that can be accessed through self.max_joint_velocity. + + """ for event in pygame.event.get(): if event.type == pygame.QUIT: # User closed pygame window -> shutdown @@ -74,7 +115,32 @@ def __call__(self): class TeleopClient(fri.LBRClient): + """ + Handles teleoperation of a robot using keyboard input. It monitors the robot's + state, receives user commands through keyboard inputs, and sends joint position + and torque commands to the robot based on these inputs. + + Attributes: + keyboard (Keyboard): Associated with a keyboard interface. It appears to + be used to track user input, such as key presses or releases, that + control robot joint movements. + torques (npndarray[float32]): Initialized to zero with a size corresponding + to the number of joints in the robot (7). It stores torque values for + each joint. + + """ def __init__(self, keyboard): + """ + Initializes an instance with a keyboard object and sets up its attributes, + including a torques array initialized as zeros with the number of joints + specified by fri.LBRState.NUMBER_OF_JOINTS. + + Args: + keyboard (object): Referenced but not defined within this snippet. It + is expected to be an instance of a class that has been imported + from elsewhere. + + """ super().__init__() self.keyboard = keyboard self.torques = np.zeros(fri.LBRState.NUMBER_OF_JOINTS) @@ -83,6 +149,20 @@ def monitor(self): pass def onStateChange(self, old_state, new_state): + """ + Prints state change information, sets robot control parameters when the + robot reaches a specific monitoring ready state, and prints control + instructions to the user. + + Args: + old_state (fri.ESessionState | str): Used to store the previous state + before it was changed to `new_state`. It represents the previous + session state or status of the robot system. + new_state (Enum[fri.ESessionState]): An enumeration value representing + the new state of the robot session, specifically the "MONITORING_READY" + state when changed. + + """ print(f"State changed from {old_state} to {new_state}") if new_state == fri.ESessionState.MONITORING_READY: @@ -100,6 +180,12 @@ def onStateChange(self, old_state, new_state): ) def waitForCommand(self): + """ + Waits for joint position and torque commands from the robot, then sends + these values to the robot's state machine. The function updates the robot's + joint positions and applies torques according to the client command mode. + + """ self.q = self.robotState().getIpoJointPosition() self.robotCommand().setJointPosition(self.q.astype(np.float32)) @@ -107,6 +193,12 @@ def waitForCommand(self): self.robotCommand().setTorque(self.torques.astype(np.float32)) def command(self): + """ + Sends joint position and torque commands to a robotic arm, based on user + input from a keyboard or other device. It updates the robot's joint positions + and torques accordingly. + + """ joint_index, vgoal = self.keyboard() if isinstance(joint_index, int): @@ -119,6 +211,18 @@ def command(self): def args_factory(): + """ + Parses command-line arguments using the `argparse` library. It defines two + optional arguments: `hostname` and `port`, both with default values, to be + used for communication with a KUKA Sunrise Controller. The parsed arguments + are returned as an object. + + Returns: + Namespace[hostnamestr,portint]: A named collection of arguments that were + passed to the script. It contains the hostname and port number parsed from + command-line arguments or default values if not provided. + + """ parser = argparse.ArgumentParser(description="LRBJointSineOverlay example.") parser.add_argument( "--hostname", @@ -138,6 +242,16 @@ def args_factory(): def main(): + """ + Initializes a KUKA Sunrise controller teleoperation application, connects to + it, and enters a loop where it continuously steps through the robot's state + until idle or interrupted. It then disconnects and exits. + + Returns: + int: 0 if the execution completes successfully and 1 otherwise, indicating + failure to connect to the KUKA Sunrise controller. + + """ print("Running FRI Version:", fri.FRI_CLIENT_VERSION) args = args_factory() diff --git a/examples/task_teleop.py b/examples/task_teleop.py index 40e416a..2a664b5 100644 --- a/examples/task_teleop.py +++ b/examples/task_teleop.py @@ -20,6 +20,13 @@ def print_instructions(): + """ + Displays a set of instructions to the user regarding control of robot joints + using keyboard keys and enabling specific task axes through key presses. The + instructions are printed to the console, emphasizing the need for focus on the + PyGame window. + + """ print("\n") print("-" * 65) print("-- Control robot joints using LEFT/RIGHT direction keys. --") @@ -29,7 +36,37 @@ def print_instructions(): class Keyboard: + """ + Manages keyboard input to control a virtual task. It handles quit events, key + presses for task selection and direction modification, and updates task velocity + accordingly. The task index is returned along with its velocity scaled by a + maximum velocity constant. + + Attributes: + max_task_velocity (float): 0.04 by default. This attribute limits the + maximum velocity at which tasks are performed. It serves as a bound + for the task's velocity. + task_index (NoneType|int): Used to keep track of the currently active task. + It defaults to None, indicating that no task is currently active. When + a key corresponding to a specific task is pressed or released, its + value updates accordingly. + task_velocity (float): Initialized to zero. It represents the velocity of + a task being controlled by the keyboard input. Its value changes when + specific keys are pressed or released. + key_to_dir_map (Dict[int,float]): Initialized with two key-value pairs + that map keyboard keys to direction values used for task velocity adjustments. + key_task_map (OrderedDict[int,str]): Initialized with key-value pairs that + map Pygame keys to task labels: x, y, z, rx, ry, rz. These tasks are + presumably controlled by the keyboard. + + """ def __init__(self): + """ + Initializes several instance variables and sets up key mappings for Pygame + events. These include task velocity, keyboard keys for directional movement + and task assignments, and an ordered dictionary to store key-task mappings. + + """ pygame.display.set_mode((300, 300)) self.max_task_velocity = 0.04 @@ -51,6 +88,19 @@ def __init__(self): self.key_task_map[pygame.K_a] = "rz" def __call__(self): + """ + Processes Pygame events to handle keyboard inputs and updates task velocity + accordingly. When specific keys are pressed, tasks are toggled on or off; + pressing movement keys modifies task direction and speed. Quitting the + program occurs upon QUIT event. + + Returns: + Tuple[int,float]: A pair of an integer and a float number. The first + element of the tuple represents the current task index, while the + second element represents the magnitude of the task's velocity scaled + to its maximum possible velocity. + + """ for event in pygame.event.get(): if event.type == pygame.QUIT: # User closed pygame window -> shutdown @@ -89,7 +139,36 @@ def __call__(self): class TeleopClient(fri.LBRClient): + """ + Handles user input through a keyboard interface and controls a robotic arm + using inverse kinematics and torque control, transitioning between monitoring + ready state and issuing commands based on keyboard inputs and joint positions. + + Attributes: + ik (callable): Associated with an instance of Inverse Kinematics solver, + which solves the forward kinematics problem to compute joint positions + given a target position or orientation. + keyboard (object): Used to obtain task index and velocity goal values + through its method call, allowing user input and control over robot actions. + torques (npndarray[float32]): Initialized as a zero-filled array with shape + NUMBER_OF_JOINTS, where NUMBER_OF_JOINTS is defined by fri.LBRState.NUMBER_OF_JOINTS. + + """ def __init__(self, ik, keyboard): + """ + Initializes object attributes: ik (inverse kinematics), keyboard, and + torques. It sets up an array of zeros to store torques for each joint based + on a predefined number from fri.LBRState.NUMBER_OF_JOINTS. + + Args: + ik (InvertedKinematics): Assigned to an instance variable called + `self.ik`. It represents an object that performs inverse kinematics + calculations for the robot's joints. + keyboard (object | Dict[str, Any]): Expected to be passed when creating + an instance of this class. Its actual type or behavior is not + specified by the code snippet provided. + + """ super().__init__() self.ik = ik self.keyboard = keyboard @@ -99,6 +178,20 @@ def monitor(self): pass def onStateChange(self, old_state, new_state): + """ + Monitors state changes and triggers specific actions when entering the + MONITORING_READY state. It resets internal variables, clears a queue, and + initializes torques to zero before printing instructions. + + Args: + old_state (fri.ESessionState): Passed to the function when its state + changes, representing the previous state before the change occurred. + Its value is used for logging purposes within the function body. + new_state (str | Enum): Used to store the current state of an object, + specifically the monitoring ready state of a session. It represents + the new status after a state change event occurred. + + """ print(f"State changed from {old_state} to {new_state}") if new_state == fri.ESessionState.MONITORING_READY: @@ -108,6 +201,11 @@ def onStateChange(self, old_state, new_state): print_instructions() def waitForCommand(self): + """ + Synchronizes the robot's actual and commanded states by setting joint + positions and torques according to the current command mode. + + """ self.q = self.robotState().getIpoJointPosition() self.robotCommand().setJointPosition(self.q.astype(np.float32)) @@ -115,6 +213,13 @@ def waitForCommand(self): self.robotCommand().setTorque(self.torques.astype(np.float32)) def command(self): + """ + Updates the robot's joint positions and torques based on user input from + the keyboard, converting user-defined goals into control commands for the + robot. It ensures that the command mode is set accordingly to either + position or torque control. + + """ task_index, vgoal = self.keyboard() if isinstance(task_index, int): @@ -130,6 +235,17 @@ def command(self): def args_factory(): + """ + Parses command line arguments using the `argparse` library and returns them + as a parsed argument object. It defines three positional or optional arguments: + hostname, port number, and LBR Med version number. The function is designed + to configure communication settings with a KUKA Sunrise Controller. + + Returns: + argparseNamespace|argparseArgumentParser: A parsed object containing + arguments that are used to initialize an application or module. + + """ parser = argparse.ArgumentParser(description="LRBJointSineOverlay example.") parser.add_argument( "--hostname", @@ -157,6 +273,16 @@ def args_factory(): def main(): + """ + Establishes a connection to a KUKA Sunrise controller, runs a teleoperation + client application, and continuously steps through the application until it + reaches an idle state or encounters an error. + + Returns: + int: 0 if the execution completes successfully and 1 if it fails, indicating + a non-zero status code. + + """ print("Running FRI Version:", fri.FRI_CLIENT_VERSION) args = args_factory() diff --git a/pyfri/tools/filters.py b/pyfri/tools/filters.py index 7695ebf..480ede0 100644 --- a/pyfri/tools/filters.py +++ b/pyfri/tools/filters.py @@ -4,7 +4,32 @@ class StateFilter(abc.ABC): + """ + Provides a base implementation for filtering state data over a fixed window + size. It initializes with a specified window size and allows appending new + data points, maintaining a deque to store the recent values within the defined + window. + + Attributes: + _window_size (int): Set during initialization of an instance of this class + through its constructor (`__init__`). It specifies the maximum number + of values that a filter window can hold. + reset (Callable[[None],None]): A method that resets the internal state of + the filter by clearing its window to the specified size. + + """ def __init__(self, window_size): + """ + Initializes an instance with a specified window size and calls the `reset` + method to set the initial state. It stores the provided window size as an + attribute `_window_size`. + + Args: + window_size (int): Mandatory for initialization of the object. It + represents the size of the window and it determines the scope or + range of data considered by the object. + + """ self._window_size = window_size self.reset() @@ -16,15 +41,60 @@ def append(self, x): @abc.abstractmethod def filter(self, x): + """ + Takes one argument, an object x, and returns nothing by default, indicating + that it must be implemented by any subclass of this abstract base class. + Its purpose is to filter or modify objects based on specific criteria. + + Args: + x (Any): Positional-only, indicating it must be passed as an argument + without any keyword prefix. It represents the input value to be filtered. + + """ pass class ExponentialStateFilter(StateFilter): + """ + Implements an exponential moving average filter, which calculates a weighted + average between new and previous values, with a smoothing factor to control + the rate at which the filtered value adapts to changes in the input. + + Attributes: + _smooth (float): Initialized with a default value of 0.02. It represents + the smoothing coefficient for exponential moving average calculations. + + """ def __init__(self, smooth=0.02): + """ + Initializes an instance with optional smoothing parameter, defaulting to + 0.02, and calls its superclass's constructor with a fixed value of 1. It + stores the smoothing parameter in an attribute named `_smooth`. + + Args: + smooth (float): 0.02 by default. It is used to initialize an instance + variable `_smooth`. + + """ super().__init__(1) self._smooth = smooth def filter(self, x): + """ + Updates an exponential moving average (EMA) filter with a new data point + and returns the filtered value. The EMA combines past values using a + smoothing factor, giving more weight to recent data points. + + Args: + x (float | numpy.ndarray): A value to be filtered by the exponential + moving average (EMA) algorithm. It represents the new data point + to be included in the EMA computation. + + Returns: + float: A smoothed version of the input `x`, calculated using a weighted + average between `x` and the oldest element in the `_window` list. + + """ if len(self._window) == 0: self.append(x) xp = np.array(self._window[0]) @@ -34,6 +104,29 @@ def filter(self, x): class MovingAverageFilter(StateFilter): + """ + Filters data by maintaining a window of values and computing their mean at + each time step. It extends the `StateFilter` class, overriding its `filter` + method to append new input and return the current window's average as output. + + """ def filter(self, x): + """ + Calculates and returns the mean value of the current window's elements + along axis 0, simultaneously updating the internal state by appending a + new element to it. + + Args: + x (array_like): 1-dimensional. It represents a data point or sample + to be processed by the filter algorithm. The exact structure and + content depend on the context in which this code snippet is used. + + Returns: + numpyndarray: The mean of all values currently storedinself._window. + The axis=0 argument indicates that the mean should be calculated along + each column, resulting in a one-dimensional array with the same number + of elements as there are columns in self._window. + + """ self.append(x) return np.mean(self._window, axis=0) diff --git a/pyfri/tools/state_estimators.py b/pyfri/tools/state_estimators.py index b30d721..22b3240 100644 --- a/pyfri/tools/state_estimators.py +++ b/pyfri/tools/state_estimators.py @@ -28,6 +28,17 @@ def __init__( self, client, ): + """ + Updates the client's command function to call `_update_window` before + executing the original command, thereby updating the estimator's internal + window with new data. + + Args: + client (object | ClientClass): Assigned to instance variable `_client`. + It appears to represent an external client or API, which is being + wrapped by the current class for monitoring. + + """ # Set class attributes/variables self._client = client self._first_update = True @@ -39,6 +50,17 @@ def __init__( orig_command = self._client.command def command(*args, **kwargs): + """ + Updates a window before executing an original command. It does this + by calling `_update_window` and then passing any arguments to the + `orig_command`. This allows window updates to be performed consistently + before executing user commands. + + Args: + *args (list): List of positional arguments + **kwargs (dict): Dictionary of keyword arguments + + """ self._update_window() orig_command(*args, **kwargs) @@ -54,6 +76,12 @@ def ddq(self, idx): return np.array(self._ddq[idx]) def _update_window(self): + """ + Updates internal state variables with new joint position, velocity, and + acceleration data from a robot state client. It also performs data padding + for initial window samples and calculates derivatives using finite differences. + + """ # Retrieve state dt = self._client.robotState().getSampleTime() q = self._client.robotState().getMeasuredJointPosition().flatten().tolist() @@ -98,6 +126,27 @@ class TaskSpaceStateEstimator: def __init__( self, client, joint_state_estimator, robot_model, ee_link, base_link=None ): + """ + Initializes an object by setting up transforms and Jacobians for task space + estimation based on provided robot model, end-effector link, client, and + optional base link. + + Args: + client (object): Required for initialization, but its actual usage or + purpose is not specified within this code snippet. + joint_state_estimator (object): Required for the class initialization. + It likely refers to an estimator used to estimate joint states + (positions, velocities) from other sensor data or measurements. + robot_model (object): Required for initialization. It represents a + model of a robot, likely used to retrieve transformation functions. + ee_link (str): An end effector link, typically the last joint or tool + attached to a robot arm, specifying its location in the kinematic + tree. + base_link (Union[NoneType, str]): Optional. It represents a reference + frame from which to compute the transform and Jacobian of an + end-effector link. + + """ self._client = client self._joint_state_estimator = joint_state_estimator @@ -120,16 +169,50 @@ def __init__( raise ValueError(f"{base_link=} was not recognized") def get_transform(self): + """ + Computes and returns a transformation matrix, likely a homogeneous + transformation matrix, based on the current position as estimated by the + joint state estimator. The actual transformation is performed by calling + the _T function with the q argument. + + Returns: + numpyndarray: 4x4 matrix representing a transformation that is calculated + based on the current position. This transformation is derived from an + object's internal state estimator, which is likely a component of a + larger robotic system. + + """ q = self._joint_state_estimator.get_position() return self._T(q) def get_velocity(self): + """ + Computes the velocity in task space by taking the dot product of the + Jacobian matrix `J` and the joint velocities `dq`, with both derived from + a joint state estimator. + + Returns: + numpyndarray: A vector representing the linear velocity of a rigid + body at its center of mass, calculated based on joint velocities and + their Jacobian transformation. + + """ q = self._joint_state_estimator.get_position() dq = self._joint_state_estimator.get_velocity() J = self._J(q) return J @ dq def get_acceleration(self): + """ + Calculates the acceleration of the end effector in task space by numerically + differentiating the velocity vector with respect to time using two consecutive + sample times and joint state estimates. + + Returns: + numpyndarray: A representation of the acceleration of the robot's + joints based on previous velocity measurements and sample time. + + """ # Retreive joint states q = self._joint_state_estimator.q(-1) qp = self._joint_state_estimator.q(-2) @@ -146,12 +229,36 @@ def get_acceleration(self): class ExternalTorqueEstimator(abc.ABC): + """ + Defines an abstract base class that must be implemented by subclasses to + estimate external torques. It includes a single method, `get_external_torque`, + which is not implemented and must be overridden in concrete implementations + to return the estimated external torque. + + """ @abc.abstractmethod def get_external_torque(self): + """ + Returns an estimate of the external torque acting on a system or object. + It is declared as abstract, requiring concrete implementations from its + subclasses to provide actual torque estimation logic. + + """ pass class FRIExternalTorqueEstimator(ExternalTorqueEstimator): + """ + Provides an estimate of external torques acting on a robot, utilizing data + from a client that interfaces with the robot's state information. It retrieves + and flattens the external torque values obtained through the client's API. + + Attributes: + _client (object|FRIProtocolClient): Initialized by passing it to the + constructor during object creation through the parameter client. It + holds a reference to a client object used for communication with a robot. + + """ def __init__(self, client): self._client = client @@ -191,6 +298,34 @@ def __init__( base_link=None, n_data=50, ): + """ + Initializes object attributes, including estimators and robot model + components, for computing wrenches based on joint states and external + torques, given a tip link and base link configuration. + + Args: + client (object | None): Referenced but not used within the method. Its + purpose or usage is not clear from this snippet of code. + joint_state_estimator (object): Stored as an instance variable + `_joint_state_estimator`. It appears to be an estimator for joint + states, possibly from sensor data or other sources. + external_torque_estimator (object): Expected to implement an external + torque estimation capability. Its purpose is not explicitly stated, + but its name suggests it estimates torques from sources outside + the robot's mechanical system. + robot_model (RobotModel): Used to get Jacobian functions for geometric + calculations. It provides methods such as `get_global_link_geometric_jacobian_function` + or `get_link_geometric_jacobian_function`. + tip_link (str | int): Required for initializing an instance of this + class. It represents the final link on the robot's kinematic chain + that is used as a reference point for various calculations. + base_link (str | NoneType): Optional. It can either be omitted (None) + or set to a string representing the name of a link in the robot + model, used as an alternative base for calculating the Jacobian. + n_data (int | None): 50 by default, representing the number of data + points or samples to be stored within the class instance. + + """ # Create class attributes self._joint_state_estimator = joint_state_estimator self._external_torque_estimator = external_torque_estimator @@ -215,6 +350,12 @@ def __init__( self._data = [] def _inverse_jacobian(self): + """ + Calculates the Moore-Penrose pseudoinverse of the Jacobian matrix, given + by the input position from the joint state estimator and a conditioning + parameter `rcond`. The pseudoinverse is used to invert the Jacobian. + + """ q = self._joint_state_estimator.get_position() return np.linalg.pinv(self._jacobian(q), rcond=self._rcond) @@ -222,15 +363,34 @@ def ready(self): return len(self._data) >= self._n_data def update(self): + """ + Checks if the current data length is less than the required data length. + If true, it calls the `_update_data` method to update the internal state, + ensuring that the required amount of data is always available for estimation. + + """ if len(self._data) < self._n_data: self._update_data() @abc.abstractmethod def _update_data(self): + """ + Serves as an abstract method, requiring derived classes to implement it. + This method likely updates internal data used by the estimator to perform + calculations or operations. Its implementation is not provided and must + be defined elsewhere in the codebase. + + """ pass @abc.abstractmethod def get_wrench(self): + """ + Returns a wrench object, which represents an estimation or calculation of + a wrench force or torque in a mechanical system. The actual implementation + is abstract and must be provided by concrete subclasses. + + """ pass @@ -246,10 +406,28 @@ class WrenchEstimatorJointOffset(WrenchEstimator): """ def _update_data(self): + """ + Retrieves an external torque estimate from its associated estimator, + converts it to a list, and appends this value to the data stored by the + object for subsequent use or analysis. + + """ tau_ext = self._external_torque_estimator.get_external_torque() self._data.append(tau_ext.tolist()) def get_wrench(self): + """ + Calculates the estimated wrench (torque and force) acting on a joint by + subtracting an offset from the external torque, then multiplying by the + inverse Jacobian transpose. This is used to correct for bias in the torque + estimator. + + Returns: + ndarray: 1D array representing the wrench applied to a robotic arm or + end effector, calculated by taking the transpose of the inverse Jacobian + matrix and multiplying it with the estimated external torque. + + """ offset = np.mean(self._data, axis=0) tau_ext = self._external_torque_estimator.get_external_torque() - offset Jinv = self._inverse_jacobian() @@ -268,11 +446,29 @@ class WrenchEstimatorTaskOffset(WrenchEstimator): """ def _update_data(self): + """ + Updates the internal data with the estimated external force. It retrieves + the estimated external torque, computes the corresponding force through a + matrix multiplication and flattens it into a list before appending it to + the internal data. + + """ tau_ext = self._external_torque_estimator.get_external_torque() f_ext = self._inverse_jacobian().T @ tau_ext self._data.append(f_ext.flatten().tolist()) def get_wrench(self): + """ + Computes an estimate of the wrench acting on an end-effector by subtracting + a task space offset from the product of the inverse jacobian and external + torque. + + Returns: + ndarray: 1-dimensional, representing a wrench (force and torque) exerted + on an object, calculated by combining external torque and inverse + jacobian transformation with data offset adjustment. + + """ offset = np.mean(self._data, axis=0) tau_ext = self._external_torque_estimator.get_external_torque() return self._inverse_jacobian().T @ tau_ext - offset diff --git a/setup.py b/setup.py index aebe38d..476967a 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,13 @@ # Read the configuration settings class UserInputRequired(Exception): + """ + Defines a custom exception to handle situations where user input is missing + or incomplete. It inherits from Python's built-in `Exception` class and takes + an error message as an argument, allowing for customizable error handling and + messaging. + + """ def __init__(self, msg): super().__init__(msg) @@ -33,13 +40,55 @@ def __init__(self, msg): # The name must be the _single_ output extension from the CMake build. # If you need multiple extensions, see scikit-build. class CMakeExtension(Extension): + """ + Initializes a C extension for use with Pybind11. It sets up the extension's + name and source directory, resolving the source directory to an absolute path + using Python's `os.fspath` and `Path.resolve` functions. + + Attributes: + sourcedir (Path|str): Resolved to a string path using + `os.fspath(Path(sourcedir).resolve())`. It represents the directory + where sources for the CMake extension are located. + + """ def __init__(self, name: str, sourcedir: str = "") -> None: + """ + Initializes an instance of the class with a name and optionally a sourcedir. + It calls the superclass's `__init__` method to set up basic attributes, + then sets the sourcedir attribute to its resolved absolute path. + + Args: + name (str): Required to be passed during object instantiation. It is + expected to be a string representing the name associated with the + instance being created. + sourcedir (str): Initialized with an empty string. It represents the + directory where source files are located. The `os.fspath` and + `Path.resolve()` functions are used to ensure the path is resolved + and converted to a string. + + """ super().__init__(name, sources=[]) self.sourcedir = os.fspath(Path(sourcedir).resolve()) class CMakeBuild(build_ext): + """ + Extends the `build_ext` class to build C++ extensions using the CMake build + system, handling various configuration options and environmental variables for + cross-platform compatibility. + + """ def build_extension(self, ext: CMakeExtension) -> None: + """ + Configures and builds a CMake-based extension using Python's subprocess + module, invoking the cmake command to generate build files and then running + the build process according to the specified configuration. + + Args: + ext (CMakeExtension): Required for the function to execute. It represents + an extension being built with CMake. + + """ # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ ext_fullpath = Path.cwd() / self.get_ext_fullpath(ext.name) extdir = ext_fullpath.parent.resolve() From c194c8d7249d90af0992ff1d9f857051c05b1286 Mon Sep 17 00:00:00 2001 From: "komment-ai[bot]" <122626893+komment-ai[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 14:42:27 +0000 Subject: [PATCH 2/4] Added comments to 10 items across 1 file --- examples/joint_teleop.py | 145 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 73 deletions(-) diff --git a/examples/joint_teleop.py b/examples/joint_teleop.py index 557a8da..81e9596 100644 --- a/examples/joint_teleop.py +++ b/examples/joint_teleop.py @@ -1,10 +1,10 @@ +import argparse import sys -# PyGame: https://www.pygame.org/news -import pygame - # FRI Client: https://github.com/cmower/FRI-Client-SDK_Python import pyfri as fri +# PyGame: https://www.pygame.org/news +import pygame pygame.init() @@ -14,32 +14,33 @@ class Keyboard: """ - Initializes a pygame display and sets up event handling for keyboard input. - It manages joint activation and direction control using keys 1-7 and left/right - arrow keys, respectively, with velocity limits. + Handles keyboard input to control a joint's direction and velocity. It tracks + which joint is currently active, updates its velocity based on key presses, + and returns the current joint index and its updated velocity for further processing. Attributes: - max_joint_velocity (float): 5 degrees in radians converted to a radian - measure using numpy's `deg2rad` function, limiting the maximum velocity - of the joint when controlled by keyboard input. - joint_index (Optional[int]|NoneType): Initialized to None. It keeps track - of the index of a joint that has been turned on, allowing the program - to control multiple joints with different keys. - joint_velocity (float|NoneType): 0.0 by default. It represents the current - velocity of a mechanical joint, initially at rest (0.0), which can be - changed based on user input to control the joint's movement speed. - key_to_dir_map (Dict[pygameK_LEFT,float]|Dict[pygameK_RIGHT,float]): Used - to map keyboard keys (LEFT, RIGHT) to direction values (1.0, -1.0). - It determines how movement is controlled when using these keys. - key_joint_map (List[int]): Mapped to a list of Pygame key constants, - representing the keys on the keyboard that control the joints. + max_joint_velocity (float): 5 degrees converted to radians using NumPy's + `deg2rad` function, limiting the maximum speed at which a joint can + be rotated. + joint_index (NoneType|int): Used to track which joint (out of a maximum + of six) is currently active for movement. It's initialized as None and + updated when a specific key is pressed or released. + joint_velocity (float): 0 by default. It represents the velocity of a + joint, updated based on user input from the keyboard with directions + mapped to specific keys. The maximum allowed value is determined by `max_joint_velocity`. + key_to_dir_map (Dict[int,float]): Initialized with two key-value pairs. + It maps Pygame keys to numerical values, specifically left arrow (1.0) + and right arrow (-1.0), used for joint rotation control. + key_joint_map (List[pygameK_]): Defined with values from 1 to 7 on keys + K_1, K_2, ..., K_7, used as a mapping between keyboard keys and joint + indices. """ def __init__(self): """ - Initializes Pygame's display mode and sets up various variables for handling - keyboard input, including joint velocity limits, direction mappings, and - key associations with joints. + Initializes Pygame display, sets up joint movement parameters, and defines + mappings for keyboard input to control joystick direction and selection + of joints. It configures internal state variables with default values. """ pygame.display.set_mode((300, 300)) @@ -66,16 +67,15 @@ def __init__(self): def __call__(self): """ - Handles events from the pygame library, allowing the user to control joints - and change their velocity through keyboard input. It also allows the user - to quit the application by pressing the ESC key or closing the window. + Processes Pygame events to control the movement of a joint in real-time + simulation. It handles QUIT, KEYDOWN, and KEYUP events to toggle joint + activation, change direction, and update joint velocity based on user input. Returns: - Tuple[int,float]: A tuple containing two values: - - 1/ The index of the currently active joint (or None if no joint is active). - 2/ A scalar value representing the current velocity of all joints, - scaled by a factor that can be accessed through self.max_joint_velocity. + tuple[int,float]: 2-element list containing an integer and a float. + The integer represents the currently selected joint index, or None if + no joint is selected. The float represents the joint's velocity scaled + by its maximum allowed value. """ for event in pygame.event.get(): @@ -116,29 +116,29 @@ def __call__(self): class TeleopClient(fri.LBRClient): """ - Handles teleoperation of a robot using keyboard input. It monitors the robot's - state, receives user commands through keyboard inputs, and sends joint position - and torque commands to the robot based on these inputs. + Initializes a client for teleoperation of a robot, interacting with keyboard + input to command joint positions and torques, handling state changes, and + updating commands based on keyboard input and current robot state. Attributes: - keyboard (Keyboard): Associated with a keyboard interface. It appears to - be used to track user input, such as key presses or releases, that - control robot joint movements. - torques (npndarray[float32]): Initialized to zero with a size corresponding - to the number of joints in the robot (7). It stores torque values for - each joint. + keyboard (object): Used to retrieve joint index and velocity goal from + user input, presumably through a graphical interface or a library like + PyGame. + torques (numpyndarray[float32]): Initialized as a zeros array with a length + of fri.LBRState.NUMBER_OF_JOINTS, which represents the number of joints + in the robot. It stores torques values for each joint. """ def __init__(self, keyboard): """ - Initializes an instance with a keyboard object and sets up its attributes, - including a torques array initialized as zeros with the number of joints - specified by fri.LBRState.NUMBER_OF_JOINTS. + Initializes an object by setting its keyboard attribute to the passed + keyboard argument and creating a vector of zeros representing joint torques + with the specified number of joints from the LBRState class. Args: - keyboard (object): Referenced but not defined within this snippet. It - is expected to be an instance of a class that has been imported - from elsewhere. + keyboard (object): Set as an attribute of the class instance, referring + to an external keyboard device or its interface that interacts + with the robotic system. """ super().__init__() @@ -150,17 +150,16 @@ def monitor(self): def onStateChange(self, old_state, new_state): """ - Prints state change information, sets robot control parameters when the - robot reaches a specific monitoring ready state, and prints control - instructions to the user. + Updates the state of the robot and initializes data structures for control + when the robot transitions into monitoring-ready state. Args: - old_state (fri.ESessionState | str): Used to store the previous state - before it was changed to `new_state`. It represents the previous - session state or status of the robot system. - new_state (Enum[fri.ESessionState]): An enumeration value representing - the new state of the robot session, specifically the "MONITORING_READY" - state when changed. + old_state (fri.ESessionState | None): An indication of the robot + session's previous state before the change occurred. It represents + the old state of the system. + new_state (fri.ESessionState): Checked to determine whether it equals + MONITORING_READY. This state indicates that the robot is ready for + control. """ print(f"State changed from {old_state} to {new_state}") @@ -181,9 +180,9 @@ def onStateChange(self, old_state, new_state): def waitForCommand(self): """ - Waits for joint position and torque commands from the robot, then sends - these values to the robot's state machine. The function updates the robot's - joint positions and applies torques according to the client command mode. + Updates the robot's joint positions and torques based on received commands + from a client, ensuring consistency between joint positions and torque + values when the client is in TORQUE mode. """ self.q = self.robotState().getIpoJointPosition() @@ -194,9 +193,9 @@ def waitForCommand(self): def command(self): """ - Sends joint position and torque commands to a robotic arm, based on user - input from a keyboard or other device. It updates the robot's joint positions - and torques accordingly. + Executes user input to control the robot's joints and apply torques based + on current state and desired goals, updating the robot's joint positions + and torque commands accordingly. """ joint_index, vgoal = self.keyboard() @@ -212,15 +211,13 @@ def command(self): def args_factory(): """ - Parses command-line arguments using the `argparse` library. It defines two - optional arguments: `hostname` and `port`, both with default values, to be - used for communication with a KUKA Sunrise Controller. The parsed arguments - are returned as an object. + Constructs an argument parser using the `argparse` library and returns the + parsed command line arguments. It defines two arguments: `hostname` with a + default value of `None`, and `port` as an integer with a default value of `30200`. Returns: - Namespace[hostnamestr,portint]: A named collection of arguments that were - passed to the script. It contains the hostname and port number parsed from - command-line arguments or default values if not provided. + argparseNamespace: An object that holds all the arguments passed through + the command-line interface, parsed by the ArgumentParser instance. """ parser = argparse.ArgumentParser(description="LRBJointSineOverlay example.") @@ -243,13 +240,15 @@ def args_factory(): def main(): """ - Initializes a KUKA Sunrise controller teleoperation application, connects to - it, and enters a loop where it continuously steps through the robot's state - until idle or interrupted. It then disconnects and exits. + Establishes a connection to a KUKA Sunrise controller, allowing for teleoperation + and control of the robot through keyboard input. It runs indefinitely until + interrupted by user input or system exit, then disconnects and prints a farewell + message before returning successfully. Returns: - int: 0 if the execution completes successfully and 1 otherwise, indicating - failure to connect to the KUKA Sunrise controller. + int: 1 if the connection to KUKA Sunrise controller fails and 0 otherwise, + indicating successful execution or an intentional exit due to a keyboard + interrupt. """ print("Running FRI Version:", fri.FRI_CLIENT_VERSION) From f638f8000e77a6807af5c7873a5ad7b3bbc2ea55 Mon Sep 17 00:00:00 2001 From: "komment-ai[bot]" <122626893+komment-ai[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:03:38 +0000 Subject: [PATCH 3/4] Added comments to 11 items across 2 files --- examples/hand_guide.py | 85 ++++++++++++++++++++---------------------- setup.py | 59 +++++++++++++++-------------- 2 files changed, 71 insertions(+), 73 deletions(-) diff --git a/examples/hand_guide.py b/examples/hand_guide.py index 066fc55..b15c234 100644 --- a/examples/hand_guide.py +++ b/examples/hand_guide.py @@ -20,42 +20,37 @@ class HandGuideClient(fri.LBRClient): """ - Initializes and manages a robotic arm's state, commanding its position based - on estimated joint states and external wrenches. It updates the robot's pose, - checks command mode, and filters wrench estimates to ensure smooth motion control. + Initializes a hand guide client for interacting with a robot arm. It estimates + joint states and wrenches, filters sensor data, and updates robot commands + based on the controller's output to maintain stable movement. Attributes: - controller (AdmittanceController|None): Initialized with an instance of - AdmittanceController in the constructor. It represents a controller - used for controlling the robot's joints based on estimated wrenches - and other factors. - joint_state_estimator (JointStateEstimator|None): Initialized with the - instance itself as its argument, indicating it's responsible for - estimating joint states based on current conditions. + controller (AdmittanceController|None): Initialized with lbr_ver as a + parameter in its constructor method, likely controlling the robot's admittance. + joint_state_estimator (JointStateEstimator): Initialized with a reference + to the current `HandGuideClient` instance as its argument. It estimates + joint states of the robot. external_torque_estimator (FRIExternalTorqueEstimator|None): Initialized - with its constructor being called on self, which suggests it estimates - external torques acting on the robot. - wrench_estimator (WrenchEstimatorTaskOffset|JointStateEstimator|FRIExternalTorqueEstimator): - Associated with a robot, ee link, and joint state estimator. It estimates - task-space forces and torques from joint sensor readings and external - torque estimations. - wrench_filter (ExponentialStateFilter): Used to filter wrench data from - the `WrenchEstimatorTaskOffset`. It appears to be a state estimation - method to provide more accurate results. + with a reference to the client object itself during construction. + wrench_estimator (WrenchEstimatorTaskOffset|None): Initialized with four + parameters: the instance itself, its associated joint state estimator, + external torque estimator, and controller. It filters and estimates + wrench values based on task offsets. + wrench_filter (ExponentialStateFilter): Used to filter the wrench estimates + from the WrenchEstimatorTaskOffset instance associated with it. """ def __init__(self, lbr_ver): """ - Initializes objects for admittance control, joint state estimation, external - torque estimation, wrench estimation, and exponential state filtering based - on provided parameters. It also establishes relationships between these - components for coordinated functioning. + Initializes various components necessary for the controller and estimators, + including an admittance controller, joint state estimator, external torque + estimator, wrench estimator, and wrench filter. These are likely used to + control a robot arm's movements. Args: - lbr_ver (str | int): Used to specify the version or identifier of the - robot. This value is passed to the AdmittanceController class - during initialization. It appears to be related to the robotic - arm's model. + lbr_ver (str | int | float): Used to specify a version or other version + information about the LBR (Lightweight Robot) being controlled by + this class instance. """ super().__init__() @@ -82,9 +77,10 @@ def onStateChange(self, old_state, new_state): def waitForCommand(self): """ - Waits for a command from the client to be executed. It checks if the current - client mode matches POSITION, updates the wrench estimator, retrieves the - current joint position, and executes the command position. + Checks if the robot's client command mode is set to POSITION, otherwise + it prints an error message and exits. If the mode is correct, it retrieves + the current joint position from the robot state and calls the `command_position` + method. """ if self.robotState().getClientCommandMode() != POSITION: @@ -93,15 +89,14 @@ def waitForCommand(self): ) raise SystemExit - self.wrench_estimator.update() self.q = self.robotState().getIpoJointPosition() self.command_position() def command(self): """ - Updates robot position commands by filtering and processing wrench data - from the wrench estimator, then calls the `controller` function to compute - new joint positions. It also sets the joint position command of the robot. + Updates the robot's joint position based on estimated wrench and filtered + feedback, using a controller to compute the new position. It checks if the + estimator is ready before proceeding with the update process. """ if not self.wrench_estimator.ready(): @@ -125,13 +120,14 @@ def command(self): def args_factory(): """ - Creates an instance of the ArgumentParser class and defines arguments for - parsing command-line input. It returns a Namespace object containing user-specified - values for hostname, port, and LBR Med version number. + Parses command-line arguments using argparse. It defines three arguments: + hostname, port, and lbr-ver, which are then returned as parsed arguments. The + lbr-ver argument requires a specific integer value from the list [7, 14] and + is mandatory. Returns: - argparseNamespace: An object containing arguments parsed from the command - line. This object holds attributes for `hostname`, `port`, and `lbr_ver`. + argparseNamespace: An instance that stores the parsed command-line arguments + as attributes, allowing access to them as a dictionary-like object. """ parser = argparse.ArgumentParser(description="LRBJointSineOverlay example.") @@ -162,13 +158,14 @@ def args_factory(): def main(): """ - Initializes a connection to a KUKA Sunrise controller, attempts to establish - communication, and repeatedly polls the robot's state until idle or an error - occurs. It also handles keyboard interrupts and system exits before disconnecting - from the controller and terminating. + Initializes a FRI client, connects to a KUKA Sunrise controller, and executes + a sequence of steps. It continuously checks if the robot session is idle; if + so, it breaks out of the loop. The function gracefully handles keyboard + interrupts and system exits. Returns: - int: 1 when connection to KUKA Sunrise controller fails, and 0 otherwise. + int|None: 1 when connection to KUKA Sunrise controller fails or 0 on + successful execution. """ print("Running FRI Version:", fri.FRI_CLIENT_VERSION) diff --git a/setup.py b/setup.py index 476967a..37cb424 100644 --- a/setup.py +++ b/setup.py @@ -11,10 +11,10 @@ # Read the configuration settings class UserInputRequired(Exception): """ - Defines a custom exception to handle situations where user input is missing - or incomplete. It inherits from Python's built-in `Exception` class and takes - an error message as an argument, allowing for customizable error handling and - messaging. + Defines a custom exception that signals the need for user input to proceed + with a particular operation or action. It serves as a signal to handle missing + or invalid user data, ensuring program flow continues after receiving required + input from users. """ def __init__(self, msg): @@ -41,30 +41,30 @@ def __init__(self, msg): # If you need multiple extensions, see scikit-build. class CMakeExtension(Extension): """ - Initializes a C extension for use with Pybind11. It sets up the extension's - name and source directory, resolving the source directory to an absolute path - using Python's `os.fspath` and `Path.resolve` functions. + Initializes a CMake extension project with given name and source directory + path. It sets up the basic structure for a CMake build, allowing sources to + be added later. The `sourcedir` parameter is resolved to an absolute file + system path. Attributes: - sourcedir (Path|str): Resolved to a string path using - `os.fspath(Path(sourcedir).resolve())`. It represents the directory - where sources for the CMake extension are located. + sourcedir (osPathLike[str]|str): Set to a normalized path string. It stores + the source directory for the extension. This value can be obtained + from a Path object, which provides file system operations. """ def __init__(self, name: str, sourcedir: str = "") -> None: """ - Initializes an instance of the class with a name and optionally a sourcedir. - It calls the superclass's `__init__` method to set up basic attributes, - then sets the sourcedir attribute to its resolved absolute path. + Initializes an instance with a specified name and optionally a sourcedir + path. The `super().__init__` call creates a base Extension instance with + the given name, while also initializing a sources list. The sourcedir path + is resolved and stored in self.sourcedir. Args: - name (str): Required to be passed during object instantiation. It is - expected to be a string representing the name associated with the - instance being created. - sourcedir (str): Initialized with an empty string. It represents the - directory where source files are located. The `os.fspath` and - `Path.resolve()` functions are used to ensure the path is resolved - and converted to a string. + name (str): Required for initialization. It represents the name of an + object being created and will be used as its identifier. + sourcedir (str): Optional, as indicated by its default value. It + represents the source directory path that can be resolved to an + absolute path using os.fspath and Path.resolve. """ super().__init__(name, sources=[]) @@ -73,20 +73,21 @@ def __init__(self, name: str, sourcedir: str = "") -> None: class CMakeBuild(build_ext): """ - Extends the `build_ext` class to build C++ extensions using the CMake build - system, handling various configuration options and environmental variables for - cross-platform compatibility. + Extends the `build_ext` class from Python's `setuptools` package to provide + custom CMake-based build logic for extension modules. It generates and builds + CMake project files, incorporating environment variables and project-specific + settings. """ def build_extension(self, ext: CMakeExtension) -> None: """ - Configures and builds a CMake-based extension using Python's subprocess - module, invoking the cmake command to generate build files and then running - the build process according to the specified configuration. + Builds and configures a CMake-based extension using specified environment + variables, compiler types, and configuration settings to generate native + libraries for various platforms and architectures. Args: - ext (CMakeExtension): Required for the function to execute. It represents - an extension being built with CMake. + ext (CMakeExtension): Expected to be a specific extension that needs + to be built. """ # Must be in this form due to bug in .resolve() only fixed in Python 3.10+ @@ -192,7 +193,7 @@ def build_extension(self, ext: CMakeExtension) -> None: setup( name="pyfri", - version="1.2.0", + version="1.2.1", author="Christopher E. Mower, Martin Huber", author_email="christopher.mower@kcl.ac.uk, m.huber_1994@hotmail.de", description="Python bindings for the FRI Client SDK library.", From f3ed5226991d5e98d7af6c7ac506e3f541d02e3f Mon Sep 17 00:00:00 2001 From: "komment-ai[bot]" <122626893+komment-ai[bot]@users.noreply.github.com> Date: Sun, 8 Sep 2024 16:14:49 +0000 Subject: [PATCH 4/4] Added comments to 10 items across 1 file --- examples/joint_teleop.py | 143 ++++++++++++++++++++------------------- 1 file changed, 74 insertions(+), 69 deletions(-) diff --git a/examples/joint_teleop.py b/examples/joint_teleop.py index 81e9596..db39ec1 100644 --- a/examples/joint_teleop.py +++ b/examples/joint_teleop.py @@ -3,6 +3,7 @@ # FRI Client: https://github.com/cmower/FRI-Client-SDK_Python import pyfri as fri + # PyGame: https://www.pygame.org/news import pygame @@ -14,33 +15,35 @@ class Keyboard: """ - Handles keyboard input to control a joint's direction and velocity. It tracks - which joint is currently active, updates its velocity based on key presses, - and returns the current joint index and its updated velocity for further processing. + Sets up a pygame display and manages user input to control joint movement. It + allows users to turn joints on/off using number keys (1-7) and adjust their + rotation speed using arrow keys or designated keybindings. Attributes: - max_joint_velocity (float): 5 degrees converted to radians using NumPy's - `deg2rad` function, limiting the maximum speed at which a joint can - be rotated. - joint_index (NoneType|int): Used to track which joint (out of a maximum - of six) is currently active for movement. It's initialized as None and - updated when a specific key is pressed or released. - joint_velocity (float): 0 by default. It represents the velocity of a - joint, updated based on user input from the keyboard with directions - mapped to specific keys. The maximum allowed value is determined by `max_joint_velocity`. - key_to_dir_map (Dict[int,float]): Initialized with two key-value pairs. - It maps Pygame keys to numerical values, specifically left arrow (1.0) - and right arrow (-1.0), used for joint rotation control. - key_joint_map (List[pygameK_]): Defined with values from 1 to 7 on keys - K_1, K_2, ..., K_7, used as a mapping between keyboard keys and joint - indices. + max_joint_velocity (float): Set to a value that corresponds to a + degree-to-radian conversion of 5 degrees, indicating the maximum allowed + angular velocity for the joint. + joint_index (Optional[int]|None): Used to store the index of a specific + joint selected by the user, initially set to None but assigned when a + joint key is pressed. + joint_velocity (float): 0 by default. It tracks the current velocity of a + joint, which can be increased or decreased based on user input from + the arrow keys. Its value is capped at `max_joint_velocity`. + key_to_dir_map (Dict[int,float]): Initialized with a dictionary mapping + Pygame keyboard keys (integers) to their corresponding direction values + as floats. The keys are for movement control, while the values determine + the direction. + key_joint_map (List[int]): Mapped to keys on the keyboard corresponding + to joints. It contains numeric key values from K_1 to K_7 that are + used to turn joint actions on or off by pressing the associated key. """ def __init__(self): """ - Initializes Pygame display, sets up joint movement parameters, and defines - mappings for keyboard input to control joystick direction and selection - of joints. It configures internal state variables with default values. + Initializes various attributes related to keyboard input and joint control. + It sets up a Pygame display, defines maximum joint velocity, key-to-direction + mapping, and key-joint mappings for controlling multiple joints with + specific keys. """ pygame.display.set_mode((300, 300)) @@ -67,15 +70,14 @@ def __init__(self): def __call__(self): """ - Processes Pygame events to control the movement of a joint in real-time - simulation. It handles QUIT, KEYDOWN, and KEYUP events to toggle joint - activation, change direction, and update joint velocity based on user input. + Handles keyboard events to control a joint's state and velocity, allowing + the user to turn it on/off and adjust its direction using specific key combinations. Returns: - tuple[int,float]: 2-element list containing an integer and a float. - The integer represents the currently selected joint index, or None if - no joint is selected. The float represents the joint's velocity scaled - by its maximum allowed value. + Tuple[int,float]: A tuple containing two values: an integer and a float. + The first value is either None or a joint index (an integer). + The second value is the product of max_joint_velocity (a float) and + joint_velocity (another float). """ for event in pygame.event.get(): @@ -116,29 +118,31 @@ def __call__(self): class TeleopClient(fri.LBRClient): """ - Initializes a client for teleoperation of a robot, interacting with keyboard - input to command joint positions and torques, handling state changes, and - updating commands based on keyboard input and current robot state. + Initializes a teleoperation client with keyboard input, monitors robot states, + and enables users to control robotic joints using left/right arrow keys or + specific joint numbers on the keyboard. It sends joint position commands to + the robot based on user input. Attributes: - keyboard (object): Used to retrieve joint index and velocity goal from - user input, presumably through a graphical interface or a library like - PyGame. - torques (numpyndarray[float32]): Initialized as a zeros array with a length - of fri.LBRState.NUMBER_OF_JOINTS, which represents the number of joints - in the robot. It stores torques values for each joint. + keyboard (object): Initialized in the `__init__` method. It does not have + any direct description within the code snippet provided, but its usage + suggests it is related to capturing keyboard input for controlling + robot joints. + torques (npndarray[float32]): Initialized with zeros. It stores torque + values for each joint, representing a set of torques that can be applied + to control robot movements when operating in torque mode. """ def __init__(self, keyboard): """ - Initializes an object by setting its keyboard attribute to the passed - keyboard argument and creating a vector of zeros representing joint torques - with the specified number of joints from the LBRState class. + Initializes an instance with a keyboard object and sets its attributes, + including the torques of its joints to zeros. The NUMBER_OF_JOINTS constant + is used from fri.LBRState to determine the size of the torques array. Args: - keyboard (object): Set as an attribute of the class instance, referring - to an external keyboard device or its interface that interacts - with the robotic system. + keyboard (fri.Keyboard): Assumed to be an instance of the Keyboard + class from the fri package. Its actual behavior depends on its + implementation in that package. """ super().__init__() @@ -150,16 +154,17 @@ def monitor(self): def onStateChange(self, old_state, new_state): """ - Updates the state of the robot and initializes data structures for control - when the robot transitions into monitoring-ready state. + Prints state change notifications and sets up a control interface when the + robot reaches the MONITORING_READY state, prompting the user to control + robot joints using keyboard input. Args: - old_state (fri.ESessionState | None): An indication of the robot - session's previous state before the change occurred. It represents - the old state of the system. - new_state (fri.ESessionState): Checked to determine whether it equals - MONITORING_READY. This state indicates that the robot is ready for - control. + old_state (Union[fri.ESessionState, fri.LBRState]): Used to represent + the state of the robot before it changed to the new_state. It + indicates the previous operational mode or configuration of the robot. + new_state (fri.ESessionState): Checked for equality with + fri.ESessionState.MONITORING_READY. It represents the new state + of the robot session after a change has occurred. """ print(f"State changed from {old_state} to {new_state}") @@ -180,9 +185,9 @@ def onStateChange(self, old_state, new_state): def waitForCommand(self): """ - Updates the robot's joint positions and torques based on received commands - from a client, ensuring consistency between joint positions and torque - values when the client is in TORQUE mode. + Waits for a new command from the robot and applies it by setting the joint + positions and torques accordingly based on the client's current mode, + allowing torque control when enabled. """ self.q = self.robotState().getIpoJointPosition() @@ -193,9 +198,9 @@ def waitForCommand(self): def command(self): """ - Executes user input to control the robot's joints and apply torques based - on current state and desired goals, updating the robot's joint positions - and torque commands accordingly. + Updates the robot's joint positions and torques based on user input from + a keyboard, and sends the updated values to the robot for execution. It + handles different client command modes, including torque control. """ joint_index, vgoal = self.keyboard() @@ -211,13 +216,14 @@ def command(self): def args_factory(): """ - Constructs an argument parser using the `argparse` library and returns the - parsed command line arguments. It defines two arguments: `hostname` with a - default value of `None`, and `port` as an integer with a default value of `30200`. + Parses command line arguments using argparse. It defines two arguments, + `hostname` and `port`, to connect with a KUKA Sunrise Controller. The default + port is set to 30200. Parsed arguments are returned as an object, allowing + access to their values. Returns: - argparseNamespace: An object that holds all the arguments passed through - the command-line interface, parsed by the ArgumentParser instance. + argparseNamespace: An object holding the parsed arguments from the command + line, including hostname and port. """ parser = argparse.ArgumentParser(description="LRBJointSineOverlay example.") @@ -240,15 +246,14 @@ def args_factory(): def main(): """ - Establishes a connection to a KUKA Sunrise controller, allowing for teleoperation - and control of the robot through keyboard input. It runs indefinitely until - interrupted by user input or system exit, then disconnects and prints a farewell - message before returning successfully. + Initializes and connects to a KUKA Sunrise controller using the FRI (Fieldbus + Remote Interface) protocol, then enters an infinite loop where it continuously + checks for new commands until the session state changes or an interrupt is received. Returns: - int: 1 if the connection to KUKA Sunrise controller fails and 0 otherwise, - indicating successful execution or an intentional exit due to a keyboard - interrupt. + int: 0 when a connection to KUKA Sunrise controller is established, and 1 + otherwise, indicating failure. This allows the operating system or script + that calls this function to determine whether it was successful. """ print("Running FRI Version:", fri.FRI_CLIENT_VERSION)