diff --git a/README.md b/README.md index fb41e84e2c33d5cbe7ac331f621bd8ef16b6a8ef..f5e44e503aafa77a967db05d2002080f94cd5bc3 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,38 @@ # Data collector -This tool is made to collect sensor data from KUKA KR 3 R540 robotic arm. +This tool is made to collect and plot sensor data from KUKA KR 3 R540 robotic arm. It collects the system vairables via a custom KRL submodule and trace data from -the KUKA Trace module. +the KUKA Trace module of the robot controler. + +For the tests, we can use three idetntical KUKA Cell with robotic arms, as one is shown here :  -For each of the 6 motors, the following variables are measured by system variables: +The robot is driven by a controler, that takes care of the power supply of each DC +motor independantly, robot program, memory and security. A robot program is stored in +a text format in .src file, associated with a .dat file for program data, each written +in KRL programming language. Global variables, which can be accessed by Python, are +located in $config.dat file. Among global variables there are system variables in read +only access, and this Python tool use it to collect data from the robot. + +As the purpose of this tool is to make a diagnosis on the robot arm health, the robot +program is a full-range motion on each axis of the robot. One motor iteration consists +of a axis movement by a defined angle from the default position, on positive then negative +side. Data analysis for diagnosis is curenly in developement. + +For each of the 6 motors of the robot arm, the following variables are measured by system variables: - Position - Torque - Current draw - Temperature -This collects the data in real time with system variables. The main latency factor is -the network quality, so an ethernet connection is preffered over Wi-Fi. The data -is buffered on the robot side but has a hard-coded limit of 20000 samples. -The sampling rate has to be configured in accordance of the length of an -acquisition and with the network connection quality. +This tool can collect the data in real time with system variables. The main latency +factor is the network quality, so an ethernet connection is preffered over Wi-Fi. The +data is buffered on the robot side but has a hard-coded limit of 20000 samples. The +sampling rate has to be configured in accordance of the length of an acquisition and with +the network connection quality. If the write index in the buffers reach the maximum value +(size of the buffer), it will loop back to the begining of the buffer. Data from system +variables is lost when the write index reach the read index Besides the system variables, the robot controler can measure the traces of the robot. It is a way intended by KUKA to recover data from the robot, that can measure the folowwing variables : @@ -26,14 +42,13 @@ It is a way intended by KUKA to recover data from the robot, that can measure th - Temperature - Current -At the end of a measure sequence, tha traces are recovered from the network and saved in local to bea accessible. -KUKA Traces data is in binary format (.r64), with .dat, .trc and .txt associated. -Each motor has it's own file in that format, python will convert all the files and concatenate them to -return a readable file, as excel or csv. Note that you have to setup the network access to the robot -controler before launching this program to be able to recover the traces. +At the end of a measure sequence, the traces are recovered from the network and saved +in local to be accessible. KUKA Traces data is in binary format (.r64), with .dat, .trc +and .txt associated. Each motor has it's own file in that format, so Python will convert +all the files and concatenate them to return a readable file, as excel or csv. -The data is acquired from a full-range motion of a motor. Each motor moves -independently from each other. +**NB :** Network access to the robot have to be setup before launching this program to be +able to recover the traces. The data collected by the program can be found in the /data folder, each data file having an explicit name with the date of the collection @@ -66,20 +81,41 @@ Summary ## Measure Sequence -One measure run consists of a repetition of axis movement at a given speed. The numer of iterations by axis is variable and is defined by the user before launching the sequence. The speed of the robot can be modified to test the robot movement in diferent stress conditions.\ +One measure run consists of a repetition of axis movement at a given speed. +The numer of iterations by axis is variable and is defined by the user before +launching the sequence. The speed of the robot can be modified to test the robot +movement in diferent stress conditions.\ As data is recovered in time, this data collector has -a sampling rate. For the system variables, it varies from 12 to 60 milliseconds by a step of 12, the minimum -of time the KRL program can measure from internal timers. -KUKA traces provide a sampling rate from 1, 4 and 12ms, but oly the two last ones are implemented in this code. +a sampling rate. For the system variables, it varies from 12 to 60 milliseconds +by a step of 12, the minimum of time the KRL program can measure from internal timers. +KUKA traces provide a sampling rate from 1, 4 and 12ms, but oly the two last ones +are implemented and working in this code. Traces time sampling is more reliable than global variables. ## UI Description and details -As shown on the image below, the User Interface is divided in main 6 functions. +As shown on the image below, the User Interface is divided in 6 main functions.  -The PysimpleGUI librairy used in this application provide easy access to basic components of a gui, called widjets. For example, we can use buttons, keyboard input, checkboxes and combos. Combos are a way to give the user a visual choice on a variable. Note that all PysimpleGUI widjets give the possibility to run an action when an event occurs from it. Events are treated in the main loop of the program, and are defined as text by the programmer for each widjet. +The PysimpleGUI librairy used in this application provide easy access to basic +components of a gui, called widjets. For example, we can use buttons, keyboard +input, checkboxes and combos. Combos are a way to give the user a visual choice +on a variable. Note that all PysimpleGUI widjets give the possibility to run an +action when an event occurs from it. Events are treated in the main loop of the +program, and are defined as text by the programmer for each widjet. + +As shown on the GUI, robot related functions are disabled on startup. To enable +it, one of the robot has to be connected by clicking on the blue buttons followng +"Connected Robots"in collection settings. If connection has succeed, it will apear +green, red otherwise. + +With multiple testings, we sometimes noticed connection difficulties with the robot +cell 1. It can appear after a measure sequence or after a period of time doing nothing, +so make sure the connection is not broken for this robot. If connection problems occurs, +**C3 bridge** on the cell has to be restarted by **Minimizing HMI** on the control panel +(in settings). When Windows is showed, right click using a mouse on **C3** on the right +side of the task bar. ### Collection settings @@ -94,22 +130,42 @@ Due to software limitations from the KRL environment, the sampling rate is bound to be a multiple of 12. The speed of the motors during the acquisition varies according to the confifuration. -This value is defined as a percentage of the max value (i.e. 0 to 100). -By default, the 'Constant' checkbox is set to true. It means that the robot speed will be constant for the whole test and thus only one acquisition is made. By clicking on the checkbox, it allows the user to set a range of speeds for the test, from minimum to maximum by a step.\ -A test at multiple speed is equivalent to the merge of multiple single speed runs. So, check the configurations before launching an acquisition, even if it is able to stop between the runs if a problem has occurred. \ -During a run at multple speed, system variables and KUKA traces are runing. To minimize data loss, we wait for each collection method to finish its process to start the next run. +This value is defined as a percentage of the max value (i.e. 0 to 100), Note that +limiting the value on the control panel will affect the resulting speed of the robot, +as Python speed is a multiplicator of the one on the control panel. + +By default, the 'Constant' checkbox is set to true. It means that the robot speed will +be constant for the whole test and thus only one acquisition is made. By clicking on the +checkbox, it allows the user to set a range of speeds for the test, from minimum to maximum +by a step. + +A test at multiple speed is equivalent to the data merge of multiple single speed runs. So, +check the configurations before launching an acquisition, even if it is able to stop between +the runs if a problem has occurred. + +During a measure sequence, system variables and KUKA traces can run at the same time to compare +the data. If one of the collection method is prefered, we give the ability to chose which one +is running with two checkboxes. By configurattion, Kuka trace running at 12ms sampling rate +can't exceed 600 seconds of time (same with 4ms -> 200 seconds maximum). So to minimize data +loss, we wait for both collection method to finish its process to start the next run, especialy +on multiple speed runs. ### KUKA traces A combo selector is used to select the KUKA Trace configuration that will be -run along side of the system variable collection. The collected data is the same +run for data collection. The collected data is the same as with the system variables but with more precision and reliability. The acquisition is done by the RTOS of the robot. ### Robots loads -Enter here the load on each robot, with weight or bungee coords -It will be displayed in the result dataset for data processing +Curent kuka cell gripper tool on the robot enable the use of loads. Diferent weights of 500g, +1kg and 2kg are available as linear loads, as if the robot arm carry something. For more specific +stress testing, two bungee cords are available to bring a non linear force in the robot movement. +As other sections of the UI, a robot must be connected to enable load input. +Load configuration entered by the user will apear in the dataset, coded in a numerical format. +For details, see get_category() function in mainwindow class of the python program. Load entry +on the gui is not automatic, so entry the right configuration corresponding to the test. ### Latency test @@ -118,12 +174,13 @@ Plots a graph of the latency versus time and distributions with histograms ### Gripper commands -Command the gripper of the selected robot : open or close +Command the gripper of the selected robot : open or close. +This section is enabled when one robot is connected, and will do nothing if the selected +robot is not connected ### Collected data plot -Accessible without robot connection, this section plots data by selecting -a excel file and hitting corresponding buttons +Accessible without robot connection, this section plots data. First, a excel file containing data has to be selected with the Browse button, then OPEN. Variables available on robot axes will be shown as checkboxes in the frame 'varriables to plot' to give the user the choice to plot a unique variable. As variables provide data on each axis, each one of them can be disabled by unchecking the checkboxes 'Axis to plot'. ## Program Structure @@ -155,7 +212,8 @@ class methods instead of global variables and functions. ## KRL Submodule -This section describes how to deploy the data collection on a cell. +This section describes how to deploy the data collection with system variables on a kuka cell. +Kuka trace is by default implemented on the robot, making this tool more convenient to use on every robot. ### Global variables @@ -222,9 +280,9 @@ DECL E6AXIS ColBUFFER_POS_MEAS[20000] ### `Data_collector.sub` -To allow for data collection, please be sure to add the `Data_collector.sub` -to the list of running submodules. This submodules takes up to **3 minutes** to -properly initialize the internal data buffers. +To allow data collection with system variables, please be sure to add the `Data_collector.sub` +to the list of running submodules. This submodules takes up to **1 minute** to +properly initialize the first values of the internal data buffers. This progam can be found in [`robot/KRL/`](./robot/KRL). @@ -237,8 +295,8 @@ the `TRACE` folder of the robot. ### The `Axis_Main.src` program -The data collection uses the `Axis_Main.src` program to create the data -to collect. It must be running in `AUT` mode at `100%` of run speed to produce +The data collection uses the `Axis_Main_dataset.src` program to create the data +to collect. It must be running in `AUT` mode at `100%` of run speed on the control panel to produce valid data. This progam can be found in [`robot/KRL/`](./robot/KRL)`. \ No newline at end of file diff --git a/images/GUI.png b/images/GUI.png index 98ed7ea9140616481ac2ca1a8d3ec92e22056369..bbf170a171ec63246af75d603499316b8769874b 100644 Binary files a/images/GUI.png and b/images/GUI.png differ diff --git a/kuka/__init__.py b/kuka/__init__.py index d75c503b5ef331c72c34752d781a64cef8f8e263..4adf2e5264ecc5d65d1ba4131e0f4e6fda261061 100644 --- a/kuka/__init__.py +++ b/kuka/__init__.py @@ -1,3 +1,5 @@ +# import python classes from the folder kuka + from .kukavarproxy import openshowvar from .handler import KUKA_Handler from .trace import KUKA_Trace diff --git a/main.py b/main.py index 723f83891152cd39dbcca097cba8c668a5593eb7..1ef332e2d581f6294bba06e1e4c591dbc17741aa 100644 --- a/main.py +++ b/main.py @@ -30,7 +30,7 @@ class MainProgram (MainWindow): ### ---- Methods ---- ### def __init__(self): - super().__init__() + super().__init__() # super : access to the inherited class MainWindow self.update_disabled_ui() @@ -74,9 +74,6 @@ class MainProgram (MainWindow): self.data.disabled = disabled self.collection_settings.update_robot_buttons() - - def collection_methods (self): - pass def done (self): """Called when a robot has collected the samples for a run. @@ -111,9 +108,11 @@ class MainProgram (MainWindow): if not self.collection_settings.check_configuration(): print("Failed to run the collection : invalid configuration") return - + + # Collection method choice dosysvar_method = bool(self.collection_settings.docollect_sysvar.get()) dotrace_method = bool(self.collection_settings.docollect_trace.get()) + # Check if one collection method is selected if not dosysvar_method and not dotrace_method: print("No collection method selected") @@ -204,7 +203,6 @@ class MainProgram (MainWindow): def gripper_open (self): """Tries to open the gripper of the selected robot """ - if self.robot_handlers[self.gripper._robot_choice - 1] is not None: self.robot_handlers[self.gripper._robot_choice - 1].KUKA_WriteVar('PyOPEN_GRIPPER', True) self.gripper._btn_open.update(disabled=True) @@ -219,8 +217,7 @@ class MainProgram (MainWindow): self.gripper._btn_close.update(disabled=True) def open_xlsx (self, path: str): - """Tries to load a .xslx file collected from the system variables - + """Tries to load a .xslx file collected Args: path (str): The path to the file to open """ @@ -235,18 +232,22 @@ class MainProgram (MainWindow): self.data.enable_plot_buttons(False) def trace_selected_variables (self): + """ plot the variables from the opened file selected by the checkboxes + """ plt.close('all') - a = self.data.get_selected_axis() - if (len(a)==0): + axis = self.data.get_selected_axis() + if (len(axis)==0): sg.PopupOK("Select at least one axis to plot") return for i, var in enumerate(self.data._var_totrace): if(self.data._do_trace_var[i]): - self.dataframe.plot(x="Sample_time_s",y=[f"{var}_A{i}" for i in a], grid=True),plt.ylabel(f"Motor {var}") + self.dataframe.plot(x="Sample_time_s",y=[f"{var}_A{i}" for i in axis], grid=True),plt.ylabel(f"Motor {var}") plt.tight_layout() plt.pause(0.1) # Alternative to plt.show() that is not blocking def trace_sampling (self): + """ plot the write and read index in buffers in robot controler memory, with network latency in the background + """ self.dataframe.plot(x="Sample_time_s",y=["Queue_Read", "Queue_Write"], grid=True),plt.ylabel("Samples") plt.xlabel("Collection execution time (s)") plt.twinx(), plt.plot(self.dataframe["Sample_time_s"],self.dataframe['Read_time'], alpha=0.05), plt.ylabel("Request time of the sample (ms)") @@ -255,15 +256,16 @@ class MainProgram (MainWindow): plt.show() def trace_latencies (self): + """ plot the network latency of each sample collected from system variables + """ self.dataframe.hist(column='Read_time', grid=True, bins=30) plt.title("Distribution of the collection time of a sample") plt.xlabel(f"Request time (ms) : mean = {self.dataframe['Read_time'].mean().__round__(2)}"),plt.ylabel("Number of samples") plt.show() def run (self): - """Runs the main program + """Runs the pysimplegui loop """ - while True: event, values = self.read(timeout=10) @@ -279,6 +281,7 @@ class MainProgram (MainWindow): self[f'num_of_iter{i}'].update(value=values['num_of_iter1']) if event == '-BTN_start-': + # start measure sequence self.start(values) if event == "-dataset_auto-name-": @@ -322,6 +325,12 @@ class MainProgram (MainWindow): if '-open_data' in event : nb = int(event.removeprefix("-open_data").removesuffix("-")) # get the identifier of the clicked button self.open_xlsx(values[f'-path_data{nb}-']) + if 'TRACE' in values[f'-path_data{nb}-']: + self.data.trace_latency.update(disabled=True) + self.data.trace_samples.update(disabled=True) + else: + self.data.trace_latency.update(disabled=False) + self.data.trace_samples.update(disabled=False) if event == '-trace_selvar-': self.trace_selected_variables() diff --git a/ui/__init__.py b/ui/__init__.py index d25840128b226d606fbc16c7c7bd2de98b9c1d4d..3cce722cd896f67c9c242fa4e27fe140f50f66a3 100644 --- a/ui/__init__.py +++ b/ui/__init__.py @@ -1,3 +1,5 @@ +# import python classes from the folder ui + from .ui_collection_settings import UI_Collection_Settings from .ui_data import UI_Data from .ui_gripper import UI_Gripper diff --git a/ui/graph_window.py b/ui/graph_window.py index 56128d1f4f83d9401cd93cda3e67d2386ae9fd99..307466fdcc261f58fb104d71a2924f637312a18d 100644 --- a/ui/graph_window.py +++ b/ui/graph_window.py @@ -6,8 +6,9 @@ from threading import Semaphore class CollectionGraphWindow (sg.Window): lock = Semaphore(1) + enable = True - def __init__(self, cell: int): + def __init__(self, cell: int, enable: True): """ : Class constructor With __make_layout, generate a pysimplegui window to be shown when a colelction sequence on a robot is lauched give a dynamic view of the robot buffer fill level and sample latency with plot @@ -20,41 +21,47 @@ class CollectionGraphWindow (sg.Window): self._data_latency = [] self._s = 0 + self.enable = enable super().__init__(f'Robot {cell}', self.__make_layout(), finalize=True) - # Canvas settings - self._canvas = self._canvas_elem.TKCanvas - self._figure: Figure = Figure() - self._ax = self._figure.add_subplot(2, 1, 1) - self._ay = self._figure.add_subplot(2, 1, 2) + # Canvas settings enabled if system variables are collected + if self.enable: + self._canvas = self._canvas_elem.TKCanvas + self._figure: Figure = Figure() + self._ax = self._figure.add_subplot(2, 1, 1) + self._ay = self._figure.add_subplot(2, 1, 2) - self._ax.set_xlabel("Sample") - self._ax.set_ylabel("Number of buffered values") - self._ax.grid() + self._ax.set_xlabel("Sample") + self._ax.set_ylabel("Number of buffered values") + self._ax.grid() - self._ay.set_xlabel("Sample") - self._ay.set_ylabel("Network latency (ms)") - self._ay.grid() + self._ay.set_xlabel("Sample") + self._ay.set_ylabel("Network latency (ms)") + self._ay.grid() - self._fig_agg = FigureCanvasTkAgg(self._figure, self._canvas) - self._fig_agg.draw() - self._fig_agg.get_tk_widget().pack(side='top', fill='both', expand=1) + self._fig_agg = FigureCanvasTkAgg(self._figure, self._canvas) + self._fig_agg.draw() + self._fig_agg.get_tk_widget().pack(side='top', fill='both', expand=1) def __make_layout (self): - self._title = sg.Text(f'Robot {self.cell}', key="-TITLE-", font='Helvetica 20') - self._subtitle = sg.Text("Collecting sample n°...", key="Subtitle") - self._canvas_elem = sg.Canvas(size=(480,360), key="-CANVAS-") - self._status = sg.Text("",key="-colstatus-",text_color="#000", font="Helvetica 15") + self._title = sg.Text(f'Robot {self.cell}', key="-TITLE-", font='Helvetica 20', size=(20,3), justification='center') + self._subtitle = sg.Text("Collecting Data ...", key="Subtitle") + if self.enable: + self._canvas_elem = sg.Canvas(size=(480,360), key="-CANVAS-") + else: + self._canvas_elem = None + self._status = sg.Text("",key="-colstatus-", text_color="#000", font="Helvetica 15") self._exit = sg.Button("Exit", key='-colexit-',font="Helvetica 11", size=(15,1),button_color='#F0F0F0') layout = [ [ sg.Push(), self._title, sg.Push() ], [ sg.Push(), self._subtitle, sg.Push() ], - [ self._canvas_elem ], - [ sg.Push(), self._status, sg.Push() ], - [ sg.Push(), self._exit, sg.Push() ] ] + if self.enable: + layout.append([ self._canvas_elem ]) + layout.append([ sg.Push(), self._status, sg.Push() ]) + layout.append([ sg.Push(), self._exit, sg.Push() ]) return layout def add (self, buffer: int, latency: float): diff --git a/ui/measure_robot.py b/ui/measure_robot.py index 9beee904009e355a988f0b5105a0fbbab18b1ee5..0463cbf73e213f23d693ee59f010ce1aec29c4b6 100644 --- a/ui/measure_robot.py +++ b/ui/measure_robot.py @@ -24,16 +24,19 @@ class Measure_robot (CollectionGraphWindow): latencies = np.zeros(0) def __init__ (self, handler: KUKA_Handler, cell: int, dosysvar: bool, dotrace: bool, file_prefix: str, temp_dir: str = ".\\temp"): - """Creates a new measurement window + """Creates a new measurement window, showing the user the progression of the collection + If system variables collection is enabled in the measurement config, it will plot the number of buffered data and network latency + If not, it will be a simple window, updating when robot movement is done and when the data file is saved Args: handler (KUKA_Handler): The Kuka Handler cell (int): The number of the cell (from 1 to 3) + dosysvar / dotrace (bool) : True if the system variable / kuka trace collection method is enabled file_prefix (str): The prefix for the output file name temp_dir (str, optional): The temporary working dir for the Kuka Trace parsing. Defaults to ".\temp". """ - super().__init__(cell) + super().__init__(cell, dosysvar) self._dosysvar = dosysvar self._dotrace = dotrace @@ -89,17 +92,16 @@ class Measure_robot (CollectionGraphWindow): self.add(buffer, latency) self.latencies = np.append(self.latencies, latency) - try: + try: + # launch data collection with configuration self.data, self.trace_data = self.reader.acquire(A_iter, speed, sampling, trace_sampling, next, done, load, lock, self.temp_dir) self.collecting_data_done = True - # print("Moyenne Tps Reponse : ",self.data["Read_time"].mean()) - # print("Moyenne Essais : ",self.data["Read_tries"].mean()) - # print("Moyenne Dupliqués : ",self.data["Duplicates"].mean()) except Exception as e: self.collecting_data_done = True traceback.print_exception(e) + # save data in xlsx file self.export_measures() def export_measures (self): @@ -144,7 +146,9 @@ class Measure_robot (CollectionGraphWindow): self.storing_data_done = False self._status.update("Successfully stored data", text_color='#0c2') self._exit.update(disabled=False) - else: + elif self._dosysvar: self.redraw() + if not self._dosysvar: + self._subtitle.update("Kuka trace started\nRobot running") return True diff --git a/ui/ui_data.py b/ui/ui_data.py index 212429f6ec413c2a74294923e7354f1ea0b2cbea..86b2d938e9a297c79ab760887c85962bdbdc9b99 100644 --- a/ui/ui_data.py +++ b/ui/ui_data.py @@ -116,7 +116,7 @@ class UI_Data (sg.Frame): self.var_checkbox = [] i=0 for col in columns: - if '_A1' in col: # sorting the variables per axis + if '_A1' in col: # extracting the variables per axis colname = col.rsplit('_A1', 1)[0] self._var_totrace.append(colname) self.var_checkbox.append(sg.Checkbox('Motor ' + colname, enable_events=True, default=False, key=f'-varplot_cbx{i}-'))