# t32_test_example.py
#
# Example Python script that shows how to launch TRACE32,
# connect to it, load a simple demo program and run a couple
# of tests and display some results.
# Requires:
#     Python 3
#     Tkinter


from tkinter import *
from tkinter.filedialog import askopenfilename
from tkinter import messagebox
import sys
import platform
import os
import pathlib
import tempfile
import subprocess
import time

import ctypes        # module for C data types
import enum

# Change the value of APIPORT if UDP port 20000 is not available
# on your system
APIPORT='20000'
T32_DEV = 1
connstate=FALSE

# Use this class to determine the state of any scripts that we get
# TRACE32 to run via the API.
class PracticeState(enum.IntEnum):
    UNKNOWN = -1
    NOT_RUNNING = 0
    RUNNING = 1
    DIALOG_OPEN = 2

class Trace32State(enum.IntEnum):
    UNKNOWN = -1
    DOWN = 0
    NOACCESS = 1
    HALTED = 2
    RUNNING = 3


# Our simple GUI is defined in this class
class simple_tk_gui:
    def __init__(self, parent):
        self.connstate = FALSE
        self.parent = parent
        self.parent.minsize(width=350, height = 200)
        parent.title("TRACE32 Test Example")

        self.topframe = Frame(root)
        self.topframe.pack(fill=BOTH, padx=5, pady=5)

        self.midframe = Frame(root)
        self.midframe.pack(fill=BOTH, padx = 5, pady = 5)

        self.label = Label(self.midframe, text="")
        self.label.pack(side=LEFT, padx=5)

        self.bottomframe = Frame(root)
        self.bottomframe.pack(side=TOP, padx = 5, pady = 5 )

        self.start_test1_button = Button(self.topframe,
                                         text="Start Test 1",
                                         command=self.starttest1)
        self.start_test1_button.pack(side=LEFT, padx=5)

        self.start_test2_button = Button(self.topframe,
                                         text="Start Test 2",
                                         command=self.starttest2)
        self.start_test2_button.pack(side=RIGHT, padx=5)

        # Quite a bit of trouble to get a scrollbar for the listbox.
        # Put the scrollbar in the same frame as the listbox and use the
        # scrollbar position to adjust the yview of the Listbox
        self.results = Listbox(self.bottomframe, bg="WHITE", width=40)
        self.lbscroll = Scrollbar(self.bottomframe, orient=VERTICAL)
        self.lbscroll.pack(side=RIGHT,fill=Y)
        self.results.config(yscrollcommand=self.lbscroll.set)
        self.lbscroll.config(command=self.results.yview)
        self.results.pack(side=RIGHT, pady=5, padx=5)

    def LoadScript(self, msg):
        # Set text item and clear any data in the Listbox
        self.label.config(text=msg)
        self.results.delete(0,END)
        self.parent.update()

        # Build the command. TRACE32 is '/' and '\' agnostic and will convert
        # either to the correct one for the underlying host OS.
        cmd='DO '+SYSDIR+'/demo/powerpc/compiler/gnu/demo_sram.cmm'
        t32api.T32_Cmd(cmd.encode())

        # The next 4 lines will loop, checking whether the script we just
        # launched has completed or not. Control will not pass back to us until
        # it has finished.
        state = ctypes.c_int(PracticeState.UNKNOWN)
        rc = 0
        while rc==0 and not state.value==PracticeState.NOT_RUNNING:
            time.sleep(0.05)            # 50 ms pause in each loop
            rc = t32api.T32_GetPracticeState(ctypes.byref(state))

    def WaitTargetHalt(self):
        # Loop until the target is no longer executing
        state = ctypes.c_int(Trace32State.UNKNOWN)
        rc = 0
        while rc==0 and state.value!=Trace32State.HALTED:
            time.sleep(0.05)            # 50 ms pause in each loop
            rc = t32api.T32_GetState(ctypes.byref(state))


    def starttest1(self):
        # Launch ~~/demo/powerpc/compiler/gnu/demo_sram.cmm
        # Wait until script has completed
        # Set break on 100th write to flags[3]
        # Start target and wait for it to halt
        # Get the contents of array flags. Use the values to indicate whether
        # a test passed or not
        # Delete breakpoint

        self.LoadScript('Test 1 running. Please wait.')

        # Set a breakpoint to indicate the end of the test
        cmd='Break.Set Var.Address(flags[3]) /Write /Data.Byte 0x01 /Count 100.'
        t32api.T32_Cmd(cmd.encode())

        # Start the target running
        cmd='Go'
        t32api.T32_Cmd(cmd.encode())

        # Wait until the target has halted
        self.WaitTargetHalt()

        # Now that the target has halted, we can delete the breakpoint that we
        # set earlier to catch the end of the tests
        cmd='Break.Delete /ALL'
        t32api.T32_Cmd(cmd.encode())

        # Read the flags array from the target. This is defined as char[19]
        # Get the address and size of the array flags:
        symaddr = ctypes.c_uint()
        symsize = ctypes.c_uint()
        symreserved = ctypes.c_uint()

        rc = t32api.T32_GetSymbol(b'flags',
                                            ctypes.byref(symaddr),
                                            ctypes.byref(symsize),
                                            ctypes.byref(symreserved))

        # Declare a buffer large enough to hold the data to be read from target
        # memory. This will be symsize.value.
        buffer = ctypes.create_string_buffer(symsize.value)

        # Read symsize.value bytes from symaddr.value into the buffer
        rc = t32api.T32_ReadMemory(symaddr.value, 0, buffer, symsize.value)

        # Convert ctype into a bytearray for Python access
        test_data=bytearray(buffer)

        self.label.config(text="Test 1 Results:")
        self.parent.update()

        # If the value of an element in the array is 0 the test failed, if it
        # is 1 the test passed.
        # Update the Listbox widget in the GUI with the results
        i=1
        for value in test_data:
            item='Test '+str(i)+' '
            if value==0:
                item=item+'FAILED'
                self.results.insert(END,item)
                self.results.itemconfig(i-1,{'fg': 'red'})
            else:
                item=item+'PASSED'
                self.results.insert(END,item)
            i=i+1

    def starttest2(self):
        # Launch ~~/demo/powerpc/compiler/gnu/demo_sram.cmm
        # Wait until script has completed
        # Set break on entry and exit of function subst()
        # Start target and wait for it to halt
        # Load the values into local variable 'c' and run the function
        # Examine the result against the expected result
        #    'a' -> 'e'
        #    'e' -> 'i'
        #    'i' -> 'o'
        #    'o' -> 'u'
        #    'u' -> 'a'
        # Delete breakpoints
        # Update results

        test_values=['a','e','i','o','u']
        expected_results=['e','i','o','u','a']

        # Clear the listbox
        self.results.delete(0,END)

        self.LoadScript('Test 2 running. Please wait.')

        # Set a breakpoint to halt at the beginning of the function to
        # be tested. I added a one line offset to allow for variables to be
        # copied from the stack and be in scope.
        cmd='Break.Set subst\\1'
        t32api.T32_Cmd(cmd.encode())

        # Set a breakpoint to halt at the end of the function to
        # be tested. TRACE32 functions sYmbol.EPILOG() will ensure that the
        # breakpoint is set in the correct place.
        cmd='Break.Set sYmbol.EPILOG(subst)'
        t32api.T32_Cmd(cmd.encode())

        for i in range(len(test_values)):
            # Start the target running
            cmd='Go'
            t32api.T32_Cmd(cmd.encode())

            # Wait or the target to halt.
            self.WaitTargetHalt()

            # Write the current test value to the variable 'c'
            # which should now be in scope due to the breakpoint
            rc = t32api.T32_WriteVariableValue(b'c',
                                            ctypes.c_uint32(ord(test_values[i])),
                                            ctypes.c_int32(0))

            # Start the target running
            cmd='Go'
            t32api.T32_Cmd(cmd.encode())
            # Wait or the target to halt.
            self.WaitTargetHalt()

            # Ordinarily, just read the value of the returned variable at the
            # ends of the function. The compiler optimisations have made this
            # example a little more complicated. The value is pushed onto the
            # stack when the function is called. It is then copied into a register
            # but the return value is held in a different register. The value in
            # a register can be extracted like this:
            #    symval = ctypes.c_uint()
            #    symvalH = ctypes.c_uint()
            #    rc = t32api.T32_ReadRegisterByName(b'R3',
            #                                   ctypes.byref(symval),
            #                                   ctypes.byref(symvalH))
            # Alternatively, the compiler uses a built-in (but not normally visible)
            # place holder for return values called 'return'. Here, we will be
            # getting the value of this for our test results.
            symval = ctypes.c_uint()
            symvalH = ctypes.c_uint()
            rc = t32api.T32_ReadVariableValue(b'return',
                                            ctypes.byref(symval),
                                            ctypes.byref(symvalH))

            item='Test '+str(i+1)+' '
            # Compare results against expected result
            if chr(symval.value)==expected_results[i]:
                item=item+'PASSED: Expected '+expected_results[i]+', got '+chr(symval.value)
                self.results.insert(END,item)
            else:
                item=item+'FAILED: Expected '+expected_results[i]+', got '+chr(symval.value)
                self.results.insert(END,item)
                self.results.itemconfig(i,{'fg': 'red'})

        self.label.config(text="Test 2 Results:")
        self.parent.update()

        # Delete breakpoints
        cmd='Break.Delete /ALL'
        t32api.T32_Cmd(cmd.encode())



