This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

TICSPRO-SW: TICSProTCPClient Connect Default Port Number w/ LMX2615

Part Number: TICSPRO-SW

Good morning,

I'm using the provided TICSPro_TCP.py class and attributes to write a python script that remotely controls the TICSPro GUI to automate some testing for our LMX2615 eval board.

Is the 9001 port number provided in the __init__ attribute (line 81) supposed to be the default / intended port for connecting to the GUI?  9001 has not worked for me, I've been able to use a different port to establish connection after listening to all ports when starting the GUI.  This port number has occasionally changed and I've had to re-listen to the ports and find the new one.

Thank you for your help,

Nolan

"""
 * Copyright (C) {2021-2022} Texas Instruments Incorporated - https://www.ti.com/
 * ALL RIGHTS RESERVED
"""

import socket
from socket import AF_INET, SOCK_STREAM, SHUT_RDWR
import os
import subprocess
from subprocess import CREATE_NEW_CONSOLE, CREATE_NO_WINDOW
import time
import re
import configparser

class TICSProTCPClient:
    """
    A communication object which exposes the APIs available through the 
    TCP server. The current revision of TICS Pro (1.7.4) supported by this 
    communication object represents a be ta version of the server - some 
    features may not be supported, and there may be bugs in others.

    Communication with the TCP server takes the form of commands and 
    responses. Commands are generated by the client, while responses are 
    generated by TICS Pro.

    Commands use the string "<SOC>" to identify the start of a command, and 
    "<EOC>" to identify the end of a command. Each command starts with a 
    command string, and may potentially be followed by one to four parameters 
    separated by ascii character 0x19 (). A few examples:

    Write all registers: '<SOC>writeallregisters<EOC>'
    Set POWERDOWN = 1: '<SOC>setfieldvaluePOWERDOWN1<EOC>'
    Readback POWERDOWN: '<SOC>readparameterPOWERDOWN<EOC>'

    Responses use the string "<SOR>" to identify the start of a response, and 
    "<EOR>" to identify the end of a response. Each response consists of the 
    command name which triggered the response, the status of the response 
    (for now True on success and False on failure, loosely), and occasionally 
    a return value from the triggering command, separated by ascii character 
    0x19 (). A few examples, representing responses to the examples above:

    Write all registers: '<SOR>writeallregistersTrue<EOR>'
    Set POWERDOWN = 1: '<SOR>setfieldvalueTrue<EOR>'
    Readback POWERDOWN: '<SOR>readparameterTrue1<EOR>'

    The server can be initialized in TICS Pro using the TICS Pro.ini file,
    located in the configuration directory
    (default: C:\\ProgramData\\Texas Instruments\\TICS Pro\\Configurations)
    by controlling the value of the keys in the API section.
        TCPCLIENT: If 'true', start with the TCP server enabled. If false, 
                   start with the TCP server disabled and ignore other keys.
        TCPLOCAL:  If 'true', use localhost as the address. If false, use 
                   the currently active network adapter as the address. 
                   Strongly recommended not to set false while TCP server is 
                   enabled, since this behavior is still being defined.
        TCPPORT:   A value between 0 and 65535, representing the port on 
                   which the TCP server will listen.
        DISABLECLOSEBUTTON: Set to 'false' for now. This behavior is not 
                   fully defined.

    The server can also be initialized from command line with the port number 
    as an argument to the executable. The server will attempt to start 
    listening on localhost:portNum:

    > "C:\\Program Files (x86)\\Texas Instruments\\TICS Pro\\TICS Pro.exe" 11000

    The example invocation would attempt to start TICS Pro with the TCP server 
    enabled and listening on port 11000.

    Typical usage of the TICSProTCPClient object looks like:
        tp = TICSProTCPClient(port=11000)
        tp.SetFieldValue("POWERDOWN",1)
    """

    _SOC = "<SOC>" # Start of command  (python   -> TICS Pro)
    _EOC = "<EOC>" # End of command    (python   -> TICS Pro)
    _SOR = "<SOR>" # Start of response (TICS Pro ->   python)
    _EOR = "<EOR>" # End of response   (TICS Pro ->   python)
    _SEP = chr(25) # Separates arguments in the command/response

    def __init__(self, ip_address="127.0.0.1", port=9001, check_alive=False, connection_retry_count=30):
        """
        Create a connection to a TICS Pro TCP server. Assumes TICS Pro has 
        already been started. Each connection attempt has a timeout period of 
        three seconds by default, but some device profiles may take more than 
        3s to load; multiple attempts may be necessary to connect to TICS Pro.

        ip_address:  String representing IPv4 address
        port:        Integer port number
        check_alive: Raises ConnectionError the first time a connection
                     attempt fails, regardless of retry count
        connection_retry_count: If check_alive==False, number of times to 
                                retry making the connection to TICS Pro. If 
                                check_alive==True, this argument is ignored.
        """
        self.connected = False
        self.connect(ip_address, port, check_alive, connection_retry_count=connection_retry_count)
        
    def __del__(self):
        self.disconnect()

    def _prepare_command(self, *args):
        """
        Formats commands for transmission. Adds separators, <SOC>/<EOC> 
        headers and footers, and formats as UTF-8.
        """
        command = self._SOC + self._SEP.join([str(x) for x in args]) + self._EOC
        return command.encode('utf-8')

    def _prepare_response(self, buf):
        """
        Decodes response. Converts bytes to UTF-8, removes <SOR>/<EOR>, 
        splits by separators, and returns response as a list.
        """
        response = buf.decode('utf-8').lstrip(self._SOR).rstrip(self._EOR)
        return response.split(self._SEP)

    def _SEND(self, *args):
        """
        Wraps command + args in required protocol header/footer/separator,
        sends it to TICS Pro, and returns the response as a string.
        """
        if not self.connected:
            raise ConnectionError("Client is not connected to TICS Pro")

        # Send the data. In theory over localhost this should never split up 
        # any API commands we could realistically send, but it's a good idea 
        # to make sure we sent everything under normal circumstances.
        raw_cmd = self._prepare_command(*args)
        bytes_sent = 0
        bytes_to_be_sent = len(raw_cmd)
        while bytes_sent < bytes_to_be_sent:
            bytes_sent_this_time = self._sock.send(raw_cmd[bytes_sent:])
            if bytes_sent_this_time == 0:
                raise ConnectionError("Connection lost during send")
            bytes_sent += bytes_sent_this_time

        # Receive the response. We don't know the length, but in practice no 
        # response should ever take more than 4kB. This may be updated in 
        # future versions of TICS Pro.
        raw_resp = self._sock.recv(4096)
        response = self._prepare_response(raw_resp)
        return response

    def connect(self, ip_address, port, check_alive=False, connection_retry_count=30):
        """
        Create a connection to a TICS Pro TCP server. Assumes TICS Pro has 
        already been started. Each connection attempt has a timeout period of 
        three seconds by default, but some device profiles may take more than 
        3s to load; multiple attempts may be necessary to connect to TICS Pro.

        ip_address:  String representing IPv4 address
        port:        Integer port number
        check_alive: Raises ConnectionError the first time a connection
                     attempt fails, regardless of retry count
        connection_retry_count: If check_alive==False, number of times to 
                                retry making the connection to TICS Pro. If 
                                check_alive==True, this argument is ignored.
        """
        if self.connected:
            raise ConnectionError("Already connected to TICSPro {0}:{1}".format(self.address))
        self.address = (ip_address, port)
        self._sock = socket.socket(AF_INET, SOCK_STREAM, 0)
        self._sock.settimeout(10) # default communication timeout - change as needed
        connection_count = 0
        while (not self.connected) and (connection_count < connection_retry_count):
            try:
                self._sock.connect(self.address)
                self.connected = True
            except: # could possibly be restricted to ConnectionRefusedError
                if check_alive: raise ConnectionError("Server at {0}:{1} is not running".format(self.address))
                connection_count += 1
                print("Could not connect on try {connection_count}, trying again until count reaches >= {connection_retry_count}".format(**locals()))
                time.sleep(3)
                
    def disconnect(self):
        """Terminates TICSPro connection. No effect if already disconnected."""
        if self.connected:
            self._sock.shutdown(SHUT_RDWR)
            self._sock.close()
            self.connected = False

    # =====================
    # === API FUNCTIONS ===
    # =====================

    def BurstDeleteAll(self):
        """
        Deletes everything in the burst mode text box
        """
        n = "burstdeleteall"
        r = self._SEND(n)

    def BurstRun(self, Run):
        """
        Equivalent to pressing run button on burst mode.
        Run (bool): Set to True.
        """
        n = "burstrun"
        r = self._SEND(n, Run)

    def BurstStop(self):
        """
        Stops Burst mode
        """
        n = "burststop"
        r = self._SEND(n)

    def CheckModeText(self, ModeName):
        """
        Checks if the mode name is present in the default configuration menu.
        Returns True if it exists, or False if it does not.
        ModeName (str): exact name of mode in default configuration menu
        """
        n = "checkmodetext"
        r = self._SEND(n, ModeName)
        return True if r[1].lower() == "true" else False

    def CloseTICSPro(self):
        """
        Closes TICSPro. Subsequent communications will fail.
        """
        n = "closeticspro"
        r = self._SEND(n)

    def ConnectToUSB2ANY(self, SerialNumber):
        """
        Tries connecting to a USB2ANY with the listed serial number.
        Returns True on success or False on failure.
        SerialNumber (str): USB2ANY serial number.
        """
        n = "connect"
        r = self._SEND(n, SerialNumber)
        if r[1].lower() == "true":
            return True
        else:
            return False

    def GetAllUSB2ANY(self):
        """
        Returns a list of all connected USB2ANY serial numbers.
        """
        n = "getallusb2any"
        r = self._SEND(n)
        if r[1].lower() == "true" and len(r) > 2:
            return r[2:]
        else:
            return []

    def GetDevice(self):
        """
        Retrieves the name and type of the currently loaded device.
        Returns deviceName, deviceType on success, or False, False
        on failure.
        """
        n = "getdevice"
        r = self._SEND(n)
        if r[1].lower() == "true":
            return r[2], r[3]
        else:
            return False, False

    def GetFieldValue(self, ControlName):
        """
        Returns integer representation of a control's value. For register-
        backed controls, this is the literal integer value of the field in 
        the register map. Do not use with FlexControls (which are not 
        register-backed).
        Updates controls with linked bits.
        ControlName (str): name of indexed control to read.
        """
        n = "getfieldvalue"
        r = self._SEND(n, ControlName)
        if r[2] == "":
            # Control does not exist
            return -1
        else:
            return int(r[2])

    def GetIndex(self, ControlName):
        """
        Returns integer index value of the GUI's currently selected item in 
        the items collection. For register-backed controls, this is usually 
        the literal integer value of the field in the register map, but can 
        be the GUI index with sparse lists (where some values are missing). 
        ControlName (str): name of indexed control to read.
        """
        n = "getindex"
        r = self._SEND(n, ControlName)
        return int(r[2])

    def GetPin(self, PinName):
        """
        Returns 1 if the pin is set, 0 if it is not.
        Returns -1 if operation fails.
        PinName (str): name of the pin.
        """
        n = "getpin"
        r = self._SEND(n, PinName)
        if r[1].lower() == "true":
            return int(r[2])
        else:
            return -1

    def GetRegister(self, RegisterName):
        """
        <todo>
        """
        pass

    def GetRegisterbyIndex(self, Index):
        """
        <todo>
        """
        pass

    def GetText(self, ControlName):
        """
        Returns value in text field, or displayed text value of a combobox or 
        listbox.
        ControlName (str): name of text field to read.
        """
        n = "gettext"
        r = self._SEND(n, ControlName)
        return r[2]

    def Help(self):
        """
        Prints a complete list of all the help options reported by the client.
        """
        n = "help"
        r = self._SEND(n)
        print("|".join(r[2:]))

    def Initialize(self, AppDirectory):
        """
        <todo>
        """
        pass

    def PressButton(self, ControlName):
        """
        Triggers a press event for a button. Returns False if press is not 
        valid, and True if press is valid.
        ControlName (str): name of button to press.
        """
        n = "pressbutton"
        r = self._SEND(n, ControlName)
        return True if r[1].lower() == "true" else False

    def PressSpinButton(self, ControlName, ByAmount):
        """
        Triggers a press event for a spin button. Returns False if press is 
        not valid, and True if press is valid.
        ControlName (str): name of button to press.
        ByAmount (int): If positive, increments by the specified amount. 
                        If negative, decrements by the specified amount.
        """
        n = "pressspinbutton"
        r = self._SEND(n, ControlName, ByAmount)
        return True if r[1].lower() == "true" else False

    def ReadAllRegisters(self):
        """
        Updates TICS Pro with the current value of all registers.
        """
        n = "readallregisters"
        r = self._SEND(n)

    def ReadDirect_I2C(self, SlaveAddress, AddressBytes, DataBytes):
        """
        <todo>
        """
        pass

    def ReadDirect_SPI(self, AddressBytes, DataBytes):
        """
        <todo>
        """
        pass

    def ReadParameterAndUpdateUI(self, Parameter):
        """
        Checks the register tied to a TICS Pro parameter, and reports the 
        integer parameter value or -1 on failure. UI will be updated by 
        readback event.
        Parameter (str): parameter name in TICSPro.
        """
        n = "readparameter"
        r = self._SEND(n, Parameter)
        if r[1].lower() == "true":
            return int(r[2])
        else:
            return -1

    def ReadRegister(self, RegisterName):
        """
        Attempts to read a register. Automation will hang if device is not 
        configured for register readback and CheckReadback event occurs.
        Returns True on success or False on failure.
        RegisterName (str): Name of register e.g. "R0"
        """
        n = "readregister"
        r = self._SEND(n, RegisterName)
        if r[1].lower() == "true":
            return int(r[2])
        return -1

    def ReadRegisterDataOnly(self, RegisterName):
        """
        <todo>
        """
        pass

    def ReadRegisterbyIndex(self, Index):
        """
        <todo>
        """
        pass

    def RestoreSetup(self, FileName, FilePath):
        """
        Restores a .tcs file <FileName>.tcs from directory FilePath.
        FileName (str): a valid Windows filename.
        FilePath (str): a valid Windows path.

        Raises a FileNotFoundError if the .tcs file doesn't exist.
        """
        n = "restoresetup"
        if not FileName.endswith(".tcs"):
            FileName += ".tcs"
        f = os.path.join(FilePath, FileName)
        if os.path.exists(f):
            r = self._SEND(n, f)
        else:
            raise FileNotFoundError(2, os.strerror(2), f)

    def SaveSetup(self, FileName, FilePath):
        """
        Saves a .tcs file as <FileName>.tcs in the directory FilePath.
        FileName (str): a valid Windows filename. 
        FilePath (str): a valid Windows path.
        """
        n = "savesetup"
        if not FileName.endswith(".tcs"):
            FileName += ".tcs"
        f = os.path.join(FilePath, FileName)
        r = self._SEND(n, f)

    def SelectDevice(self, DeviceName):
        """
        Loads built-in device into TICS Pro.
        DeviceName (str): Name of the device to be loaded.
        """
        n = "selectdevice"
        r = self._SEND(n, DeviceName)

    def SelectInterface(self, Interface, Protocol, SerialNumber):
        """
        Selects the communication interface.
        Interface (str): Can be DemoMode, USB2ANY, TIHera, or FTDI.
        Protocol (str): Can be SPI, SPI_CLKLOW, UWIRE, I2C.
        SerialNumber (str): The unique ID of the USB2ANY, FTDI, etc.
        """
        n = "selectinterface"
        r = self._SEND(n, Interface, Protocol, SerialNumber)

    def SelectPage(self, PageName):
        """
        Switches TICS Pro's selected page to PageName.
        PageName (str): Name of the page to select.
        """
        n = "selectpage"
        r = self._SEND(n, PageName)

    def SelectUserDevice(self, DeviceName):
        """
        Loads user device into TICS Pro.
        DeviceName (str): Name of the device to be loaded.
        """
        n = "selectuserdevice"
        r = self._SEND(n, DeviceName)

    def SetAddress_I2C(self, SlaveAddress):
        """
        Sets the I2C address to SlaveAddress.
        SlaveAddress (int): 0-10 bit integer address.
        """
        n = "setaddress_i2c"
        r = self._SEND(n, SlaveAddress)

    def SetFieldValue(self, ControlName, Value):
        """
        Sets the integer representation of a control's value. For register-
        backed controls, this is the literal integer value of the field in 
        the register map. Do not use with FlexControls (which are not 
        register-backed). 
        ControlName (str): name of control to write. 
        Value (int): integer value of control to write.
        """
        n = "setfieldvalue"
        r = self._SEND(n, ControlName, Value)

    def SetIndex(self, ControlName, Value):
        """
        Sets the integer index value of the GUI's currently selected item in 
        the items collection. For register-backed controls, this is usually 
        the literal integer value of the field in the register map, but can 
        be the GUI index with sparse lists (where some values are missing).
        ControlName (str): name of control to write.
        Value (int): index of control to write.
        """
        n = "setindex"
        r = self._SEND(n, ControlName, str(Value))

    def SetMode(self, Index):
        """
        Loads the mode at the index in the default configuration menu.
        Index (int): Index of mode.
        """
        n = "setmode"
        r = self._SEND(n, str(Index))

    def SetModeText(self, ModeName):
        """
        Loads the mode with specified name in the default configuratin menu.
        ModeName (str): exact name of mode in default configuration menu.
        """
        n = "setmodetext"
        r = self._SEND(n, ModeName)

    def SetPin(self, PinName, Value):
        """
        Sets/Clears the pin. Returns -1 if operation fails.
        PinName (str): name of the pin.
        Value (bool): Pin value; True=set, False=clear
        """
        n = "setpin"
        r = self._SEND(n, PinName, Value)

    def SetText(self, ControlName, Value):
        """
        Sets a text field to Value, or sets a combobox or listbox to the 
        index with text matching Value.
        ControlName (str): name of control to write.
        Value (str): value to write or match.
        """
        n = "settext"
        r = self._SEND(n, ControlName, Value)

    def WriteAddressData(self, Address, Value):
        """
        Writes the input value to the specified register address.
        Address (int): register address to send the write
        Value (int): Value to write to the register at Address
        """
        n = "writeaddressdata"
        r = self._SEND(n, Address, Value)

    def WriteAllRegisters(self):
        """
        Writes the current value of all registers to the device.
        """
        n = "writeallregisters"
        r = self._SEND(n)

    def WriteDirect_I2C(self, SlaveAddress, AddressBytes, DataBytes):
        """
        <todo>
        """
        pass

    def WriteDirect_SPI(self, AddressBytes, DataBytes):
        """
        <todo>
        """
        pass

    def WriteRawData(self, RawRegData):
        """
        Parses the input value into address and data, and writes the data of 
        the register to the corresponding address of the device. Parser uses 
        the position of the address bits on the register map to determine 
        address and data positions and lengths.
        RawRegData (int): raw value of the register, including
        address and data.
        """
        n = "writerawdata"
        r = self._SEND(n, RawRegData)

    def WriteRegister(self, RegisterName):
        """
        Writes the current value of the register with the specified name. 
        This does not write to register 64 if you specify R64, but if 
        "PLL2_PRE" is a field in R64 and you specify "PLL2_PRE", this will 
        write the current value of R64.
        """
        n = "writeregister"
        r = self._SEND(n, RegisterName)

    def WriteRegisterbyIndex(self, Index):
        """
        Writes the current value of the register located at the index in the 
        register map. For example, when writing to register R64, Index=64.
        Index (int): index of register to write in register map
        """
        n = "writeregisterbyindex"
        r = self._SEND(n, Index)

    
