# -*- coding: UTF-8 -*-
# Calibration and/or adjustment app for Keithley 2400, v0.8, Jan.30.2024 (C) xDevs.com
# Tested with Raspberry Pi and Windows on Python 2.7.18 and Python 3.7.5
# DMM7510 stuff is untested yet
# https://xdevs.com/fix/kei2400
# https://xdevs.com/fix/kei2400pa
# https://xdevs.com/fix/hp3458a
import os
import sys
import time
import signal
import numbers
import numpy as np
import six
from colorama import init
from math import log10

init(convert=True)                              # Fix ESCAPE-commands support issues with windows

'''
Currently this scripts only works with following DMMs:
 * 3458a   = HP/Agilent/Keysight 3458A 8.5-digit DMM with GPIB
 * 2002    = Keithley 2002 8.5-digit DMM with GPIB
 * 2001    = Keithley 2001 7.5-digit DMM with GPIB
 * 7510    = Keithley 7510 7.5-digit DMM with GPIB
 * 7510lan = Keithley 7510 7.5-digit DMM with LAN connection
# Possible future improvements:
 * Add better logging
 * Put reading error into function
 * Parametrize NPLC, delays, sample time
 * Add more verbose data
 * Show better stats / GUI?
 * Eventually refactor calibration function into modules and functions for easier code structure
'''

###################### Configuration settings for script #############################################################

dmm_temp = 23.0
dmm_type = "3458a"                                   # Reference DMM type. Supported types listed in legend above
hw_interface_type = "vxi"                           # Please select vxi or visa or linux-gpib
samples_num = 16                                    # Number of samples to take a average from 3458A, should be more than 5
dmm_nplc = 20                                       # NPLC for DMM
run_acal = False                                     # If "True" then execute ACAL ALL on 3458A before calibration
gpib_addr_k2400 = 24                                # GPIB address of HP 3458A
gpib_addr_dmm = 3                                   # GPIB address of DUT K2400
gpib_vxi_ip = "192.168.1.10"                        # TCP/IP address of Agilent E5810A GPIB-LAN gateway
dmm_vxi_ip = "192.168.1.12"                         # TCP/IP address for LAN-equipped reference DMM (e.g. DMM7510)
skip_pre_calibration = False                        # Do not perform calibration before adjustment if True
skip_post_calibration = False                       # Do not perform calibration after adjustment if True
cal_date_str = time.strftime('%Y, %m, %d')          # String for date write after adjustment, YEAR, MM, DD
cal_ndue_str = "2026, 09, 20"                       # String for ndue write after adjustment, YEAR, MM, DD, enter by user
today_date = time.strftime("%d_%B_%Y_%H_%M")        # Datestamp of runtime script
logfilen = 'callog_rev10_am2400_gpib24_%s.log' % today_date   # Filename for datalog and command reports

#####################################################################################################################


if (hw_interface_type == "vxi"):
    import vxi11                           # Import library for Agilent E5810A/B GPIB to LAN bridge
elif (hw_interface_type == "visa"):
    import pyvisa                          # Import library for VISA interface
elif (hw_interface_type == "linux-gpib"):
    import Gpib                            # Import library for USB linux-gpib (works only on Linux)
else:
    print ('\033[31;1m -e- Incorrect interface define, use "vxi" or "visa" or "linux-gpib" for hw_interface_type parameter\033[39;0m')

def intro():
    if (dmm_type == "3458a"):
        ref_type = "HP 3458A DMM    "
    elif (dmm_type == "2002"):
        ref_type = "Keithley 2002   "
    elif (dmm_type == "2001"):
        ref_type = "Keithley 2001   "
    elif ((dmm_type == "7510") or (dmm_type == "7510lan")):
        ref_type = "Keithley DMM7510"
    # Print introduction info block
    print ("\033[40;37m╔═══════════════════════════════════════════════════════════════════════════════════╗")
    print ("║ xDevs.com calibration and adjustment tool for Keithley 2400 SourceMeters (C) 2024 ║")
    print ("╠═══════════════════════════════════════════════════════════════════════════════════╣")
    if (hw_interface_type == "vxi"):
        print ("║        Using VXI LAN-GPIB adapter and \033[34;0m%16s \033[37;40m as reference              ║" % ref_type)
    elif (hw_interface_type == "visa"):
        print ("║         Using VISA interface link and \033[34;0m%16s \033[37;0m as reference              ║" % ref_type)
    elif (hw_interface_type == "linux-gpib"):
        print ("║      Using linux-gpib and USB dongle with \033[34;0m%16s \033[37;0m as reference          ║" % ref_type)
    if (dmm_type) == "7510lan":
        print ("║      DMM7510 connected over the LAN with IP address %15s                       ║" % dmm_vxi_ip)
    print ("║  SMU GPIB address : \033[92;1m%02d\033[40;37m     DMM GPIB address : \033[93;1m%02d\033[40;37m   VXI IP: \033[94;1m%21s\033[40;37m  ║" % (gpib_addr_k2400, gpib_addr_dmm, gpib_vxi_ip ))
    print ("║     \033[93;41mDo not swap terminals during calibration, high voltage may be present !\033[40;37m       ║")
    print ("║ Calibration: verification by DMM, NO adjustment. Adjustment: Deviation correction ║")
    print ("║ ! For additional details reach us at https://xdevs.com/contact/           REV 0.8 ║")
    print ("╚═══════════════════════════════════════════════════════════════════════════════════╝")
    print ("Stamp: ", today_date, "   Logfile:", logfilen)
    print ("\033[49;39m")
    # Output legend block into the log-file as well
    with open(logfilen,'a') as b:
        b.write("╔═══════════════════════════════════════════════════════════════════════════════════╗\r")
        b.write("║ xDevs.com calibration and adjustment tool for Keithley 2400 SourceMeters (C) 2024 ║\r")
        b.write("╠═══════════════════════════════════════════════════════════════════════════════════╣\r")
        if (hw_interface_type == "vxi"):
            b.write("║        Using VXI LAN-GPIB adapter and %16s  as reference              ║\r" % ref_type)
        elif (hw_interface_type == "visa"):
            b.write("║         Using VISA interface link and %16s  as reference              ║\r" % ref_type)
        elif (hw_interface_type == "linux-gpib"):
            b.write("║      Using linux-gpib and USB dongle with %16s  as reference          ║\r" % ref_type)
        if (dmm_type) == "7510lan":
            b.write("║      DMM7510 connected over the LAN with IP address %15s                       ║\r" % dmm_vxi_ip)
        b.write("║  SMU GPIB address : %02d     DMM GPIB address : %02d   VXI IP: %21s  ║\r" % (gpib_addr_k2400, gpib_addr_dmm, gpib_vxi_ip ))
        b.write("║ Calibration: verification by DMM, NO adjustment. Adjustment: Deviation correction ║\r")
        b.write("║ ! For additional details reach us at https://xdevs.com/contact/           REV 0.8 ║\r")
        b.write("╚═══════════════════════════════════════════════════════════════════════════════════╝\r")


