From 90cb9d20d2fec2b231a26b0f2c12f32e96e9f601 Mon Sep 17 00:00:00 2001
From: unknown <asoalexandros@gmail.com>
Date: Thu, 27 Mar 2025 14:30:02 +0100
Subject: [PATCH] Beta Version of Custom SMU for Users

---
 hp4155/Custom_SMU/interface_custom.ipynb |  52 ++
 hp4155/Custom_SMU/lib/help.py            | 223 ++++++++
 hp4155/Custom_SMU/lib/interface.py       | 212 ++++++++
 hp4155/Custom_SMU/main.py                | 653 +++++++++++++++++++++++
 hp4155/hp4155a.py                        |  40 +-
 5 files changed, 1178 insertions(+), 2 deletions(-)
 create mode 100644 hp4155/Custom_SMU/interface_custom.ipynb
 create mode 100644 hp4155/Custom_SMU/lib/help.py
 create mode 100644 hp4155/Custom_SMU/lib/interface.py
 create mode 100644 hp4155/Custom_SMU/main.py

diff --git a/hp4155/Custom_SMU/interface_custom.ipynb b/hp4155/Custom_SMU/interface_custom.ipynb
new file mode 100644
index 0000000..138fcb8
--- /dev/null
+++ b/hp4155/Custom_SMU/interface_custom.ipynb
@@ -0,0 +1,52 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "3f5d53eb-4723-4e65-8d58-22888bcc84b1",
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "SyntaxError",
+     "evalue": "'(' was never closed (main.py, line 27)",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[1;36m  File \u001b[1;32m~\\labcode\\hp4155\\Custom_SMU\\main.py:27\u001b[1;36m\u001b[0m\n\u001b[1;33m    ini = widgets.Button(description = 'Import from ini. (Coming Soon)',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')\u001b[0m\n\u001b[1;37m                        ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m '(' was never closed\n"
+     ]
+    }
+   ],
+   "source": [
+    "%run main.py"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "b4134f36-9202-45f5-ad20-b71dfc315edd",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.4"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/hp4155/Custom_SMU/lib/help.py b/hp4155/Custom_SMU/lib/help.py
new file mode 100644
index 0000000..876b79b
--- /dev/null
+++ b/hp4155/Custom_SMU/lib/help.py
@@ -0,0 +1,223 @@
+import tkinter as tk
+import tkinter.messagebox
+import pandas as pd 
+import matplotlib.pyplot as plt
+from IPython.display import clear_output
+from datetime import datetime
+from tkinter import filedialog
+
+# Check the functionalities of smus VAR1,VAR2 and VARD
+def check_sweep_func(smus:list,func:str):
+    for i, smu in enumerate(smus):
+        if smu["func"] == func and smu["disabled"]== False:
+            return True
+    return False
+
+# Check if there is a pulsed smu for sweep smu
+def check_sweep_pulse(smus:list):
+    for smu in smus:
+        if "PULSE" in smu["mode"] and smu["disabled"]== False:
+            return True
+    return False
+
+# Check if there any constant smus and return their numbers
+def check_sweep_cons(smus:list):
+    cons_smu_numbers = []
+    for i,smu in enumerate(smus):
+        if (smu['func']=='CONS' and smu['mode']!='COMM') and smu['disabled']== False: # Non Grounded SMUs
+            cons_smu_numbers.append(i+1) # for the actual number of smus
+
+    return cons_smu_numbers
+
+# Setup the axes in tool
+def setup_axes(axes_info:list,device):
+    axes_plot = ['X','Y1','Y2']
+    for ax_plot,ax_info in zip(axes_plot,axes_info):
+        if ax_plot == 'Y2' and ax_info['name'] == "": # Y2 axis can be empty
+            pass
+        else:
+            device.display_variable(ax_plot,ax_info['name'])
+            device.axis_scale(ax_plot,ax_info['scale'])
+            device.display_variable_min_max(ax_plot,'MIN',ax_info['min'])
+            device.display_variable_min_max(ax_plot,'MAX',ax_info['max'])
+
+
+# Setup the variables to be saved
+def save_variables(axes,variables,device):
+    # condition number one: the plotted variables need to be saved in the file
+    # Sticter policy with exceptions
+
+    # Retrieve the names of the variables
+    variable_names =[]
+    for variable in variables:
+        variable_names.append(variable['name'])
+
+    # Retrieve the axes names (variable)
+    axes_names=[]
+    for i,ax in enumerate(axes):
+        if i != 2 and ax['name']!="": # Y2 axis can be left empty
+            axes_names.append(ax['name'])
+
+    # Now Check if axes names are in the variable names
+    for ax_name in axes_names:
+        if ax_name not in variable_names:
+            raise Exception("Plotted Variables should be saved!")
+
+    # Send the command to tool
+    device.variables_to_save(variable_names)
+
+    # Now this should work    
+    
+def error_box(information):
+    #open dialog and hide the main window
+    root = tk.Tk()
+    root.withdraw()
+    root.lift() #show window above all other applications
+
+    root.attributes("-topmost", True)#window stays above all other applications
+
+    #display meaagebox
+    tkinter.messagebox.showerror(message=information)
+    root.destroy()
+
+def information_box(information):
+    #open dialog and hide the main window
+    root = tk.Tk()
+    root.withdraw()
+    root.lift() #show window above all other applications
+
+    root.attributes("-topmost", True)#window stays above all other applications
+
+    #display meaagebox
+    tkinter.messagebox.showinfo(message=information)
+    root.destroy()
+
+# We plot results: but we will play with the axes!
+def plot_results(values,device):
+    
+    # convert scale from tool to matplotlib
+    def get_scale(scale):
+        if scale == 'LIN':
+            scale = 'linear'
+        else:
+            scale = 'log'
+        return scale
+
+    # Get the names of the plotted variables
+    x_var = device.get_axis_variable('X')
+    y1_var = device.get_axis_variable('Y1')
+    y2_var = device.get_axis_variable('Y2')
+
+    # Get the scales of the plotted variables
+    x_scale = device.get_axis_scale('X') 
+    x_scale = get_scale(x_scale)
+    y1_scale = device.get_axis_scale('Y1')
+    y1_scale = get_scale(y1_scale)
+    y2_scale = device.get_axis_scale('Y2')
+    y2_scale = get_scale(y2_scale)
+
+    # Find the values that will be plotted
+    for key, results in values.items():
+        if x_var in key: # check if the substring is contained
+            x_values = results.copy() # This is a list
+            x_label = key
+        elif y1_var in key:
+            y1_values = results.copy()
+            y1_label = key
+        elif y2_var in key and y2_var !="":
+            y2_values = results.copy()
+            y2_label = key
+
+    # Now lets do the plot
+    fig,ax1 = plt.subplots()
+
+    ax1.set_xlabel(x_label)
+    ax1.set_ylabel(y1_label,color = 'tab:red') # Yellow Color
+    ax1.set_xscale(x_scale)
+    ax1.set_yscale(y1_scale)
+
+    ax1.plot(x_values,y1_values,color='tab:red')
+    ax1.tick_params(axis ='y', labelcolor ='tab:red',which='both')
+
+    if y2_var!= "":
+        # Adding Twin Axes # Blue color
+        ax2 = ax1.twinx()
+
+        ax2.set_ylabel(y2_label,color = 'tab:green')
+        ax2.set_yscale(y2_scale)
+
+        ax2.plot(x_values,y2_values,color='tab:green')
+        ax2.tick_params(axis ='y', labelcolor ='tab:green',which='both')
+
+    display(fig)
+    # The checks will be corrected later
+
+
+def create_file(filename):
+    root = tk.Tk()
+    root.withdraw()
+    root.lift() #show window above all other applications
+
+    root.attributes("-topmost", True)#window stays above all other applications
+
+    file = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text files","*.txt")],title = "save results path",initialfile =filename)
+
+    #check if the file path is correct(.txt)
+    while file.endswith(".txt") == False:
+        #open again filedialog with error message box
+        tk.messagebox.showerror(message='invalid filename!')
+        file = filedialog.asksaveasfilename(defaultextension=".txt", filetypes=[("Text files","*.txt")],title = "save results path",initialfile =filename)
+    root.destroy()
+    return file
+
+# sampling help functions
+
+def check_cons_smu_samp(smus:list):
+    cons_smu_numbers = []
+    for i,smu in enumerate(smus):
+        if smu['mode']!='COMM' and smu["disabled"]==False: # Non Grounded active SMUs
+            cons_smu_numbers.append(i+1) # for the actual number of smus
+
+    return cons_smu_numbers
+
+def load_ini():
+    root = tk.Tk()
+    root.withdraw()
+    root.lift() #show window above all other applications
+
+    root.attributes("-topmost", True)#window stays above all other applications
+
+    
+    file = filedialog.askopenfilename(filetypes=[("Ini files","*.ini")],title ='Select ini file')
+    while file.endswith(".ini") == False:
+        #open again filedialog with error message box
+        answer=tk.messagebox.askyesno(message='Do you want to cancel the ini file load?')
+        if answer == True:
+            raise Exception("Ini File Operation aborted!")
+        else:
+            file = filedialog.askopenfilename(filetypes=[("Ini files","*.ini")],title = "Select ini file")
+    root.destroy()
+    return file
+
+
+# Get measurement mode from ini file
+def get_mode(sections):
+    for section in sections:
+        if "MEASUREMENT MODE:" in section:
+            mode = section.split(":")[1]
+            return mode
+
+    raise Exception("Meaurement mode not found ini file!")
+
+def get_integration(sections):
+    for section in sections:
+        if "INTEGRATION TIME:" in section:
+            integration = section.split(":")[1]
+            return integration
+
+    raise Exception("Integration Time not found ini file!")
+    
+    
+
+    
+    
\ No newline at end of file
diff --git a/hp4155/Custom_SMU/lib/interface.py b/hp4155/Custom_SMU/lib/interface.py
new file mode 100644
index 0000000..c15969a
--- /dev/null
+++ b/hp4155/Custom_SMU/lib/interface.py
@@ -0,0 +1,212 @@
+import ipywidgets as widgets
+from IPython.display import clear_output
+from ipywidgets import GridspecLayout,Layout
+
+
+def create_dict(**widgets):
+    return widgets
+    
+# The smu configuration page
+def page_1():
+    first_page =  GridspecLayout(5,6)
+    
+    #first column
+    first_page[0,0]= widgets.Label("UNIT",layout=Layout(height='auto', width='auto'))
+    first_page[1,0] = widgets.Label('SMU1',layout=Layout(height='auto', width='auto'))
+    first_page[2,0] = widgets.Label('SMU2',layout=Layout(height='auto', width='auto'))
+    first_page[3,0] = widgets.Label('SMU3',layout=Layout(height='auto', width='auto'))
+    first_page[4,0] = widgets.Label('SMU4',layout=Layout(height='auto', width='auto'))
+    
+    #second column voltage name
+    first_page[0,1]= widgets.Label('VNAME',layout=Layout(height='auto', width='auto'))
+    for i in range(1,5):
+        first_page[i,1]= widgets.Text(layout=Layout(height='auto', width='auto'), value =f'V{i}' )
+
+    #Third column Iname
+    first_page[0,2]= widgets.Label('INAME',layout=Layout(height='auto', width='auto'))
+    for i in range(1,5):
+        first_page[i,2]= widgets.Text(layout=Layout(height='auto', width='auto'),value = f'I{i}')
+
+   
+    #Fourth column mode 
+    options_mode = ['V','I','COMM','VPULSE','IPULSE']
+    first_page[0,3]= widgets.Label('MODE',layout=Layout(height='auto', width='auto'))
+    first_page[1,3]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_mode,value = 'COMM') #smu1
+    first_page[2,3]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_mode,value = 'I') #smu2
+    first_page[3,3]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_mode,value = 'V') #smu3
+    first_page[4,3]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_mode,value = 'V') #smu4
+    
+
+    #Fifth column function
+    options_function= ['VAR1','VAR2','VARD','CONS']
+    first_page[0,4]= widgets.Label('FUNC',layout=Layout(height='auto', width='auto'))
+    first_page[1,4]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_function,value ='CONS') #smu1
+    first_page[2,4]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_function,value ='VAR2') #smu2
+    first_page[3,4]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_function,value ='VAR1') #smu3
+    first_page[4,4]= widgets.Dropdown(layout=Layout(height='auto', width='auto'),options = options_function,value ='CONS') #smu4
+
+    # Sixth column ask if smus are disabled
+    first_page[0,5] = widgets.Label("DISABLE")
+    for i in range(1,5):
+        first_page[i,5]= widgets.Checkbox(layout=Layout(height='auto', width='auto'),value = False,indent = False)
+
+    return first_page
+
+
+#page 2 the user function page
+def page_2():
+    second_page = GridspecLayout(7,3)
+
+    # first line (descriptions)
+    second_page[0,0]= widgets.Label('NAME',layout=Layout(height='auto', width='auto'))
+    second_page[0,1]= widgets.Label('UNIT',layout=Layout(height='auto', width='auto'))
+    second_page[0,2]= widgets.Label('DEFINITION',layout=Layout(height='auto', width='auto'))
+
+    # Iterate through the lines and columns for text boxes 
+    for i in range(1,7):
+        for j in range(3):
+            second_page[i,j] = widgets.Text(layout=Layout(height='auto', width='auto'))
+
+    return second_page
+
+#parameters setting
+def page_3():
+    third_page = GridspecLayout(24,5)
+
+    # First Line
+    third_page[0,0]=widgets.Dropdown(options = ['SWEEP','SAMPLING','STRESS'], value = 'SWEEP',description ="MEASUREMENT MODE",layout=Layout(height='auto', width='auto'),style = {'description_width': 'initial'}) 
+    third_page[0,1] = widgets.Dropdown(options = ['SHORt','MEDium','LONG'], value = 'MEDium',description ="INTEGRATION TIME",layout=Layout(height='auto', width='auto'),style = {'description_width': 'initial'})
+
+    # Second Line
+    third_page[2,:] = widgets.Label("SWEEP SETUP",layout=Layout(height='auto', width='auto'))
+
+    # Third Line
+    third_page[3,0] = widgets.Label("VAR1",layout=Layout(height='auto', width='auto'))
+    third_page[3,1] = widgets.Label("VAR2",layout=Layout(height='auto', width='auto'))
+    third_page[3,2] = widgets.Label("VAR1'",layout=Layout(height='auto', width='auto'))
+    third_page[3,3] = widgets.Label("SMU PULSE",layout=Layout(height='auto', width='auto'))
+
+    # Fourth Line
+    third_page[4,0] = widgets.FloatText(0, description = 'START',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[4,1] = widgets.FloatText(0, description = 'START',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[4,2] = widgets.FloatText(description = 'OFFSET',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[4,3] = widgets.FloatText(description = 'PERIOD',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+
+    # Fifth line
+    third_page[5,0] = widgets.FloatText(5,description = 'STOP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[5,1] = widgets.FloatText(500e-6, description = 'STOP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[5,2] = widgets.FloatText(description = 'RATIO',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[5,3] = widgets.FloatText(description = 'WIDTH',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+
+    # Sixth Line
+    third_page[6,0] = widgets.FloatText(0.1,description = 'STEP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[6,1] = widgets.FloatText(6,description = 'NO OF STEPS',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    #skip 5,2
+    third_page[6,3] = widgets.FloatText(5,description = 'BASE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+
+    #Seventh Line
+    third_page[7,0] = widgets.FloatText(100e-3,description = 'COMPLIANCE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[7,1] = widgets.FloatText(5,description = 'COMPLIANCE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[7,2] = widgets.FloatText(description = 'COMPLIANCE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    #no more pulse
+
+    # Eighth Line
+    third_page[8,0] = widgets.FloatText(0,description = 'POWER COMP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[8,1] = widgets.FloatText(0,description = 'POWER COMP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[8,2] = widgets.FloatText(description = 'POWER COMP',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+
+    third_page[9,0] = widgets.Checkbox(False,description = 'HYSTERISIS',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+
+    ## Sampling Parameters
+    third_page[11,:] = widgets.Label("SAMPLING OR STRESS PARAMETERS")    
+    third_page[12,0] = widgets.Dropdown(options=["LINear","L10","L25","L50","THINnedout"], value = 'LINear',description = 'MODE',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[13,0] = widgets.FloatText(2e-3,description = "INITIAL INTERVAL", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[14,0] = widgets.IntText(1001,description = "NO. OF SAMPLES", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[15,0] = widgets.FloatText(0,description = "DURATION", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[16,0] = widgets.FloatText(0,description = "HOLD TIME", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    third_page[17,0] = widgets.Checkbox(True,description = "FILTER", style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+
+
+    # Set constant SMU for all measurement in case they are not disabled
+
+    third_page[19,:] = widgets.Label("CONSTANT",layout=Layout(height='auto', width='auto'))
+    third_page[20,0] = widgets.Label("UNIT",layout=Layout(height='auto', width='auto'))
+    third_page[20,1] = widgets.Label("SMU1",layout=Layout(height='auto', width='auto'))
+    third_page[20,2] = widgets.Label("SMU2",layout=Layout(height='auto', width='auto'))
+    third_page[20,3] = widgets.Label("SMU3",layout=Layout(height='auto', width='auto'))
+    third_page[20,4] = widgets.Label("SMU4",layout=Layout(height='auto', width='auto'))
+
+    third_page[21,0] = widgets.Label("SOURCE",layout=Layout(height='auto', width='auto'))
+    third_page[22,0] = widgets.Label("COMPLIANCE",layout=Layout(height='auto', width='auto'))
+
+    for i in range(21,23):
+        for j in range(1,5):
+            third_page[i,j] = widgets.FloatText(layout=Layout(height='auto', width='auto'))    
+
+    return third_page
+    
+
+#display variables
+def page_4():
+    fourth_page = GridspecLayout(5,4)
+
+    #first line headers
+    fourth_page[0,1]= widgets.Label('X',layout=Layout(height='auto', width='auto'))
+    fourth_page[0,2]= widgets.Label('Y1',layout=Layout(height='auto', width='auto'))
+    fourth_page[0,3]= widgets.Label('Y2',layout=Layout(height='auto', width='auto'))
+
+    #first column 
+    fourth_page[1,0] = widgets.Label('NAME',layout=Layout(height='auto', width='auto'))
+    fourth_page[2,0] = widgets.Label('SCALE',layout=Layout(height='auto', width='auto'))
+    fourth_page[3,0] = widgets.Label('MIN',layout=Layout(height='auto', width='auto'))
+    fourth_page[4,0] = widgets.Label('MAX',layout=Layout(height='auto', width='auto'))
+
+    #iterate through the second line (NAME)
+    fourth_page[1,1]=widgets.Text(layout=Layout(height='auto', width='auto'),value = 'V3')
+    fourth_page[1,2]=widgets.Text(layout=Layout(height='auto', width='auto'),value = 'I3')
+    fourth_page[1,3]=widgets.Text(layout=Layout(height='auto', width='auto'))
+
+    #Iterate through the third line (scale) 
+    options_scale = ['LIN','LOG']
+    for j in range(1,4):
+        fourth_page[2,j] = widgets.Dropdown(value = 'LIN',options = options_scale, layout=Layout(height='auto', width='auto'),)
+
+    #iterate throuh the last 2 lines(min-max)
+    for j in range(1,4):
+        fourth_page[3,j] = widgets.FloatText(value = 0) #min
+
+    fourth_page[4,1]=widgets.FloatText(value = 1) #max X-axis
+    fourth_page[4,2]=widgets.FloatText(value = 0.1) #max Y1-axis
+    fourth_page[4,3]=widgets.FloatText(value = 0) #max Y2-axis
+
+    return fourth_page
+
+
+def page_5():
+    fifth_page = GridspecLayout(14,2)
+
+    fifth_page[0,0] = widgets.Text(description = 'MEASUREMENT NAME',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    fifth_page[1,0] = widgets.Text(description ='PROCESSING NR.',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto')) 
+    fifth_page[2,0] = widgets.Text(description ='SAMPLE SERIES',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    fifth_page[3,0] = widgets.Text(description ='FIELD',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+    fifth_page[4,0] = widgets.Text(description ='DUT',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+
+    fifth_page[5,0] = widgets.Label("VARIABLES TO SAVE",layout=Layout(height='auto', width='auto'))
+    fifth_page[5,1] = widgets.Label("UNIT",layout=Layout(height='auto', width='auto'))
+    for i in range(8):
+        for j in range(2):
+            fifth_page[6+i,j] = widgets.Text(layout=Layout(height='auto', width='auto'))
+        
+    return fifth_page
+
+#change state off the grid
+def change_state(*grids):
+    for grid in grids:
+        for i in range(grid.n_rows):
+            for j in range(grid.n_columns):
+                try:
+                    grid[i,j].disabled = not grid[i,j].disabled
+                except: 
+                    pass
+
+    
diff --git a/hp4155/Custom_SMU/main.py b/hp4155/Custom_SMU/main.py
new file mode 100644
index 0000000..98f8c9c
--- /dev/null
+++ b/hp4155/Custom_SMU/main.py
@@ -0,0 +1,653 @@
+import sys
+sys.path.insert(0, './lib')
+sys.path.insert(0, '..') #append parent directory
+import os
+import configparser
+import warnings
+
+from interface import *
+from help import *
+import hp4155a
+
+first_page = page_1()
+second_page = page_2()
+third_page = page_3()
+fourth_page = page_4()
+fifth_page = page_5()
+
+titles = ["SMUs","User Functions","Parameters","Plotting","Save to file"]
+children = [first_page,second_page,third_page,fourth_page,fifth_page]
+tab = widgets.Tab()
+tab.children = children
+tab.titles = titles
+
+display(tab)
+
+start = widgets.Button(description='Start Measurement')
+ini = widgets.Button(description = 'Import from ini. (Coming Soon)',style = {'description_width': 'initial'},layout=Layout(height='auto', width='auto'))
+output = widgets.Output()
+
+display(widgets.HBox([start,ini]),output)
+
+device = hp4155a.HP4155a('GPIB0::17::INSTR')
+
+def on_start_clicked(b):
+    with output:
+        clear_output()
+        change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+        start.disabled = True
+        ini.disabled = True
+
+        # Reset the device
+        device.reset()
+        
+        # Step 1 create the dictionaries appropriately for every measurement
+        
+        # read general information
+        measurement_mode = third_page[0,0].value
+        measurement_name = fifth_page[0,0].value
+        processing_nr = fifth_page[1,0].value
+        sample_series = fifth_page[2,0].value
+        field = fifth_page[3,0].value
+        dut = fifth_page[4,0].value
+        integration = third_page[0,1].value
+
+        # we need constant smus for all measurements
+        # Constant Smus
+        cons_smus = []
+        for j in range(1,5):
+            cons_smus.append(create_dict(value = third_page[21,j].value,comp=third_page[22,j].value))
+
+        # 2nd page
+        user_functions = []
+        #iterate over the rows
+        for i in range(1,7):
+            if second_page[i,0].value!="": # do not save user functions without names
+                user_functions.append(
+                    create_dict(
+                        name = second_page[i,0].value,
+                        unit=second_page[i,1].value,
+                        expression = second_page[i,2].value
+                    )
+                )
+
+         # Page 4
+        axes = [] # 0 is x-axis , 1 is y1-axis and 2 is y2-axis
+        for j in range(1,4): #iterate over the column
+            axes.append(
+                create_dict(
+                    name = fourth_page[1,j].value,
+                    scale = fourth_page[2,j].value,
+                    min =fourth_page[3,j].value,
+                    max = fourth_page[4,j].value
+                )
+            )
+
+        # Page 5
+        variables = []
+        for i in range(8):
+            if fifth_page[6+i,0].value!="": # do not save empty rows
+                variables.append(
+                    create_dict(
+                        name =  fifth_page[6+i,0].value,
+                        unit =fifth_page[6+i,1].value
+                    )
+                )
+    
+
+        # first sweep
+        if measurement_mode == 'SWEEP':
+            #page 1
+            smus = []
+            #iterate over the rows
+            for i in range(1,5):
+                smus.append(
+                    create_dict(
+                        vname = first_page[i,1].value,
+                        iname = first_page[i,2].value,
+                        mode = first_page[i,3].value,
+                        func = first_page[i,4].value,
+                        disabled = first_page[i,5].value
+                    )
+                )
+            
+           
+            
+            # Page 3
+            #match hysteris checkbox with the command forwarded to the tool
+            if third_page[9,0].value == True:
+                hyst = 'DOUB'
+            else:
+                hyst = 'SING'
+    
+            var1 = create_dict(
+                start = third_page[4,0].value,
+                stop = third_page[5,0].value,
+                step = third_page[6,0].value,
+                comp = third_page[7,0].value,
+                pcomp =third_page[8,0].value,
+                mode = hyst
+            )
+            var2 = create_dict(
+                start = third_page[4,1].value,
+                step = third_page[5,1].value,
+                points = third_page[6,1].value,
+                comp = third_page[7,1].value,
+                pcomp=third_page[8,1].value
+            )
+            vard = create_dict(
+                offset=third_page[4,2].value,
+                ratio = third_page[5,2].value,
+                comp = third_page[7,2].value,
+                pcomp=third_page[8,2].value
+            )
+            
+            pulse = create_dict(
+                period=third_page[4,3].value,
+                width = third_page[5,3].value,
+                base = third_page[6,3].value
+            )
+
+            # Now execute measurement
+
+            #setup sweep measurement mode
+            device.measurement_mode('SWE')
+
+            #disable all irrelevant units
+            device.disable_not_smu()
+
+            # First Setup Smus
+            for i,smu in enumerate(smus):
+                if smu['disabled'] == False:
+                    device.setup_smu(i+1,smu)
+                else: #disabled
+                    device.smu_disable(i+1)
+                
+            
+            # Setup User Functions
+            for user_function in user_functions:
+                device.user_function(user_function['name'],user_function['unit'],user_function['expression'])     
+
+            # Set the integration time
+            
+            device.integration_time(integration)
+            # Setup VAR1 (always in sweep measurements)
+            device.setup_var1(var1) # device will check for errors
+
+            # Now set the 3 additional columns
+            if check_sweep_func(smus,'VAR2') == True:
+                device.setup_var2(var2)
+            if check_sweep_func(smus,'VARD') == True:
+                device.setup_vard(vard)
+            if check_sweep_pulse(smus)== True:
+                device.setup_pulse(pulse)
+
+            # Check for constant SMUs but not grounded
+            cons_smu_numbers = check_sweep_cons(smus)
+            for i, cons_smu in enumerate(cons_smus):
+                if i+1 in cons_smu_numbers: # check if the constant smu was found in the first page func
+                    device.setup_cons_smu(i+1,cons_smu)
+
+            # Now set the axes
+            setup_axes(axes,device)
+
+            # Set the variables to be saved (This function has an exemption)
+            try:
+                save_variables(axes,variables,device)
+            except Exception as e:
+                error_box(e)
+                change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+                start.disabled = False
+                ini.disabled = False
+                return
+                
+
+            # Start the measurement
+            device.single_measurement()
+            while device.operation_completed()==False:
+                pass
+    
+            device.autoscaling()
+
+            # List all errors occured
+            counter,message = device.list_all_errors()
+            if counter>0:
+                error_box(message)
+                change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+                start.disabled = False
+                ini.disabled = False
+                return
+            
+
+            # Get the data from the device 
+            # These are correcly defined and include the plotted ones
+            values = {}
+            for variable in variables:
+                key = variable['name']+"("+variable['unit'] +")"
+                values[key] = device.return_values(variable['name'])
+
+            plot_results(values,device)
+
+            # Save results
+            df = pd.DataFrame(values)
+
+            filename = f"{sample_series}_{field}_{dut}_{measurement_name}_sweep.txt"
+            txt_file = create_file(filename)
+            ini_file = os.path.splitext(txt_file)[0]+'.ini'
+
+            with open(txt_file,'w') as f:
+                date = str(datetime.today().replace(microsecond=0))
+                f.write(f"{measurement_name} (sweep) at {date}:"+"\n")
+                f.write("Sample Information\n")
+                f.write(f"Processing Number:{processing_nr}"+"\n")
+                f.write(f"Sample Series:{sample_series}"+"\n")
+                f.write(f"Field:{field}"+"\n")
+                f.write(f"DUT:{dut}"+"\n")
+                f.write("\n")
+                f.write(f"Measurement parameters can be found at: {ini_file}"+"\n")
+                f.write("\nResults\n")
+            
+            df.to_csv(txt_file,sep=" ",mode='a')
+
+            # export interface to ini file
+            # First the Smus
+            config = configparser.ConfigParser()
+            with open(ini_file,'w') as configfile:
+                config.add_section("THESE ARE THE PARAMETERS OF THE CORRESPONDING MEASUREMENT")
+                config.add_section("THE WHOLE INTERFACE IS SAVED SO FIND WHAT YOU NEED")
+                config.add_section("DO NOT MODIFY THIS FILE")
+                config.add_section(f"MEASUREMENT MODE: {measurement_mode}")
+                config.add_section(f"INTEGRATION TIME: {integration}")
+                
+                # First the smus
+                config.add_section("SMUS")
+                for i, smu in enumerate(smus):
+                    config.add_section(f"SMU{i+1}")
+                    for key,value in smu.items():
+                        config.set(f"SMU{i+1}",key,str(value))
+
+                # Secondly the user functions
+                config.add_section("USER FUNCTIONS")
+                for i, user_function in enumerate(user_functions):
+                    config.add_section(f"USER FUNCTION {i+1}")
+                    for key,value in user_function.items():
+                        config.set(f"USER FUNCTION {i+1}",key,str(value))
+
+                # Then the 3rd page
+                config.add_section("VAR1")
+                for key,value in var1.items():
+                    config.set("VAR1",key,str(value))
+
+                config.add_section("VAR2")
+                for key,value in var2.items():
+                    config.set("VAR2",key,str(value))
+                
+                config.add_section("VARD")
+                for key, value in vard.items():
+                    config.set("VARD",key,str(value))
+
+                config.add_section("PULSE")
+                for key,value in pulse.items():
+                    config.set("PULSE",key,str(value))
+            
+                # Now The constant smus
+                config.add_section('CONSTANT SMUS')
+                for i, cons_smu in enumerate(cons_smus):
+                    config.add_section(f"CONSTANT SMU{i+1}")
+                    for key, value in cons_smu.items():
+                        config.set(f"CONSTANT SMU{i+1}",key,str(value))
+                
+
+                # Page 4 The axes
+                config.add_section('AXES')
+                for i,axis in enumerate(axes):
+                    config.add_section(f"AXIS {i+1}")
+                    for key,value in axis.items():
+                        config.set(f"AXIS {i+1}",key,str(value))
+                
+                # Page 5 The varibles
+                config.add_section("SAVED VARIABLES")
+                for i, variable in enumerate(variables):
+                    config.add_section(f"VARIABLE {i+1}")
+                    for key,value in variable.items():
+                        config.set(f"VARIABLE {i+1}",key,str(value))
+
+                config.write(configfile)
+        # Sampling Measurement Mode
+        elif measurement_mode=='SAMPLING':
+            # sampling parameters
+            parameters= create_dict(
+                mode=third_page[12,0].value,
+                interval=third_page[13,0].value,
+                hold=third_page[16,0].value,
+                points=third_page[14,0].value,
+                filter=int(third_page[17,0].value)
+            )
+            duration = third_page[15,0].value
+
+            # Set the smus all constant
+            #page 1
+            smus = []
+            #iterate over the rows and set the function to constant
+            for i in range(1,5):
+                smus.append(
+                    create_dict(
+                        vname = first_page[i,1].value,
+                        iname = first_page[i,2].value,
+                        mode = first_page[i,3].value,
+                        func = 'CONS',
+                        disabled = first_page[i,5].value
+                    )
+                )
+            
+            # Now start the measurement
+            device.measurement_mode('SAMP')
+
+            #disable all irrelevant units
+            device.disable_not_smu()
+
+            # First Setup Smus
+            for i,smu in enumerate(smus):
+                if smu['disabled'] == False:
+                    device.setup_smu(i+1,smu)
+                else: #disabled
+                    device.smu_disable(i+1)
+                
+            
+            # Setup User Functions
+            for user_function in user_functions:
+                device.user_function(user_function['name'],user_function['unit'],user_function['expression'])     
+
+            # Set the integration time
+            device.integration_time(integration)
+
+            # Set the sampling parameters
+            device.setup_sampling(parameters)
+
+            # Set the total sampling time
+            if duration<= 0:
+                warnings.warn("Non positive measurement duration. Auto Sampling time will be set")
+                device.auto_sampling_time(1)
+            else:
+                device.total_sampling_time(duration)
+
+            # Setup the constant SMUs
+            cons_smu_numbers = check_cons_smu_samp(smus)
+            for i, cons_smu in enumerate(cons_smus):
+                if i+1 in cons_smu_numbers: # check if the constant smu was found in the first page func
+                    device.setup_smu_sampling(i+1,cons_smu)    
+
+            # Now set the axes
+            setup_axes(axes,device)
+
+            # Set the variables to be saved (This function has an exemption)
+            try:
+                save_variables(axes,variables,device)
+            except Exception as e:
+                error_box(e)
+                change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+                start.disabled = False
+                ini.disabled = False
+                return
+                
+
+            # Start the measurement
+            device.single_measurement()
+            while device.operation_completed()==False:
+                pass
+    
+            device.autoscaling()
+
+            # List all errors occured
+            counter,message = device.list_all_errors()
+            if counter>0:
+                error_box(message)
+                change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+                start.disabled = False
+                ini.disabled = False
+                return
+            
+
+            # Get the data from the device 
+            # These are correcly defined and include the plotted ones
+            values = {}
+            for variable in variables:
+                key = variable['name']+"("+variable['unit'] +")"
+                values[key] = device.return_values(variable['name'])
+
+            plot_results(values,device)
+
+            # Save results
+            df = pd.DataFrame(values)
+
+            filename = f"{sample_series}_{field}_{dut}_{measurement_name}_sampling.txt"
+            txt_file = create_file(filename)
+            ini_file = os.path.splitext(txt_file)[0]+'.ini'
+
+            with open(txt_file,'w') as f:
+                date = str(datetime.today().replace(microsecond=0))
+                f.write(f"{measurement_name} (sampling) at {date}:"+"\n")
+                f.write("Sample Information\n")
+                f.write(f"Processing Number:{processing_nr}"+"\n")
+                f.write(f"Sample Series:{sample_series}"+"\n")
+                f.write(f"Field:{field}"+"\n")
+                f.write(f"DUT:{dut}"+"\n")
+                f.write("\n")
+                f.write(f"Measurement parameters can be found at: {ini_file}"+"\n")
+                f.write("\nResults\n")
+            
+            df.to_csv(txt_file,sep=" ",mode='a')
+
+            # export interface to ini file
+            # First the Smus
+            config = configparser.ConfigParser()
+            with open(ini_file,'w') as configfile:
+                config.add_section("THESE ARE THE PARAMETERS OF THE CORRESPONDING MEASUREMENT")
+                config.add_section("THE WHOLE INTERFACE IS SAVED SO FIND WHAT YOU NEED")
+                config.add_section("DO NOT MODIFY THIS FILE")
+                config.add_section(f"MEASUREMENT MODE: {measurement_mode}")
+                config.add_section(f"INTEGRATION TIME: {integration}")
+                
+                # First the smus
+                config.add_section("SMUS")
+                for i, smu in enumerate(smus):
+                    config.add_section(f"SMU{i+1}")
+                    for key,value in smu.items():
+                        config.set(f"SMU{i+1}",key,str(value))
+
+                # Secondly the user functions
+                config.add_section("USER FUNCTIONS")
+                for i, user_function in enumerate(user_functions):
+                    config.add_section(f"USER FUNCTION {i+1}")
+                    for key,value in user_function.items():
+                        config.set(f"USER FUNCTION {i+1}",key,str(value))
+
+                # Then the 3rd page
+                config.add_section('SAMPLING PARAMETERS')
+                for key,value in parameters.items():
+                    config.set('SAMPLING PARAMETERS',key,str(value))
+
+                # Now the constant smus
+                config.add_section('CONSTANT SMUS')
+                for i, cons_smu in enumerate(cons_smus):
+                    config.add_section(f"CONSTANT SMU{i+1}")
+                    for key, value in cons_smu.items():
+                        config.set(f"CONSTANT SMU{i+1}",key,str(value))
+                
+
+                # Page 4 The axes
+                config.add_section('AXES')
+                for i,axis in enumerate(axes):
+                    config.add_section(f"AXIS {i+1}")
+                    for key,value in axis.items():
+                        config.set(f"AXIS {i+1}",key,str(value))
+                
+                # Page 5 The varibles
+                config.add_section("SAVED VARIABLES")
+                for i, variable in enumerate(variables):
+                    config.add_section(f"VARIABLE {i+1}")
+                    for key,value in variable.items():
+                        config.set(f"VARIABLE {i+1}",key,str(value))
+
+                config.write(configfile)
+        else: # Stress
+            #page 1
+            smus = []
+            #iterate over the rows and set the function to constant
+            for i in range(1,5):
+                # Set SYNC non SYNC mode
+                mode= first_page[i,3].value
+                if mode != 'COMM':
+                    func = 'SYNC'
+                else:
+                    func = 'NSYNC'
+                smus.append(
+                    create_dict(
+                        name = first_page[i,1].value,
+                        mode = mode,
+                        func = func,
+                        disabled = first_page[i,5].value
+                    )
+                )
+
+            # Now define the parameters
+            duration = third_page[15,0].value
+            if duration <= 0:
+                error_box("Stress Time should be positive!")
+                change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+                start.disabled = False
+                ini.disabled = False
+                return
+            hold_time = third_page[16,0].value
+            filter = int(third_page[17,0].value)
+
+            # Now start the measurement
+            device.stress_page()
+            device.stress_disable_not_smu()
+
+            # First Setup Smus
+            for i,smu in enumerate(smus):
+                if smu['disabled'] == False:
+                    device.smu_stress(i+1,smu)
+                else: #disabled
+                    device.smu_stress_disable(i+1)
+            
+            # Now set the Parameters
+            device.stress_filter(filter)
+            device.stress_hold_time(hold_time)
+            device.stress_time(duration)
+
+            cons_smu_numbers = check_cons_smu_samp(smus) # works also for sampling
+            for i, cons_smu in enumerate(cons_smus):
+                if i+1 in cons_smu_numbers: # check if the constant smu was found in the first page func
+                    device.setup_smu_stress(i+1,cons_smu)
+        
+            # Now start the measurement
+            device.start_stress()
+            while device.operation_completed() == False:
+                pass
+            
+            # List all errors occured
+            counter,message = device.list_all_errors()
+            if counter>0:
+                error_box(message)
+                change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+                start.disabled = False
+                ini.disabled = False
+                return
+
+            filename = f"{sample_series}_{field}_{dut}_{measurement_name}_stress.txt"
+            txt_file = create_file(filename)
+            ini_file = os.path.splitext(txt_file)[0]+'.ini'
+
+            with open(txt_file,'w') as f:
+                date = str(datetime.today().replace(microsecond=0))
+                f.write(f"{measurement_name} (Stress) at {date}:"+"\n")
+                f.write("Sample Information\n")
+                f.write(f"Processing Number:{processing_nr}"+"\n")
+                f.write(f"Sample Series:{sample_series}"+"\n")
+                f.write(f"Field:{field}"+"\n")
+                f.write(f"DUT:{dut}"+"\n")
+                f.write("\n")
+                f.write(f"Measurement parameters can be found at: {ini_file}"+"\n")
+                f.write("\nNo Results Available\n")
+            
+            #export the interface to ini file
+            config = configparser.ConfigParser()
+            with open(ini_file,'w') as configfile:
+                config.add_section("THESE ARE THE PARAMETERS OF THE CORRESPONDING MEASUREMENT")
+                config.add_section("THE WHOLE INTERFACE IS SAVED SO FIND WHAT YOU NEED")
+                config.add_section("DO NOT MODIFY THIS FILE")
+                config.add_section(f"MEASUREMENT MODE: {measurement_mode}")
+                
+                # First the smus
+                config.add_section("SMUS")
+                for i, smu in enumerate(smus):
+                    config.add_section(f"SMU{i+1}")
+                    for key,value in smu.items():
+                        config.set(f"SMU{i+1}",key,str(value))
+
+                config.add_section('PARAMETERS')
+                config.set('PARAMETERS', "STRESS TIME",str(duration))
+                config.set('PARAMETERS', "HOLD TIME",str(hold_time))
+                config.set('PARAMETERS', "FILTER",str(filter))
+        
+        
+                # Now the constant smus
+                config.add_section('CONSTANT SMUS')
+                for i, cons_smu in enumerate(cons_smus):
+                    config.add_section(f"CONSTANT SMU{i+1}")
+                    for key, value in cons_smu.items():
+                        config.set(f"CONSTANT SMU{i+1}",key,str(value))
+                config.write(configfile)
+
+        # End of fuction
+        information_box("Measurement finished!")
+        change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+        start.disabled = False
+        ini.disabled = False
+        return # just to be sure
+
+# This should be tested at the end.
+# After the ini files have been created
+"""
+def on_ini_clicked(b):
+    with output:
+        clear_output()
+        change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+        start.disabled = True
+        ini.disabled = True
+
+        #load values to the interface
+        config = configparser.ConfigParser()
+        try:
+            file = load_ini()
+        except Exception as e:
+            error_box(e)
+            change_state(first_page,second_page,third_page,fourth_page,fifth_page)
+            start.disabled = False
+            ini.disabled = False
+            return
+        try:
+            # Now we do exactly the opposite thing dictionaries to widgets
+            #read the values from each section
+            config.read(file)
+
+            # Get the sections 
+            sections = config.sections()
+            # Get the measurement Mode
+            measurement_mode = get_mode(sections)
+
+            # Get the constant smus
+            for j in range(1,5):
+                third_page[21,j].value = config.get(f"CONSTANT SMU{j}",'value') 
+                third_page[22,j].value = config.get(f"CONSTANT SMU{j}",'comp')
+                
+
+            if measurement_mode == 'SWEEP':
+                third_page[0,0].value = measurement_mode
+                third_page[0,1].value = get_integration(sections)
+
+"""                
+            
+start.on_click(on_start_clicked)
\ No newline at end of file
diff --git a/hp4155/hp4155a.py b/hp4155/hp4155a.py
index 854256c..a47efe5 100644
--- a/hp4155/hp4155a.py
+++ b/hp4155/hp4155a.py
@@ -22,16 +22,37 @@ class HP4155a(object):
         command = f":PAGE:STR:SMU{number}:MODE {smu['mode']};FUNC {smu['func']};NAME {repr(smu['name'])}"
         self.inst.write(command)
 
+    def setup_smu_stress(self,number:int,smu:dict):
         command =f":PAGE:STR:SET:CONS:SMU{number} {smu['value']}"
         self.inst.write(command)
         
         command = f":PAGE:STR:SET:CONS:SMU{number}:COMP {smu['comp']}"
         self.inst.write(command)
-    
+
+    def smu_stress_disable(self,number:int):
+        command = f":PAGE:STR:SMU{number}:DIS"
+        self.inst.write(command)
+
+    def stress_filter(self,status:int):
+        command = f":PAGE:STR:SET:FILT {status}"
+        self.inst.write(command)
+
+    def stress_hold_time(self,time:float):
+        command = f":PAGE:STR:SET:HTIM {time}"
+        self.inst.write(command)
+
     #set the stess time in seconds
     def stress_time(self,time:float):
         command = f":PAGE:STR:SET:DUR {time}"
         self.inst.write(command)
+
+    def stress_disable_not_smu(self):
+        self.inst.write(":PAGE:STR:VSU1:DIS")
+        self.inst.write(":PAGE:STR:VSU2:DIS")
+
+    def stress_smu_disable(self,number:int):
+        command = f":PAGE:STR:SMU{number}:DIS"
+        self.inst.write(command)
         
     #start stress operation
     def start_stress(self):
@@ -58,7 +79,17 @@ class HP4155a(object):
     def clear_error_stack(self):
         code,_ = self.error()
         while code != 0:
-            code,_ = self.error() 
+            code,_ = self.error()
+
+    def list_all_errors(self):
+        counter = 0
+        error_code, error_message = self.error()
+        message = f"{error_code},{error_message}"+"\n"
+        while error_code!=0:
+            counter = counter + 1
+            error_code, error_message = self.error()
+            message = message + f"{error_code},{error_message}"+"\n"
+        return counter, message
 
     def operation_completed(self):
         text = self.inst.query('*OPC?')
@@ -188,6 +219,11 @@ class HP4155a(object):
         command = f":PAGE:DISP:GRAP:{axis}:NAME?"
         var_name = self.inst.query(command)
         return var_name.rstrip() #remove \n
+
+    def get_axis_scale(self,axis):
+        command = f":PAGE:DISP:GRAP:{axis}:SCAL?"
+        scale = self.inst.query(command)
+        return scale.rstrip()
     
     def autoscaling(self):
         self.inst.write(":PAGE:GLIS:SCAL:AUTO ONCE")
-- 
GitLab