def closeWindow():
    result=messagebox.askyesno("Exit?",
                               "Quitting will also close TRACE32.\nAre you sure?")
    if result==FALSE:
        return
    if connstate==TRUE:
        # This will instruct TRACE32 to close the session and terminate
        # QT based TRACE32 running on Mac OS and Linux do not close
        # correctly when calling T32_Terminate(). This workaround solves that.
        if platform.system() == "Windows":
            t32api.T32_Terminate(rc)
        else:
            t32api.T32_Cmd(b'quit')
    else:
        t32process.kill()
    root.destroy()

# Launch TRACE32 for PowerPC Instruction Set Simulator using
# an example config.t32 file and one of the standard lauterbach
# demos.    To continue with the simulator a license is required.

# determine host OS
# This will load the correct DLL/shared object and set the
# system path, assuming a default TRACE32 installation:
# Windows   - C:/T32
# Mac/Linux - /opt/t32
#
# DLL, Python and OS must all be either 64bit or 32bit - you
# can't mix versions!!
if platform.system() == "Linux":
    SYSDIR='/opt/t32'
    if platform.machine().endswith('64'):
        #Linux64
        APIFILE='t32api64.so'
        T32BIN=SYSDIR+"/bin/pc_linux64/t32mppc-qt"
    else:
        #Linux32
        APIFILE='t32api.so'
        T32BIN=SYSDIR+"/bin/pc_linux/t32mppc-qt"