intro()
dmm_val = 1e-9 # Temp value

class Timeout():
  """Timeout class using ALARM signal"""
  class Timeout(Exception): pass

  def __init__(self, sec):
    self.sec = sec

  def __enter__(self):
    if os.name == "nt":
        time.sleep(0.1)
    else:
        signal.signal(signal.SIGALRM, self.raise_timeout)
        signal.alarm(self.sec)

  def __exit__(self, *args):
    if os.name == "nt":
        time.sleep(0.1)
    else:
        signal.alarm(0) # disable alarm

  def raise_timeout(self, *args):
    raise Timeout.Timeout()

# Class for the reference DMM instrument
class dmm():
    global dmm_val
    data = ""
    temp = 26.5
    set_nplc = dmm_nplc # temporary internal value

    def __init__(self,gpib,name):
        self.gpib = gpib
        set_nplc = dmm_nplc
        if (hw_interface_type == "vxi"):
            try:
                self.inst = vxi11.Instrument(dmm_vxi_ip, "gpib0,%d" % gpib_addr_dmm)
                self.inst.timeout = 20
            except:
                print ('\033[31;1m -e- Cound not initialize GPIB-LAN bridge at %s for GPIB %d\033[39;0m' % (dmm_vxi_ip, gpib_addr_dmm))
        elif (hw_interface_type == "visa"):
            rm = pyvisa.ResourceManager()
            #rm.list_resources()   # Debug listing for all available interfaces
            self.inst = rm.open_resource('GPIB::%d"::INSTR' % gpib_addr_dmm)
        elif (hw_interface_type == "linux-gpib"):
            self.inst = Gpib.Gpib(0,gpib_addr_dmm,timeout = 20)
        self.name = name
        if (dmm_type == "3458a"):
            self.init_inst_3458()
        elif (dmm_type == "2002"):
            self.init_inst_2002()
        elif (dmm_type == "2001"):
            self.init_inst_2001()
        elif ((dmm_type == "7510") or (dmm_type == "7510lan")):
            self.init_inst_7510()

    def init_inst_3458(self):
        # Setup HP 3458A
        self.inst.write ("PRESET NORM")
        self.inst.write ("OFORMAT ASCII")
        self.inst.write ("FUNC DCV,AUTO")
        self.inst.write ("TARM HOLD")
        self.inst.write ("TRIG LINE")
        self.inst.write ("NPLC %.3f" % dmm_nplc)
        self.inst.write ("AZERO ON")
        self.inst.write ("NRDGS 1,AUTO")
        self.inst.write ("MEM OFF")
        self.inst.write ("END ALWAYS")
        self.inst.write ("NDIG 9")
        self.inst.write ("DELAY 0")

    def init_inst_2001(self):
        # Setup Keithley 2001 DVM
        if (dmm_nplc > 10):
            set_nplc = 10
        else:
            set_nplc = dmm_nplc
        self.inst.write("*RST\n")
        self.inst.write(":SYST:PRES")
        self.inst.write(":INIT:CONT OFF\n")
        self.inst.write(":SYST:AZER:TYPE SYNC\n")
        self.inst.write(":SYST:AZER:STAT ON\n")
        self.inst.write(":ABOR\n")
        self.inst.write(":SYST:LSYN:STAT ON;\n")
        self.inst.write(":INP:PRE:STAT OFF")
        self.inst.write(":SENS:VOLT:DC:AVER:COUN 10")
        self.inst.write(":SENS:VOLT:DC:DIG 8")
        self.inst.write(":SENS:VOLT:DC:AVER:ADV OFF; TCON MOV")
        self.inst.write(":SENS:VOLT:DC:NPLC %.3f" % set_nplc)
        self.inst.write(":SENS:CURR:DC:AVER:COUN 10")
        self.inst.write(":SENS:CURR:DC:AVER:ADV OFF; TCON MOV")
        self.inst.write(":SENS:CURR:DC:NPLC %.3f" % set_nplc)
        self.inst.write(":SENS:CURR:DC:DIG 8")
        self.inst.write(":FORM:ELEM READ")

    def init_inst_2002(self):
        # Setup Keithley 2002 DVM
        if (dmm_nplc > 50):
            set_nplc = 50
        else:
            set_nplc = dmm_nplc
        self.inst.write("*RST\n")
        self.inst.write(":SYST:PRES")
        self.inst.write(":INIT:CONT OFF\n")
        self.inst.write(":SYST:AZER:TYPE SYNC\n")
        self.inst.write(":SYST:AZER:STAT ON\n")
        self.inst.write(":ABOR\n")
        self.inst.write(":SYST:LSYN:STAT ON;\n")
        self.inst.write(":INP:PRE:STAT OFF")
        self.inst.write(":SENS:VOLT:DC:AVER:COUN 10")
        self.inst.write(":SENS:VOLT:DC:DIG 8")
        self.inst.write(":SENS:VOLT:DC:AVER:ADV OFF; TCON MOV")
        self.inst.write(":SENS:VOLT:DC:NPLC %.3f" % set_nplc)
        self.inst.write(":SENS:CURR:DC:AVER:COUN 10")
        self.inst.write(":SENS:CURR:DC:AVER:ADV OFF; TCON MOV")
        self.inst.write(":SENS:CURR:DC:NPLC %.3f" % set_nplc)
        self.inst.write(":SENS:CURR:DC:DIG 8")
        self.inst.write(":FORM:ELEM READ")

    def init_inst_7510(self):
        # Setup Keithley DMM7510
        if (dmm_nplc > 15):
            set_nplc = 15
        else:
            set_nplc = dmm_nplc
        self.inst.write("*RST\n")
        self.inst.write(":SYST:PRES")
        self.inst.write(":INIT:CONT OFF\n")
        self.inst.write(":SYST:AZER:TYPE SYNC\n")
        self.inst.write(":SYST:AZER:STAT ON\n")
        self.inst.write(":ABOR\n")
        self.inst.write(":SYST:LSYN:STAT ON;\n")
        self.inst.write(":INP:PRE:STAT OFF")
        self.inst.write(":SENS:VOLT:DC:AVER:COUN 10")
        self.inst.write(":SENS:VOLT:DC:DIG 7")
        self.inst.write(":SENS:VOLT:DC:AVER:ADV OFF; TCON MOV")
        self.inst.write(":SENS:VOLT:DC:NPLC %.3f" % set_nplc)
        self.inst.write(":SENS:CURR:DC:AVER:COUN 10")
        self.inst.write(":SENS:CURR:DC:AVER:ADV OFF; TCON MOV")
        self.inst.write(":SENS:CURR:DC:NPLC %.3f" % set_nplc)
        self.inst.write(":SENS:CURR:DC:DIG 7")
        self.inst.write(":FORM:ELEM READ")

    def set_dcv_range(self, cmd):
        if (dmm_type == "3458a"):
            self.inst.write ("NPLC %.3f" % dmm_nplc)
            self.inst.write ("DCV %.6e" % cmd)
        else:
            self.inst.write(":SENS:FUNC 'VOLT:DC'")
            self.inst.write(":SENS:VOLT:DC:DIG 8")
            self.inst.write(":SENS:VOLT:DC:RANG %.6e" % cmd)

    def set_dci_range(self, cmd):
        if (dmm_type == "3458a"):
            self.inst.write ("NPLC %.3f" % dmm_nplc)
            self.inst.write ("DCI %.6e" % cmd)
        else:
            self.inst.write(":SENS:FUNC 'CURR:DC'")
            self.inst.write(":SENS:CURR:DC:DIG 8")
            self.inst.write(":SENS:CURR:DC:RANG %.6e" % cmd)

    def switch_dci(self):
        if (dmm_type == "3458a"):
            self.inst.write ("PRESET NORM")
            self.inst.write ("AZERO ON")
            self.inst.write ("NRDGS 1,AUTO")
            self.inst.write ("OFORMAT ASCII")
            self.inst.write ("FUNC DCI,AUTO")
            self.inst.write ("NDIG 9")
        else:
            self.inst.write(":ABOR\n")
            self.inst.write(":SENS:FUNC 'CURR:DC'")
            self.inst.write(":SENS:CURR:DC:DIG 8")

    def write(self,cmd):
        self.inst.write(cmd)

    def read(self):
        return self.inst.read()

    def read_data(self,cmd):
        data_float = 0.0
        data_str = ""
        self.inst.write(cmd)

        try:
            with Timeout(10):
                data_str = self.inst.read()
        except Timeout.Timeout:
            print ("Timeout exception from dmm %s on read_data() inst.read()\n" % self.name)
            return (0,float(0))
        #print ("Reading from dmm %s = %s" % (self.name,data_str))
        try:
            data_float = float(data_str)
        except ValueError:
            print("Exception thrown by dmm %s on read_data() - ValueError = %s\n" % (self.name,data_str))
            return (0,float(0)) # Exception on float conversion, 0 = error
        return (1,data_float) # Good read, 1 = converted to float w/o exception

    def get_temp(self):
        global dmm_temp
        print("\r\nReading DMM temp\r\n")
        if (dmm_type == "3458a"):
            self.inst.write("TARM AUTO,1")
            self.temp_status_flag,temp = self.read_data("TEMP?")
        else:
            print ("\r\nThis DMM does not support internal temperature readout\r\n")
            return 0

        if (self.temp_status_flag):
            self.temp = temp
        dmm_temp = self.temp
        return self.temp

    def get_temp_status(self):
        return self.temp_status_flag

    def get_data(self):
        global dmm_val
        if (dmm_type == "3458a"):
            self.status_flag,data = self.read_data("TARM AUTO,1")
        else:
            self.status_flag,data = self.read_data("READ?")
        if (self.status_flag):
            self.data = data
            dmm_val = float(data)
        return self.data

    def exec_acal(self):
        if (dmm_type == "3458a"):
            sys.stdout.write ("\r\n\033[1;35m -i- ACAL ALL procedure start, please wait 15 minutes to complete.\r\n")
            self.inst.write("ACAL ALL")
            for cnt in range(0 ,290):        # wait 870 seconds, print dot every 3s
                sys.stdout.write ("\033[0;40m*")
                time.sleep(3)
                sys.stdout.flush()
            print("ACAL procedure done...\033[1;39m")
        else:
            print("This instrument does not support ACAL\033[1;39m")

    def exec_idn(self):
        if (dmm_type == "3458a"):
            self.inst.write ("END ALWAYS")
            self.inst.write ("ID?")
        else:
            self.inst.write ("*IDN?")
        dat = self.inst.read()
        tstr = dat.split()
        if (tstr[0] == "HP3458A"):
            sys.stdout.write ("\r\033[1;32m%s detected...\r\n\033[1;39m" % tstr[0])
            b.write("\r%s detected... \r" % tstr)
        elif ((tstr[0] == "KEITHLEY") and (tstr[3][:4] == "2002")):
            sys.stdout.write ("\r\033[1;32mKeithley 2002 detected... %s \r\n\033[1;39m" % tstr)
            b.write("\rKeithley 2002 detected... %s \r" % tstr)
        elif ((tstr[0] == "KEITHLEY") and (tstr[3][:4] == "2001")):
            sys.stdout.write ("\r\033[1;32mKeithley 2001 detected... %s \r\n\033[1;39m" % tstr)
            b.write("\rKeithley 2001 detected... %s \r" % tstr)
        elif ((tstr[0] == "KEITHLEY") and (tstr[2][:7] == "DMM7510")):
            sys.stdout.write ("\r\033[1;32mKeithley DMM7510 detected... %s \r\n\033[1;39m" % tstr)
            b.write("\rKeithley DMM7510 detected... %s \r" % tstr)
        else:
            sys.stdout.write ("\r\033[1;31mNo reference DMM present, exiting!\033[1;39m")
            quit()

    def get_data_status(self):
        return self.status_flag