def GetTICSProInstances():
    '''
    Returns a dictionary with keys equal to the string value of all PIDs for 
    active TICS Pro instances, and values equal to either "" for instances 
    without an active TCP server, or the local address as a string for 
    instances with an active TCP server. An example return value might be: 
        {'21804':'','20556':'127.0.0.1:11000'}
    '21804' and '20556' represent PIDs of TICS Pro instances. '21804' doesn't 
    have a TCP server running, while '20556' has a TCP server running 
    on port 11000.

    First, queries TASKLIST to get:
    - All process names and PIDs...
    - (/FO CSV) ...in CSV format
    
    From this table, only entries where the process is named "TICS Pro.exe" 
    are retained, and their PIDs are recorded.

    Next, queries NETSTAT -AON -P TCP to get:
    - (-A) All active TCP/UDP connections
    - (-O) All PIDs associated with active TCP/UDP connections
    - (-N) All entries in numerical format (usually skips DNS resolution, 
           which can save a lot of time)
    - (-P TCP) Filtered to only TCP connections
    
    From this table, scan the PIDs for entries matching known TICS Pro.exe 
    PIDs, and save any associated TCP connections.

    This function may not succeed if TASKLIST or NETSTAT permissions are 
    restricted for the user attempting to run the function.
    '''
    TASKLIST_QUERY = subprocess.check_output(('TASKLIST','/FO','CSV'), 
                       creationflags=CREATE_NO_WINDOW)
    process_list = TASKLIST_QUERY.decode().replace('"','').split('\r\n')
    TICS_Pro_set = {}
    for row in process_list:
        if row.startswith("TICS Pro"):
            TICS_Pro_set[row.split(',')[1]] = None # column 1 is PID
    
    if TICS_Pro_set:
        NETSTAT_QUERY = subprocess.check_output(('NETSTAT','-AON'), 
                            creationflags=CREATE_NO_WINDOW)
        # Split up the NETSTAT response. Two or more whitespace characters 
        # separate a table entry on every row, always
        active_connections = [x.lstrip() 
                              for x in NETSTAT_QUERY.decode().split('\r\n') 
                              if x]
        TCP_server_list = [re.split(r'\s\s+', x)
                              for x in active_connections[2:] 
                              if x]

        for row in TCP_server_list:
            if row[-1] in TICS_Pro_set: # final entry is PID
                TICS_Pro_set[row[-1]] = row[1] # column 1 is local address
    
    return TICS_Pro_set