elif platform.system() == "Darwin":
    # Mac OS
    SYSDIR='/opt/t32'
    if platform.machine().endswith('64'):
        #Mac OS64
        APIFILE='libt32api64.so.1'
        T32BIN=SYSDIR+"/bin/macosx64/t32mppc-qt"
    else:
        #Mac OS32
        print("MacOSX 32bit is currently unsupported")
        exit()
elif platform.system() == "Windows":
    SYSDIR='C:/T32'
    if platform.machine().endswith('64'):
        #win64
        APIFILE='t32api64.dll'
        T32BIN=SYSDIR+"/bin/windows64/t32mppc.exe"
    else:
        #win32
        APIFILE='t32api.dll'
        T32BIN=SYSDIR+"/bin/windows/t32mppc.exe"
else:
    print("Unknown OS")
    exit()

# If your TRACE32 installation is not installed in the default
# location, change the value of SYS here and uncomment the next
# line.
# SYS='/my/trace32/location'

APIDIR='demo/api/python'
LIBFILE=os.path.join(os.sep,SYSDIR,APIDIR,APIFILE)
print("SYSDIR= ",SYSDIR)
print("LIBFILE= ", LIBFILE)
print("T32BIN= ",T32BIN)
t32api=ctypes.cdll.LoadLibrary(LIBFILE)

# Launch TRACE32 (~~/bin/<os>/t32mppc[.exe]) -c ./config-py.t32

# First, create ./config-py.t32
CONFIGDIR= pathlib.Path(__file__).parent
CONFIGFILE=os.path.join(CONFIGDIR,'config-py.t32')
fp = open(CONFIGFILE,"w+")                # Overwrite if file exists
print(" ",CONFIGDIR)
print(" ",CONFIGFILE)
fp.write("OS=\n")
fp.write("SYS="+SYSDIR+"\n")
fp.write("TMP="+tempfile.gettempdir()+"\n")
fp.write("ID=t32_ppc_python\n\n")
fp.write("PBI=SIM\n\n")
fp.write("RCL=NETASSIST\n")
fp.write("PACKLEN=1024\n")
fp.write("PORT="+APIPORT+"\n\n")
# Un-comment the following line to make TRACE32 start in invisible
# mode (No GUI)
#fp.write("SCREEN=OFF\n\n")

# Un-comment the following lines and configure correctly for your license
# server (if required for simulator)
#fp.write("LICENSE=\n")
#fp.write("RLM_LICENSE=5055@localhost\n\n")
fp.close()

# Launch TRACE32 as a sub process - this prevents our script
# from blocking
t32process=subprocess.Popen([T32BIN,'-c',CONFIGFILE])
# Wait a few seconds to give TRACE32 time to initialise - this may
# be up to 8 seconds depending upon the hardware and connection
# method. For SIM, we'll use 3 seconds.
time.sleep(3)

# Configure communication channel to an instance
# of TRACE32 running on the localhost, listening on UDP
# port APIPORT
t32api.T32_Config(b"NODE=",b"localhost")
t32api.T32_Config(b"PORT=", APIPORT.encode())
t32api.T32_Config(b"PACKLEN=",b"1024")

# Establish a connection to TRACE32
rc = t32api.T32_Init()
if rc != 0:
    connstate=FALSE
    print("Error with T32_Init")

# Sometimes the first attempt to Attach fails but a second will
# usually succeed. This often happens if the API has been left hanging
# when the previous user didn't call T32_Exit() before quitting.
for x in range(3):
    rc = t32api.T32_Attach(T32_DEV)
    if rc == 0:
        break
if rc != 0:
    connstate=FALSE
    t32api.T32_Exit()
    print("Error in T32_Attach")

# Ping TRACE32 to check the connection really is up and running
rc = t32api.T32_Ping()
if rc != 0:
    connstate=FALSE
    t32api.T32_Exit()
    print("Error in T32_Ping")

# Main program loop starts here
root = Tk()
my_gui = simple_tk_gui(root)

# Catch the window close event and provide our own handler
root.protocol("WM_DELETE_WINDOW",closeWindow)
root.mainloop()