def large_msg(mode, text_line):
    print ("\033[44;93m")
    if (mode == "dcv"):
        mode_msg = "\033[96;44mVOLTAGE\033[44;93m"
    elif (mode == "dci"):
        mode_msg = "\033[91;44mCURRENT\033[44;93m"

    if (text_line == 0):
        text_msg = "perform calibration of the SMU %s accuracy test    " % mode_msg
    elif (text_line == 1):
        text_msg = "perform adjustment  of the SMU %s output mode      " % mode_msg

    print ("╔══════════════════════════════════════════════════════════════════════════════════╗")
    print ("║   Please connect SMU Force terminals to reference DMM HI/LO %s INPUT        ║" % mode_msg)
    print ("╠══════════════════════════════════════════════════════════════════════════════════╣")
    print ("║     Next process will %s   ║" % text_msg)
    print ("║  Do not touch the terminals or cables in runtime, dangerous signals are possible ║")
    print ("║         Press ENTER to continue after connections are done and verified          ║")
    print ("╚══════════════════════════════════════════════════════════════════════════════════╝")
    print ("\033[49;39m\r\n")

def perftest():
    sys.stdout.write("\033[35;1m ==== Performance DCV test ==== \033[39;0m \r\n")

    large_msg("dcv",0)
    try:
        input("Press Enter key to continue...")
    except SyntaxError:
        pass

    if (dmm_type == "3458a"):
        dmm.get_temp()
        print ("\r\n\033[0;33mHP3458A TEMP = %2.1f C\033[0;33m" % dmm_temp)
        b.write ("HP3458A TEMP = %2.1f C\r" % dmm_temp)

    for cnt in range (0, len(test_dcv_set)):
        kei2400.write (":SOUR:FUNC VOLT")
        dmm.set_dcv_range(test_dcv_rng[cnt])
        kei2400.write (":SENS:CURR:PROT 0.01")
        kei2400.write (":SENS:CURR:RANG 0.01")
        kei2400.write (":OUTP:STAT ON")
        kei2400.write (":SOUR:VOLT:RANGE %3.1e" % test_dcv_rng[cnt])
        kei2400.write (":SOUR:VOLT %3.1e" % test_dcv_set[cnt])
        time.sleep(3)

        dcvarr = []
        val = 0

        if (abs(test_dcv_set[cnt]) > 100):
            # Perform additional dummy 60 measurement steps to allow for dividers in DUT and DMM warm-up for >100 V points
            for samples in range (0, 60):
                dmm.get_data()
                if (abs(dmm_val) > 1e10):
                    kei2400.write (":OUTP:STAT OFF")
                    print ("\033[41;93m -ERROR- Output is overflow, incorrect DCV connections? \033[39;49m\r")
                    b.write("\rERROR: Incorrect measurement value: %.7e\r" % dmm_val)
                    quit()
                print ("Warmup DCV %d : %.9E VDC\r" % (samples, dmm_val))

        for samples in range (0, samples_num):
            dmm.get_data()
            if (abs(val) > 1e10):
                kei2400.write (":OUTP:STAT OFF")
                for beepcnt in range(0,3):
                    kei2400.write (":SYST:BEEP:IMM 4000, 5")
                    kei2400.write (":SYST:BEEP:IMM 2000, 5")
                print ("\033[91;1m -ERROR- Output is overflow, incorrect DCV connections? \033[39;0m\r")
                b.write ("\r -ERROR- Output is overflow, incorrect DCV connections? \r")
                quit()
            else:
                print ("Measure DCV %d : %.9E VDC\r" % (samples, dmm_val))
                dcvarr.append(dmm_val)
                val += dmm_val
        val = val / samples_num # Averaged
        median_dcv = np.median(dcvarr[-5:])
        sdev_dcv = np.std(dcvarr[-5:])
        ppm = (((median_dcv+1e-16) / (test_dcv_set[cnt] + 1E-16)) - 1) * 1E6

        if (abs(ppm) < 10e4):
            sys.stdout.write("\033[35;1m Verification step %02d (%.4f V) : Median %.7E VDC [deviation %6.2f ppm], SDEV = %.3f uV \033[39;0m \r\n" % (cnt, test_dcv_set[cnt], median_dcv, ppm, sdev_dcv * 1e6))
            b.write("DCV Verification step %02d (%.4EV ) : %.8E VDC median, deviation %.3f ppm, sdev = %.3f uV\r" % (cnt, test_dcv_set[cnt], median_dcv, ppm, sdev_dcv * 1e6))
        else:
            sys.stdout.write("\033[35;1m Verification step %02d (%.4f V) : Median %.7E VDC zero point, SDEV = %.3f uV \033[39;0m \r\n" % (cnt, test_dcv_set[cnt], median_dcv, sdev_dcv * 1e6))
            b.write("DCV Verification step %02d (%.4EV ) : %.8E VDC median, sdev = %.3f uV\r" % (cnt, test_dcv_set[cnt], median_dcv, sdev_dcv * 1e6))
        cnt = cnt + 1
        if six.PY2:
            while len(dcvarr) > 0: dcvarr.pop()
        else:
            dcvarr.clear()

    large_msg("dci",0)
    try:
        input("Press Enter key to continue...")
    except SyntaxError:
        pass

    sys.stdout.write("\033[35;1m ==== Performance DCI test ==== \033[39;0m \r\n")
    dmm.get_temp()
    dmm.switch_dci()
    sys.stdout.write("3458A TEMP? = %02.1f \r\n" % (dmm_temp))

    for cnt in range (0, len(test_dci_set)):
        kei2400.write (":SOUR:FUNC CURR")
        dmm.set_dci_range(test_dci_rng[cnt])
        kei2400.write (":SENS:VOLT:PROT 10")
        kei2400.write (":SENS:VOLT:RANG 10")
        kei2400.write (":OUTP:STAT ON")
        kei2400.write (":SOUR:CURR:RANGE %3.1e" % test_dci_rng[cnt])
        kei2400.write (":SOUR:CURR %3.1e" % test_dci_set[cnt])
        time.sleep(3)

        if (abs(test_dci_set[cnt]) > 200e-3):
            # Perform additional dummy 60 measurement steps to allow for shunts in DUT and DMM warm-up for 1 A points
            for samples in range (0, 60):
                dmm.get_data()
                if (abs(dmm_val) > 1e10):
                    kei2400.write (":OUTP:STAT OFF")
                    print ("\033[41;93m -ERROR- Output is overflow, incorrect DCI connections? \033[39;49m\r")
                    b.write("\rERROR: Incorrect measurement value: %.7e\r" % dmm_val)
                    quit()
                print ("Warmup DCI %d : %.9E VDC\r" % (samples, dmm_val))

        dciarr = []
        val = 0
        for samples in range (0, samples_num):
            dmm.get_data()
            if (abs(dmm_val) > 1e10):
                kei2400.write (":OUTP:STAT OFF")
                for beepcnt in range(0,3):
                    kei2400.write (":SYST:BEEP:IMM 4000, 5")
                    kei2400.write (":SYST:BEEP:IMM 2000, 5")
                print ("\033[31;1m -ERROR- Output is overflow, incorrect DCI connections? \033[39;0m\r")
                b.write (" -ERROR- Output is overflow, incorrect DCI connections? \r")
                quit()
            else:
                print ("Measure DCI %d : %.9E ADC" % (samples, dmm_val))
                dciarr.append(dmm_val)
                val += dmm_val
        val = val / samples_num
        median_dci = np.median(dciarr[-5:])
        sdev_dci = np.std(dciarr[-5:])
        ppm = (((median_dci+1e-24) / (test_dci_set[cnt] + 1E-16)) - 1) * 1E6

        if (abs(ppm) < 10e4):
            sys.stdout.write("\033[33;1m Verification step %sA : %.9E ADC median [deviation %6.2f ppm] sdev = %.4f uA \033[39;0m \r\n" % (test_dci_str[cnt], median_dci, ppm, sdev_dci * 1e6))
            b.write("DCI Verification step %sA : %.9E ADC median, deviation %.3f ppm, sdev = %.4f uA \r" % (test_dci_str[cnt], median_dci, ppm, sdev_dci * 1e6))
        else:
            sys.stdout.write("\033[33;1m  Verification step %sA : %.9E ADC median zero, sdev = %.4f uA \033[39;0m \r\n" % (test_dci_str[cnt], median_dci, sdev_dci * 1e6))
            b.write("DCI Verification step %sA : %.9E ADC median zero, sdev = %.4f uA \r" % (test_dci_str[cnt], median_dci, sdev_dci * 1e6))
        cnt = cnt + 1
        if six.PY2:
            while len(dciarr) > 0: dciarr.pop()
        else:
            dciarr.clear()

    kei2400.write (":OUTP:STAT OFF")
    kei2400.write (":SYST:BEEP:IMM 1000, 1")
    kei2400.write (":SYST:BEEP:IMM 2000, 2")
    kei2400.write (":SYST:BEEP:IMM 5000, 5")