def GetTICSProActivePorts(TICS_Pro_set=None):
    """
    Runs GetTICSProInstances, then returns a set of all ports (as integers)
    being used by TICS Pro TCP servers.

    Specify TICS_Pro_set as the output of GetTICSProInstances to skip a 
    function call.
    """
    if TICS_Pro_set is None:
        TICS_Pro_set = GetTICSProInstances()
    active_ports = []
    for v in TICS_Pro_set.values():
        if v:
            i = v.rindex(":")
            active_ports.append(int(v[i+1:]))
    return set(active_ports)

def update_ticspro_ini(enable=False,
                       port=11000,
                       root=r'C:\ProgramData\Texas Instruments\TICS Pro'):
    """
    Configures the startup options for the TICS Pro TCP server. Options are 
    stored in a static INI file located in the Configurations directory of 
    the ProgramData, by default; other locations can be specified for the 
    configuration directory location, but are usually not needed.

    Since options are stored in an INI file, they must be updated between 
    starting new instances of TICS Pro. In cases where multiple instances of 
    TICS Pro are running simultaneously, the port in the INI file must be 
    changed to prevent TICS Pro from crashing on startup. This will be fixed 
    in a subsequent release.

    enable (bool): Enables or disables TCP server on TICS Pro startup.
    port (int):    Sets the port number on which to start the TCP server. 
                   This argument is ignored if enable = False.
    root (str):    Path to Configurations directory. Default argument is the
                   default installation path.
    """
    cp = configparser.ConfigParser()
    cp.optionxform = str
    tp_path = os.path.join(root, "Configurations", "TICS Pro.ini")
    cp.read(tp_path, encoding="utf16")
    
    API_section_present = cp.has_section("API")
    change_port = enable or not API_section_present
    if not API_section_present:
        cp.add_section("API")
    cp.set("API", "TCPCLIENT", str(enable).lower())
    cp.set("API", "TCPLOCAL", "true") # Recommend keeping local at all times
    if change_port:
        cp.set("API", "TCPPORT", str(port))
    cp.set("API", "DISABLECLOSEBUTTON", "false")

    with open(tp_path, "w", encoding="utf16") as configfile:
        cp.write(configfile)

def start_ticspro(enable_server=False,
                  port=11000, 
                  connect_if_already_alive=False,
                  TICS_path=r"C:\Program Files (x86)\Texas Instruments\TICS Pro",
                  config_path=r"C:\ProgramData\Texas Instruments\TICS Pro"):
    """
    A function that demonstrates how to start up a TICS Pro instance with the 
    TCP server enabled/disabled, at the desired port.

    enable_server (bool): enables or disables the server for the started 
                          TICS Pro instance.
    port (int): port of the TCP server, if enable_server = True. Must be 
                between 0 and 65535.
    connect_if_already_alive (bool): If TICS Pro is already active at the 
                                     specified port, return an object that 
                                     can communicate with the active instance.
    TICS_path (str): string to TICS Pro path. Default is standard install dir.
    config_path (str): string to config path. Default is standard install dir.

    """
    if not enable_server and not connect_if_already_alive:
        # Start a TICS Pro without the TCP server enabled. No TICS Pro 
        # communication object will be available, since the TCP server is off.
        update_ticspro_ini(enable=False, root=config_path)
        p = subprocess.Popen(["explorer", os.path.join(TICS_path, "TICS Pro.exe")], 
                             creationflags=CREATE_NEW_CONSOLE)
        t = None
    else:
        if port & 0xffff != port:
            raise ValueError("Port must be between 0 and 65535")
        TICS_Pro_set = GetTICSProInstances()
        if port in GetTICSProActivePorts(TICS_Pro_set):
            if not connect_if_already_alive:
                raise ConnectionError("Port is already in use by another TICS Pro instance")
            else:
                # Connect to an existing TICS Pro with a TCP server at port. 
                # No Popen output, but a TICS Pro object is available.
                s_port = str(port)
                for v in TICS_Pro_set.values():
                    if v.endswith(s_port): break
                address, s_port = v.split(':')
                p = None
                t = TICSProTCPClient(address, port)
        else:
            # Start a TICS Pro with a TCP server enabled at port.
            update_ticspro_ini(enable=True, port=port, root=config_path)
            p = subprocess.Popen(["explorer", os.path.join(TICS_path, "TICS Pro.exe")], 
                                 creationflags=CREATE_NEW_CONSOLE)
            t = TICSProTCPClient(port=port)

    # The value of 'p' is irrelevant for TCP communication and can safely be 
    # ignored. It may sometimes be of interest if TICS Pro must be killed 
    # or if the PID information is required. This function returns the TICS 
    # Pro communication object 't', or None if 't' cannot be created.
    return t