def dump_constants_kei2400():
    # This function will read and store calibration constants from the Keithley 2400 SMU
    with open(logfilen,'a') as b:
        kei2400.write (":CAL:PROT:DATE?")
        last_cal_date = kei2400.read()
        b.write("\r\nKeithley Model 2400 - last date calibrated %s" % last_cal_date)
        kei2400.write (":CAL:PROT:NDUE?")
        last_due_date = kei2400.read()
        b.write("\r\nKeithley Model 2400 - last due calibrated %s" % last_due_date)

        kei2400.write (":SENS:FUNC 'VOLT:DC'")
        kei2400.write (":SOUR:FUNC VOLT")
        kei2400.write (":OUTP:STAT OFF")

        b.write("==== K2400 Calibration constants data\r\n")

        for cnt in range (0, 4):
            kei2400.write (":SOUR:VOLT:RANGE %3.1f" % cal_dcv_rng[cnt*4])
            kei2400.write (":CAL:PROT:SENS:DATA?")
            kdata = kei2400.read()
            sys.stdout.write ("%s" % kdata)
            b.write("%3.1f DCV Range, SENS:DATA? = %s\r" % (cal_dcv_rng[cnt*4], kdata))
            kei2400.write (":CAL:PROT:SOUR:DATA?")
            kdata = kei2400.read()
            sys.stdout.write ("%s" % kdata)
            b.write("%3.1f DCV Range, SOUR:DATA? = %s\r" % (cal_dcv_rng[cnt*4], kdata))
            cnt = cnt + 1
        print ("\r\n")

        kei2400.write (":CAL:PROT:LOCK")
        kei2400.write (":SENS:FUNC 'CURR:DC'")
        kei2400.write (":SOUR:FUNC CURR")

        for cnt in range (0, 7):
            kei2400.write (":SOUR:CURR:RANGE %3.1e" % cal_dci_rng[cnt*4])
            kei2400.write (":CAL:PROT:SENS:DATA?")
            kdata = kei2400.read()
            sys.stdout.write ("%s" % kdata)
            b.write("%3.1e DCI Range, SENS:DATA? = %s\r" % (cal_dci_rng[cnt*4], kdata))
            kei2400.write (":CAL:PROT:SOUR:DATA?")
            kdata = kei2400.read()
            b.write("%3.1e DCI Range, SOUR:DATA? = %s\r" % (cal_dci_rng[cnt*4], kdata))
            sys.stdout.write ("%s" % kdata)
            cnt = cnt + 1
        print ("Dump data completed\r\n")

with open(logfilen,'a') as b:
#    kei2400 = Gpib.Gpib(0,24)               # Keithley Model 2400 GPIB address for use with linux-gpib
    b.write ("\r\n-i- Script settings : %d samples, %d DMM NPLC, %d ACAL run, skip_precal %d, skip_postcal %d" % (samples_num, dmm_nplc, run_acal, skip_pre_calibration, skip_post_calibration))
    timstr = time.strftime("%d/%m/%Y-%H:%M:%S;")
    b.write ("\r\n-i- Script started %s" % timstr)

    # Keithley 2400 vxi11 IP address and GPIB address
    if (hw_interface_type == "vxi"):
        try:
            kei2400 = vxi11.Instrument(gpib_vxi_ip, "gpib0,%d" % gpib_addr_k2400)
        except:
            print ('\033[31;1m -e- Cound not initialize GPIB-LAN bridge at %s for GPIB %d\033[39;0m' % (gpib_vxi_ip, gpib_addr_k2400))
    elif (hw_interface_type == "visa"):
        rm = pyvisa.ResourceManager()
        #rm.list_resources()   # Debug listing for all available interfaces
        kei2400 = rm.open_resource('GPIB::%d"::INSTR' % gpib_addr_k2400)
    elif (hw_interface_type == "linux-gpib"):
        kei2400 = Gpib.Gpib(0,gpib_addr_k2400,timeout = 60)

    kei2400.clear()

    # Adjustment points per Keithley service manual
    cal_dcv_set = [-0.2,   0, 0.2,   0, -2, 0, 2, 0, -20,  0, 20,  0, -200,   0, 200,   0]
    cal_dcv_rng = [ 0.2, 0.2, 0.2, 0.2,  2, 2, 2, 2,  20, 20, 20, 20,  200, 200, 200, 200]
    cal_dcv_str = ["-0.2 "," -Zero"," 0.2 "," +Zero","   -2 "," -Zero","    2 "," -Zero","  -20 "," +Zero","   20 "," -Zero"," -200 ","-Zero","  200 "," +Zero"]
    cal_dci_set = [-1E-6,    0, 1E-6,    0, -10E-6,     0, 10E-6,     0, -100E-6,      0, 100E-6,      0, -1E-3,    0, 1E-3,    0,  -10E-3,     0, 10E-3,     0, -100E-3,      0, 100E-3,      0, -1, 0, 1, 0]
    cal_dci_rng = [ 1E-6, 1E-6, 1E-6, 1E-6,  10E-6, 10E-6, 10E-6, 10E-6,  100E-6, 100E-6, 100E-6, 100E-6,  1E-3, 1E-3, 1E-3, 1E-3,   10E-3, 10E-3, 10E-3, 10E-3,  100E-3, 100E-3, 100E-3, 100E-3,  1, 1, 1, 1]
    cal_dci_str = ["-1 u","-Zero", " 1 u", "+Zero", " -10 u", "-Zero", " 10 u", "+Zero", "-100 u", "-Zero", " 100 u", "+Zero", "-1 m", "-Zero", " 1 m", "+Zero", " -10 m", "-Zero", " 10 m", "+Zero", "-100 m", "-Zero", " 100 m", "+Zero", "-1 ", "-Zero ", "-1 ", "+Zero "]
    # Calibration performance test points
    test_dcv_set = [0, -0.002, 0.002, -0.02, 0.02, -0.2, 0.2,   0, -1, 1, -2, 2, 0, -10, 10, -15, 15, -20, 20,  0, -100, 100,-200, 200,  0]
    test_dcv_rng = [0.1,  0.1,   0.1,   0.1,  0.1,  0.2, 0.2, 0.2,  1, 1,  2, 2, 2,  10, 10,  20, 20,  20, 20, 10,  100, 100, 200, 200,100]
    test_dci_set = [   0, -1E-9, 1E-9, -10E-9, 10E-9, -100E-9, 100E-9, -1E-6, 1E-6, -10E-6, 10E-6, -100E-6, 100E-6, -1E-3, 1E-3, -4E-3,  4E-3, -10E-3, 10E-3, -20E-3,  20E-3,  -50E-3,  50E-3, -100E-3, 100e-3, -1, 1, 0]
    test_dci_rng = [1E-6,  1E-6, 1E-6,   1E-6,  1E-6,    1E-6,   1E-6,  1E-6, 1E-6,  10E-6, 10E-6,  100E-6, 100E-6,  1E-3, 1E-3, 10E-3, 10E-3,  10E-3, 10E-3, 100E-3, 100E-3,  100E-3, 100E-3,  100e-3, 100e-3,  1, 1, 1]
    test_dci_str = ["Zero  ","  -1 n", "   1 n", " -10 n", "  10 n", "-100 n", " 100 n", "  -1 u", "   1 u", " -10 u", "  10 u", "-100 u", " 100 u", "  -1 m", "   1 m", "  -4 m", "   4 m", " -10 m", "  10 m", " -20 m", "  20 m", "-100 m", " 100 m", "-200 m", " 200 m", "-1 ", "1 ", "Zero  "]
    kei2400_idn = "KEITHLEY INSTRUMENTS INC."
    kei2400_mdl = "MODEL 2400"
    orig_val = []
    kei_error = 0
    plc = 60 #60 Hz
    cnt = 0
    samples = 0
    val = 3.423
    meas = 0.0
    temp = 36.6

    for cnt in range(30):
        orig_val.append(0)

    # Make sure we are talking to proper K2400 instrument first
    kei2400.write ("*IDN?")
    idn_str = kei2400.read()
    idmfg = idn_str.split(",")
    if (kei2400_idn == idmfg[0]):
        if (kei2400_mdl == idmfg[1]):
            sys.stdout.write("\033[0;32mKeithley Model 2400 - detected, S/N %s, Version: %s\033[0;39m\r\n" % (idmfg[2], idmfg[3]))
            b.write("\r\nKeithley Model 2400 - detected, S/N %s, Version: %s" % (idmfg[2], idmfg[3]))
        else:
            print ("\033[0;31mIncorrect Model! Check GPIB address. Calibration cancelled.\033[0;39m")
            quit()
    else:
        sys.stdout.write ("\033[0;31mNo Keithley instrument detected. Check GPIB address. Calibration cancelled.\033[0;39m")
        quit()


    dmm = dmm(gpib_addr_dmm,"3458A") # Reference DMM
    dmm.exec_idn()

    if (dmm_type == "3458a"):
        dmm.get_temp()
        print ("\033[0;33m\r\nHP3458A TEMP = %2.1f C\033[0;33m" % dmm_temp)
        b.write ("HP 3458A detected\r TEMP? = %2.1f C" % dmm_temp)
        dmm.write ("CAL? 72")
        dmm_cal72 = dmm.read()
        b.write("HP3458 - CAL72 value %s" % dmm_cal72)
        dmm.write ("CAL? 1,1")
        dmm_cal11 = dmm.read()
        b.write("HP3458 - CAL1,1 value %s" % dmm_cal11)
        dmm.write ("CAL? 2,1")
        dmm_cal21 = dmm.read()
        b.write("HP3458 - CAL2,1 value %s\r" % dmm_cal21)

    kei2400.write ("*RST")
    kei2400.write ("*CLS")
    kei2400.write ("*ESE 1;*SRE 32")
    kei2400.write (":SENS:FUNC:CONC OFF")
    kei2400.write (":OUTP:STAT OFF")
    kei2400.write (":CAL:PROT:LOCK")

    # K2400 data read OK. Detect HP 3458A DMM on GPIB 3

    kei2400.write (":SYST:BEEP:IMM 800, 5")
    kei2400.write (":SYST:BEEP:IMM 1400, 3")
    kei2400.write (":SYST:BEEP:IMM 2200, 2")
    kei2400.write (":SYST:BEEP:IMM 3000, 1")

    print ("Reading Pre-cal calibration data from Model 2400")
    dump_constants_kei2400()

    if (run_acal == True):
        dmm.exec_acal()         # Execute ACAL on HP 3458A for best accuracy. Takes 15 minutes to complete.

    kei2400.write ("*RST")
    kei2400.write (":SOUR:FUNC VOLT")
    kei2400.write (":SENS:CURR:PROT 0.01")
    kei2400.write (":SENS:CURR:RANG 0.01")
    kei2400.write (":SOUR:VOLT:PROT:LEV MAX")
    kei2400.write (":SYST:RSEN OFF")

    if (skip_pre_calibration != True):
        sys.stdout.write("\r\n\r\n \033[34;1m ==== AS RECEIVED Performance calibration ==== \033[39;0m \r\n")
        perftest()                                                  # Run pre-adjustment calibration 

    large_msg("dcv",1)
    kei2400.write (":SYST:BEEP:IMM 1000, 5")
    print ("\r\n -i- If adjustment is executed original calibration constants in the SMU will be altered!")
    try:
        if six.PY2:
            adj_ready = raw_input("Type YES in caps to continue, or enter anything else to skip adjustment... :")
        else:
            adj_ready = input("Type YES in caps to continue, or enter anything else to skip adjustment... :")
    except SyntaxError:
        adj_ready = 0
        pass

    if (adj_ready == 'YES'):

        if (dmm_type == "3458a"):
            dmm.get_temp()
            print ("\r\n\033[0;33mHP3458A TEMP = %2.1f C\033[0;33m" % dmm_temp)
        kei2400.write ("*RST")
        kei2400.write ("*CLS")
        kei2400.write ("*ESE 1;*SRE 32")
        kei2400.write (":CAL:PROT:CODE 'KI002400'")
        kei2400.write (":OUTP:STAT ON")
        sys.stdout.write("Starting DCV adjustment...\r\n")

        for cnt in range (0, len(cal_dcv_set)):
            sys.stdout.write("\r\n Calibration adjustment step %d \r\n" % cnt)
            dmm.set_dcv_range(cal_dcv_rng[cnt])
            time.sleep(3) # 3 Second soak time
            kei2400.write (":SOUR:VOLT:RANGE %3.1f" % cal_dcv_rng[cnt])
            kei2400.write (":SOUR:VOLT %3.1f" % cal_dcv_set[cnt])
            b.write (":SOUR:VOLT:RANGE %3.1f\r\n" % cal_dcv_rng[cnt])
            b.write (":SOUR:VOLT %3.1f\r\n" % cal_dcv_set[cnt])

            if (abs(cal_dcv_set[cnt]) > 100):
                # Perform additional dummy 60 measurement steps to allow for dividers in DUT and DMM warm-up for >100 V points
                for samples in range (0, 60):
                    dmm.get_data()
                    if (abs(dmm_val) > 1e10):
                        kei2400.write (":OUTP:STAT OFF")
                        print ("\033[41;93m -ERROR- Output is overflow, incorrect DCV connections? \033[39;49m\r")
                        b.write("ERROR: Incorrect measurement value: %.7e\r" % dmm_val)
                        quit()
                    print ("Warmup DCV Adjustment %.7e : %.7f VDC\r" % (samples, dmm_val))
            val = 0
            dcvarr = []
            time.sleep(1)
            for samples in range (0, samples_num):
                dmm.get_data()
                if (abs(dmm_val) > 1e10):
                    kei2400.write (":OUTP:STAT OFF")
                    print ("\033[41;93m -ERROR- Output is overflow, incorrect DCV connections? \033[39;49m\r")
                    b.write("ERROR: Incorrect measurement value: %.7e\r" % dmm_val)
                    quit()
                print ("\rMeasured DCV %.7e : %.7f VDC" % (samples, dmm_val))
                dcvarr.append(dmm_val)
                val += dmm_val
            val = val / samples_num
            orig_val[cnt] = val
            refval = np.median(dcvarr[-5:])
            sys.stdout.write("\r\nDCV Adjustment step %sV : %.9E VDC\r\n" % (cal_dcv_str[cnt], refval))

            kei2400.write (":CAL:PROT:SOUR %3.6f;*OPC" % refval)
            time.sleep(3)
            b.write (":CAL:PROT:SOUR %3.5f;*OPC\r\n" % refval)
            kei2400.write (":SYST:ERR?")
            kei_error = kei2400.read().split(",")
            if (int(kei_error[0]) != 0):
                print ("\r\n\033[1;31mKeithley 2400 error : %d\033[1;39m" % int(kei_error[0]), kei_error, "\r\n")
                b.write("K2400 reported adjustment error %d\r" % int(kei_error[0]))
                quit()
            #kei2400.write ("*STB?")
            #kei2400.write ("*ESR?")

            kei2400.write (":CAL:PROT:SENS %3.6e;*OPC" % refval)
            time.sleep(3)
            b.write (":CAL:PROT:SENS %3.5f;*OPC\r\n" % refval)
            kei2400.write (":SYST:ERR?")
            kei_error = kei2400.read().split(",")
            if (int(kei_error[0]) != 0):
                print ("\033[1;31mKeithley 2400 error : %d\033[1;39m" % int(kei_error[0]))
                b.write("K2400 reported adjustment error %d\r" % int(kei_error[0]))
                quit()
            #kei2400.write ("*STB?")
            #kei2400.write ("*ESR?")
            if six.PY2:
                while len(dcvarr) > 0: dcvarr.pop()
            else:
                dcvarr.clear()

            if (cnt % 2 == 0):
                ppms = ((refval / (cal_dcv_set[cnt] + 1E-16)) - 1) * 1E6
                sys.stdout.write("\r\n\033[1;35m Delta: %.3f ppm\033[1;39m\r" % ppms)
                b.write("DCV Adjustment step %sV , measured : %.9E VDC, deviation %.3f ppm\r" % (cal_dcv_str[cnt], refval, ppms))
            else:
                sys.stdout.write("\r\n")
            cnt = cnt + 1

        large_msg("dci",1)
        kei2400.write (":SYST:BEEP:IMM 1000, 5")
        try:
            input("Press Enter key to continue...")
        except SyntaxError:
            pass
        sys.stdout.write("\rStarting DCI adjustment...\r\n\r\n")
        dmm.switch_dci()

        kei2400.write (":SOUR:FUNC CURR")
        kei2400.write (":SENS:VOLT:PROT 20")
        kei2400.write (":SENS:VOLT:RANG 20")
        kei2400.write (":OUTP:STAT ON")

        for cnt in range (0, len(cal_dci_set)):
            kei2400.write (":SOUR:CURR:RANGE %3.6e" % cal_dci_rng[cnt])
            kei2400.write (":SOUR:CURR %3.6e" % cal_dci_set[cnt])
            print ("Setting DMM DCI range %3.3e A" % cal_dci_rng[cnt])
            dmm.set_dci_range(cal_dci_rng[cnt])
            time.sleep(3) # 3 Second soak time
            b.write (":SOUR:CURR:RANGE %3.6e\r\n" % cal_dci_rng[cnt])
            b.write (":SOUR:CURR %3.6e\r\n" % cal_dci_set[cnt])
            time.sleep(3) # Soak 3 seconds
            val = 0
            dciarr = []

            if (abs(cal_dci_set[cnt]) > 200e-3):
                # Perform additional dummy 60 measurement steps to allow for shunts in DUT and DMM warm-up for 1 A points
                for samples in range (0, 60):
                    dmm.get_data()
                    if (abs(dmm_val) > 1e10):
                        kei2400.write (":OUTP:STAT OFF")
                        print ("\033[41;93m -ERROR- Output is overflow, incorrect DCI connections? \033[39;49m\r")
                        b.write("ERROR: Incorrect measurement value: %.7e\r" % dmm_val)
                        quit()
                    print ("Warmup cal DCI %d : %.9E VDC\r" % (samples, dmm_val))

            dciarr = []
            for samples in range (0, samples_num):
                dmm.get_data()
                if (abs(dmm_val) > 1e10):
                    kei2400.write (":OUTP:STAT OFF")
                    print ("\033[41;93m -ERROR- Output is overflow, incorrect DCI connections? \033[39;49m\r")
                    b.write("ERROR: Incorrect measurement value: %.7e\r" % dmm_val)
                    quit()
                print ("Measured DCI Adjustment %d : %.7e ADC" % (samples, dmm_val))
                dciarr.append(dmm_val)
                val += dmm_val
            val = val / samples_num

            refval = np.median(dciarr[-5:])
            sys.stdout.write("\r\nDCI Adjustment step %sA : %.6E ADC\r\n" % (cal_dci_str[cnt], refval))

            kei2400.write (":CAL:PROT:SOUR %3.6e;*OPC" % refval)
            time.sleep(1)
            b.write (":CAL:PROT:SOUR %3.6e;*OPC\r\n" % refval)
            kei2400.write (":SYST:ERR?")
            kei_error = kei2400.read().split(",")
            if (int(kei_error[0]) != 0):
                print ("\r\n\033[1;31mKeithley 2400 error : %d\033[1;39m" % int(kei_error[0]))
                b.write("K2400 reported adjustment error %d\r" % int(kei_error[0]))
                quit()
            #kei2400.write ("*STB?")
            #kei2400.write ("*ESR?")

            kei2400.write (":CAL:PROT:SENS %3.6e;*OPC" % refval)
            b.write (":CAL:PROT:SENS %3.6e;*OPC\r\n" % refval)
            time.sleep(1)
            kei2400.write (":SYST:ERR?")
            kei_error = kei2400.read().split(",")
            if (int(kei_error[0]) != 0):
                print ("\r\n\033[1;31mKeithley 2400 error : %d\033[1;39m" % int(kei_error[0]))
                b.write("K2400 reported adjustment error %d\r" % int(kei_error[0]))
                quit()
            #kei2400.write ("*STB?")
            #kei2400.write ("*ESR?")
            if six.PY2:
                while len(dciarr) > 0: dciarr.pop()
            else:
                dciarr.clear()

            if (cnt % 2 == 0):
                ppms = ((refval / (cal_dci_set[cnt] + 1E-16)) - 1) * 1E6
                sys.stdout.write("\033[1;36m Delta: %.3f ppm\033[1;39m\r\n" % ppms)
                b.write("DCI Adjustment step %sA , measured : %.6E ADC, deviation %.1f ppm\r" % (cal_dci_str[cnt], refval, ppms))
            else:
                sys.stdout.write("\r\n")
            cnt = cnt + 1

        kei2400.write (":CAL:PROT:DATE %s" % cal_date_str)
        kei2400.write (":CAL:PROT:NDUE %s" % cal_ndue_str)
        kei2400.write (":CAL:PROT:SAVE")
        kei2400.write (":CAL:PROT:LOCK")
        kei2400.write (":OUTP:STAT OFF")
        b.write (":CAL:PROT:DATE %s\r\n" % cal_date_str)
        b.write (":CAL:PROT:NDUE %s\r\n" % cal_ndue_str)
        b.write (":CAL:PROT:SAVE\r\n")
        b.write (":CAL:PROT:LOCK\r\n")
        b.write (":OUTP:STAT OFF\r\n")

        print("\r\033[0;30;42mAdjustment complete! www.xDevs.com \033[0;39;49m")
        kei2400.write (":SYST:BEEP:IMM 100, 1")
        kei2400.write (":SYST:BEEP:IMM 300, 1")
        kei2400.write (":SYST:BEEP:IMM 500, 1")
        kei2400.write (":SYST:BEEP:IMM 900, 1")
        kei2400.write (":SYST:BEEP:IMM 1200, 1")
        kei2400.write (":SYST:BEEP:IMM 1500, 1")
        kei2400.write (":SYST:BEEP:IMM 2000, 1")

        if (skip_post_calibration != True):
            sys.stdout.write("\r\n\r\n \033[34;1m ==== AS RETURNED Performance calibration ==== \033[39;0m \r\n")
            perftest()                                      # Run post-adjustment performance test
    else:
        sys.stdout.write("\r\n\r\nAdjustment and post-adjustment calibration is skipped...\r\n")

    # Post-cal constants
    print ("Reading Post-cal calibration data from Model 2400")
    dump_constants_kei2400()

    kei2400.write (":CAL:PROT:LOCK")
    b.write ("\r\nCalibration complete!\r\n")
    timstr = time.strftime("%d/%m/%Y-%H:%M:%S;")
    b.write ("-i- Script sucessfully finished %s" % timstr)
    b.close()

    kei2400.write (":SYST:BEEP:IMM 1000, 1")
    kei2400.write (":SYST:BEEP:IMM 4000, 2")
    kei2400.write (":SYST:BEEP:IMM 8000, 2")
    kei2400.write (":SYST:BEEP:IMM 2000, 2")
    kei2400.write (":SYST:BEEP:IMM 5000, 2")
    kei2400.write (":SYST:BEEP:IMM 8000, 2")
    kei2400.write (":SYST:BEEP:IMM 2000, 5")
    kei2400.write (":SYST:BEEP:IMM 7000, 5")
    kei2400.write (":SYST:BEEP:IMM 9000, 5")

    print("\r\nAll done!")