#!/usr/bin/env python3
#The AWG 8-bit Remote User Interface (AWG RemoteUI) is a program to remotely operate the Arbitray Wave form Generator(AWG).
# For instructions to build the AWG see: https://www.instructables.com/Poor-Mans-Waveform-Generator-Based-on-RP2040-Raspb
# 
#
# Following modules need to be present on Arbitray waveform generator (AWG) 8-bit based on RP2040
#	AWG.py:			contol module for the AWG communication with the remote UI
#	wave_gen.py:	module to calculate the wave form and program to RP2040 DMA and PIO
#	main.py:		modules which is called on startup to import AWG.py
#
# 14-Feb-2022: initial version
# 04-Mar-2022: added selection of maximum number of samples the AWG uses
# 09-Mar-2022: added enable/disable buttons depening on AWG state and remove user error messages not needed anymore
# 10-Mar-2022: change communication handling to adjust to Windows COM behavior
# 12-Mar-2022: add disconnect AWG on window close
#
# last update by wolf2018:
version_date = "12-Mar-2022"


from tkinter import *
from tkinter import ttk
from tkinter import messagebox
import json
from serial import Serial
import sys
import time
import io
import traceback

# AWG control variables

# define wave with defaults
wave = {"func" : "none",
        "frequency" : 2000,
        "amplitude" : 0.48,
        "offset" : 0.5,
        "phase" : 0,
        "replicate" : 1,
        "pars" : [0.2, 0.4, 0.2],
        "frequency_value" : 2000,
        "freq_range" : 1,
        "command" : "stop",
        "maxsamp" : 512,}
        #maxsamp must be a multiple of 4. 

# defaults for the different functions for initialization, when function is selected to ensure proper wave form
max_ampl = {"sine" : 0.48,
            "pulse" : 0.89,
            "gauss" : 0.55,
            "sinc" : 0.5,
            "expo" : 0.5,
            "noise" : 1,
            }

offset = {"sine" : 0.5,
          "pulse" : 0,
          "gauss" : 0,
          "sinc" : 0.5,
          "expo" : 0,
          "noise" : 0,
          }


# constants definbition
TerminationChar = "\r\n"

maxsamp_values=["32", "64", "128", "256", "512", "1024", "2048", "4096"]
# maxsamp values need to be a multiple of 4!

#set the default port
interface_port="/dev/ttyACM0"

# definition of call backs

def key_pressed(event):
	print(event)
	print(event.char)

def connect_cb():
	global ser_if
	open_handshake=bytearray(2)
	open_handshake=(0x3D,0x0D)
	port = interface.get()
	message.set("connecting to port: " + port)

	# check if AWG is already connected, otherwise open port and connect
	if Con_status.get() == "not connected":
		ser_if = Serial(port, baudrate=115200, interCharTimeout=1)
		
		time.sleep(0.1)
		ser_open = ser_if.isOpen()
		message.set(port + " is open: " + str(ser_open))

		print("ser_open= ", ser_open, "\n")
		
		while not ser_open:
			# wait a bit more to establish connection
			print("ser_open= ", ser_open, "\n")
			time.sleep(0.5)
			ser_open = ser_if.isOpen()

		if ser_open:
			print("starting handshake")
			message.set(port + " connected: starting handschake")
			time.sleep(0.5)
			
			# send "=" character to start handshake
			ser_if.write(open_handshake)
			
			# collect response
			bytes_received = ser_if.readline()

			print(str(bytes_received))
			
			response = json.load(io.BytesIO(bytes_received))

			#print("received stream: \n")
			#print(response)
			#print(type(response))

			Con_status.set(response["connection"])
			AWG_Version.set(response["version"])
			message.set("connected to AWG, port: " + interface.get())
			AWG_status.set("connected")

			cpu_freq = str(int(response["CPU"])/1000000) + " MHz"
			AWG_CPU.set(cpu_freq)
			set_button_state()



		else:

			message.set("could not connect")
			

	else:

		ser_open = ser_if.isOpen()
		if ser_open:

			message.set("AWG already connected")

		else:
			Con_status.set("not connected")
			message.set("connection status reset , check port and try again")
			set_button_state()


def receive():
    
    in_char="0"
    in_text=""

    while in_char != Terminate_char:

        in_char = ser_if.read(1)
        #print(in_char)
        in_text += in_char

    #return in_text.strip()
    return in_text


def setup_cb():
	#message.set("setup button clicked")

	#print("function: ", function.get())

	if function.get() == "":

		message.set("please select a function......")

	else:

		wave["command"] = "setup"

		# read all remaining values from UI
		#
		# wave["func"]	value is already set by function callback
		# wave["amplitude"] value is already set by amplitude callback
		# wave["offset"] value is already set by offset callback
		# wave["maxsamp"] value is set by set_maxsamp callback


		wave["frequency_value"] = float(frequency.get())
		wave["freq_range"] = int(freq_range.get())
		# calculate frequency
		wave["frequency"] = wave["frequency_value"] * wave["freq_range"]

		#wave["phase"] = 0		phase not yet implemented
		



		# set Parameters according to wave selected

		if wave["func"] == "sine":
			pass # no parameters needed

		elif wave["func"] == "pulse":
			wave["replicate"] = 1
			wave["pars"][0] = float(rise_time.get()[0:4])
			wave["pars"][1] = float(high_time.get()[0:4])
			wave["pars"][2] = float(fall_time.get()[0:4])

		elif wave["func"] == "gauss":
			wave["replicate"] = 1
			wave["pars"][0] = float(gauss_time.get()[0:5])

		elif wave["func"] == "sinc":
			wave["replicate"] = 1
			wave["pars"][0] = float(sinc_time.get()[0:5])

		elif wave["func"] == "expo":
			wave["replicate"] = int(expo_dir.get())
			wave["pars"][0] = float(expo_time.get()[0:5])

		elif wave["func"] == "noise":
			wave["replicate"] = 1
			wave["pars"][0] = float(noise_time.get()[0:5])


		# "jsoninize" wave dictionary decode as UTF-8 bytes and send to AWG

		
		out_file = json.dumps(wave).encode("UTF-8")
		#print(out_file)
		message.set("waiting for AWG response")
		ser_if.write(out_file+TerminationChar.encode())
		
		# receive status from AWG


		collect_input()



def stop_cb():

	#message.set("stop button clicked")

	wave["command"] = "stop"

	# "jasoninize" and send to AWG

	out_file = json.dumps(wave).encode("UTF-8")
	#print(out_file)
	ser_if.write(out_file+TerminationChar.encode())

	# receive status from AWG
	collect_input()

	if AWG_status.get() == "stopped":

		message.set("press setup generator to restart")

	else:
		message.set("communication error")


def disconnect_cb():

	#message.set("disconnect button clicked")
	
	wave["command"] = "disconnect"

	# "jsoninize" and send to AWG

	out_file = json.dumps(wave).encode("UTF-8")
	#print(out_file)
	ser_if.write(out_file+TerminationChar.encode())

	# receive status from AWG
	collect_input()

	if AWG_status.get() == "connection reset":

		# clear message line
		message.set("connection closed")
		Con_status.set("not connected")
		set_button_state()

		ser_if.close()

	else:
		message.set("communication error")

def window_closing():
	if messagebox.askokcancel("Quit", "Do you want to quit?"):
		if Con_status.get() == "READY":

			disconnect_cb()
		root.destroy()


def function_sine_cb():

	#print("  function sine calling")
	para_sine.tkraise()

	if wave["func"] != "sine":	# initialize parameters
		wave["func"]="sine"
		wave["amplitude"]=max_ampl["sine"]
		wave["offset"]=offset["sine"]
		Amplitude.set(str(max_ampl["sine"]))
		lbl_amplitude_value.configure(text=str(max_ampl["sine"]))
		Offset.set(str(offset["sine"]))
		lbl_offset_value.configure(text=str(offset["sine"]))


def function_pulse_cb():

	#print("  function pulse calling")
	para_pulse.tkraise()
	if wave["func"] != "pulse":	# initialize parameters
		wave["func"]="pulse"
		wave["amplitude"]=max_ampl["pulse"]
		wave["offset"]=offset["pulse"]
		Amplitude.set(str(max_ampl["pulse"]))
		lbl_amplitude_value.configure(text=str(max_ampl["pulse"]))
		Offset.set(str(offset["pulse"]))
		lbl_offset_value.configure(text=str(offset["pulse"]))
		rise_time.set(0.05)
		high_time.set(0.45)
		fall_time.set(0.05)
		low_display.configure(text=0.45)

def function_gauss_cb():

	print("  function gauss calling")
	para_gauss.tkraise()
	if wave["func"] != "gauss":	# initialize parameters
		wave["func"]="gauss"
		wave["amplitude"]=max_ampl["gauss"]
		wave["offset"]=offset["gauss"]
		Amplitude.set(str(max_ampl["gauss"]))
		lbl_amplitude_value.configure(text=str(max_ampl["gauss"]))
		Offset.set(str(offset["gauss"]))
		lbl_offset_value.configure(text=str(offset["gauss"]))
		gauss_time.set(0.095)



def function_sinc_cb():

	#print("  function sinc calling")
	para_sinc.tkraise()
	if wave["func"] != "sinc":	# initialize parameters
		wave["func"]="sinc"
		wave["amplitude"]=max_ampl["sinc"]
		wave["offset"]=offset["sinc"]
		Amplitude.set(str(max_ampl["sinc"]))
		lbl_amplitude_value.configure(text=str(max_ampl["sinc"]))
		Offset.set(str(offset["sinc"]))
		lbl_offset_value.configure(text=str(offset["sinc"]))
		sinc_time.set(0.029)


def function_expo_cb():

	print("  function expo calling")
	para_expo.tkraise()
	if wave["func"] != "expo":	# initialize parameters
		wave["func"]="expo"
		wave["amplitude"]=max_ampl["expo"]
		wave["offset"]=offset["expo"]
		Amplitude.set(str(max_ampl["expo"]))
		lbl_amplitude_value.configure(text=str(max_ampl["expo"]))
		Offset.set(str(offset["expo"]))
		lbl_offset_value.configure(text=str(offset["expo"]))
		expo_time.set(0.08)
		expo_dir.set(-1)


def function_noise_cb():

	#print("  function noise calling")
	para_noise.tkraise()
	if wave["func"] != "noise":	# initialize parameters
		wave["func"]="noise"
		wave["amplitude"]=max_ampl["noise"]
		wave["offset"]=offset["noise"]
		Amplitude.set(str(max_ampl["noise"]))
		lbl_amplitude_value.configure(text=str(max_ampl["noise"]))
		Offset.set(str(offset["noise"]))
		lbl_offset_value.configure(text=str(offset["noise"]))
		noise_time.set(3)

def rise_time_cb(v1, v2, v3):
	print(v1, " ", v2, " ", v3)
	calc_low_time()


# Amplitude and Offset value is a string, we use only 4 charactes e.g. 0.54
def Amplitude_changed(value):
	_amp=value[0:4]
	wave["amplitude"]=float(_amp)
	lbl_amplitude_value.configure(text=_amp)


def Offset_changed(value):
	_off=value[0:4]
	wave["offset"]=float(_off)
	lbl_offset_value.configure(text=_off)


def calc_low_time():

	r = rise_time.get()[0:4]
	h = high_time.get()[0:4]
	f = fall_time.get()[0:4]

	#low_tm = 1 - float(r) - float(h) - float(f)
	low_tm = 1 - float(r) - float(h) - float(f)
	low_display["text"]=str(low_tm)[0:4]


def set_maxsamp():

	maxsamp_ui = maxsamp.get()

	wave["maxsamp"] = int(maxsamp.get())


def report_exception_cb(self, exc_type, exc_value, tb):

	#print("exception type: ", exc_type)
	#print("exception value: ", exc_value)
	#print("tb object trace back: ", tb)

	if "could not open port" in str(exc_value):
		# catch error if port is wrong
		message.set("could not open port: " + interface.get())

	else:
		# get traceback stack summary
		err_msg_str = traceback.extract_tb(tb)

		# build error message for message box
		err_msg = ""

		for e in err_msg_str:

			err_text = tuple2str(e)
			err_msg += "\n" + err_text
			print(err_msg)

		err_msg += "\n" + str(exc_type) + "\n" + str(exc_value) + "\n\n press <Disconnect and close> button to close the application)"

		# show the error and raise it on top
		message.set("run-time Error occured!")
		f_error.grid()
		f_error.tkraise()
		Error_status.set(err_msg)



def exit_cb():
	# disconnect AWG then close the application
	disconnect_cb()
	time.sleep(1) # one second to show connection closed
	sys.exit()

# helper functions


# extract tupe elements into list
def tuple2str(in_tuple):
	return_str=""

	for l in in_tuple:
		return_str += (str(l) + " ")
		#print(return_str)
		#print(type(return_str))

	return return_str


# collect info from AWG and store info in the dictionary
def collect_input():

	#print("collect input")
	message.set("collecting input from AWG")

	# collect response, wait for first byte(s) from AWG

	while not ser_if.inWaiting():
		print("bytes received: ", ser_if.inWaiting())
		time.sleep(0.1)
		pass

	bytes_received = ser_if.readline()

	print("bytes received: ", str(bytes_received))

	AWG_info_in = json.load(io.BytesIO(bytes_received))

	# write AWG status to UI

	AWG_status.set(AWG_info_in["AWG_status"])
	AWG_nsamp.set(AWG_info_in["nsamp"])
	AWG_Fout.set(AWG_info_in["F_out"])
	freq_info = float(AWG_info_in["F_out"])

	#print("type F_out= ",type(freq_info))

	if freq_info > 999999.9:
		AWG_Fout.set("{:6.3f}".format(freq_info/1000000) + " MHz")

	elif freq_info > 999.9:
		AWG_Fout.set("{:6.3f}".format(freq_info/1000) + " kHz")

	else:
		AWG_Fout.set("{:6.3f}".format(freq_info) + " Hz")

	# clear message line
	message.set("connected to AWG")
	set_button_state()


def set_button_state():
	# activate / de-activate buttons based on AWG_status

	AWGstate = AWG_status.get()

	if AWGstate == "not connected":
		btn_connect["state"] = "normal"
		btn_disconnect["state"] = "disabled"
		btn_setup["state"] = "disabled"
		btn_stop["state"] = "disabled"

	elif AWGstate == "connection reset":
		btn_connect["state"] = "normal"
		btn_disconnect["state"] = "disabled"
		btn_setup["state"] = "disabled"
		btn_stop["state"] = "disabled"

	elif AWGstate == "connected":
		btn_connect["state"] = "disabled"
		btn_disconnect["state"] = "normal"
		btn_setup["state"] = "normal"
		btn_stop["state"] = "disabled"


	elif AWGstate == "running":
		btn_connect["state"] = "disabled"
		btn_disconnect["state"] = "normal"
		btn_setup["state"] = "normal"
		btn_stop["state"] = "normal"


	elif AWGstate == "stopped":
		btn_connect["state"] = "disabled"
		btn_disconnect["state"] = "normal"
		btn_setup["state"] = "normal"
		btn_stop["state"] = "disabled"


	else:
		message.set("could not get valid AWG status")


# ==========================
# User interface definitions
# ==========================

# define root window
root = Tk()
root.title("AWG 8-bit Remote User Interface")


# define default style
style_d=ttk.Style()
#style_d.theme_use("alt")
#style_d.configure("TFrame", background="#AAAAAA")
style_d.configure("TFrame", background="#E7EAEF")
#style_d.configure("AWG.TFrame", background="#AAFF7F")
style_d.configure("AWG.TFrame", background="#9dd5ff")
style_d.configure("Gen.TFrame", background="#FFFFFF")
style_d.configure("Error.TFrame", background="#FFBBBB", foreground="black")
#style_d.configure("TEntry", background="#00FF80")
style_d.configure("TScale", padding=10, font=("Arial", 10, "bold"), background="#9d9d9d")
style_d.configure("TLabel", font=("Arial", 10, "bold"))
style_d.configure("TEntry", foreground="blue",background="#AECEFF")
style_d.configure("TButton", padding=2, background="#aeceff", font=("Arial", 10, "bold"))
style_d.configure("Error.TButton", padding=2, foreground="white", background="#FF2222", font=("Arial", 10, "bold"))
style_d.configure("TRadiobutton", padding=2, font=("Arial", 10, "bold"))
style_d.configure("TSpinbox", foreground="blue")
style_d.configure("lbl_red.TLabel", foreground="black", background="red")
style_d.configure("lbl_yellow.TLabel", foreground="black", background="yellow", relief="sunken", font=("Arial", 10, "bold"))
style_d.configure("lbl_green.TLabel", foreground="white", background="#303088", font=("Arial", 10, "bold"), borderwidth=1)
#style_d.configure("AWGheading.TLabel", foreground="green", background="#BBFF9F", border=3)
style_d.configure("AWGheading.TLabel", foreground="#0055FF", background="#d3e9ff", font=("Arial", 10, "bold"), border=3)





# main frame to hold user interface
mainframe = ttk.Frame(root, padding="20 20 20 20")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
root.columnconfigure(0, weight=1)
root.rowconfigure(0, weight=1)


# Display the version
_row=0
lbl_version_text = ttk.Label(mainframe, text="RemoteUI version: ").grid(column=0, row=_row, sticky=W)
lbl_version = ttk.Label(mainframe, text=version_date).grid(column=1, row=0, sticky=W)
lbl_port = ttk.Label(mainframe, text="port: ").grid(column=3, row=0, sticky=E)
interface=StringVar()
interface.set(interface_port)
interface_entry = ttk.Entry(mainframe, width=12, textvariable=interface, font=("Arial", 10, "bold"), background="#AECEFF").grid(column=4, row=_row, sticky=(W))
btn_connect = ttk.Button(mainframe, text="connect to AWG", command=connect_cb)
btn_connect.grid(column=5, row=_row, rowspan=2, sticky=W)
btn_disconnect = ttk.Button(mainframe, text="disconnect AWG", command=disconnect_cb, state="disabled")
#btn_disconnect = ttk.Button(mainframe, text="disconnect AWG", command=disconnect_cb)
btn_disconnect.grid(column=6, row=_row, rowspan=2, sticky=W)


# Create Function selection buttons
_row+=3
lbl_function=ttk.Label(mainframe, text="Function: ").grid(column=0, row=_row, pady=10, sticky=W)
function=StringVar()
function0 = ttk.Radiobutton(mainframe, text="Sine", variable=function, value="sine", command=function_sine_cb).grid(column=1, row=_row, sticky=(W, E))
function1 = ttk.Radiobutton(mainframe, text="Pulse", variable=function, value="pulse", command=function_pulse_cb).grid(column=2, row=_row, sticky=(W, E))
function0 = ttk.Radiobutton(mainframe, text="Gaussian", variable=function, value="gauss", command=function_gauss_cb).grid(column=3, row=_row, sticky=(W, E))
function0 = ttk.Radiobutton(mainframe, text="Sinc", variable=function, value="sinc", command=function_sinc_cb).grid(column=4, row=_row, sticky=(W, E))
function0 = ttk.Radiobutton(mainframe, text="Exponential", variable=function, value="expo", command=function_expo_cb).grid(column=5, row=_row, sticky=(W, E))
function0 = ttk.Radiobutton(mainframe, text="Noise", variable=function, value="noise", command=function_noise_cb).grid(column=6, row=_row, sticky=(W, E))



# Create Frequency entering widget
_row+=1
lbl_frequency=ttk.Label(mainframe, text="Frequency:   ").grid(column=0, row=_row, padx=5, pady=10, sticky=E)
frequency=StringVar()
frequency.set("10000")
frequency_entry = ttk.Entry(mainframe, width=9, textvariable=frequency, font=("Arial", 10, "bold")).grid(column=1, row=_row, sticky=(W))

# Create Frequency range entering widget
lbl_freq_range=ttk.Label(mainframe, text="Frequency Range:").grid(column=2, row=_row, padx=10, sticky=E)
freq_range=StringVar()
freq_range.set("1")
freq_range0 = ttk.Radiobutton(mainframe, text="Hz", variable=freq_range, value="1").grid(column=3, row=_row, sticky=(W, E))
freq_range1 = ttk.Radiobutton(mainframe, text="KHz", variable=freq_range, value="1000").grid(column=4, row=_row, sticky=(W, E))


# Create Amplitude slider widget
_row=6
lbl_amplitude=ttk.Label(mainframe, text="Amplitude:   ").grid(column=0, row=_row, sticky=E)
Amplitude=DoubleVar()
Amplitude_entry = ttk.Scale(mainframe, orient=HORIZONTAL, length=100, from_=0.0, to=1.0, variable=Amplitude, command=Amplitude_changed).grid(column=1, row=_row, sticky=(W))
lbl_amplitude_value=ttk.Label(mainframe, text="0.00")
lbl_amplitude_value.grid(column=2, row=_row, sticky=W)


# Create Offset slider widget
lbl_offset=ttk.Label(mainframe, text="Offset:   ").grid(column=3, row=_row, sticky=E)
Offset=DoubleVar()
Offset_entry = ttk.Scale(mainframe, orient=HORIZONTAL, length=100, from_=0.0, to=1.0, variable=Offset, command=Offset_changed).grid(column=4, row=_row, sticky=(W))
lbl_offset_value=ttk.Label(mainframe, text="0.00")
lbl_offset_value.grid(column=5, row=_row, sticky=W)


# Create frames for the parameters
# A parameter frame will be raised to top level, on its respective radio button pressed.

# Frame for Sine parameters
#
_row=8
para_sine = ttk.Frame(mainframe, padding="5 20 5 20")
para_sine.grid(column=0, row=_row, columnspan=6, rowspan=3, sticky=(N, W, E, S))

_row+=1
lbl_parameter = ttk.Label(para_sine, text="Parameters").grid(column=0, row=_row, sticky=W)
lbl_para_function = ttk.Label(para_sine, text="for Sine").grid(column=1, row=_row, sticky=W)
_row+=1
lbl_para_range = ttk.Label(para_sine, text="Function Sine ").grid(column=0, row=_row, sticky=E)
lbl_para_range1 = ttk.Label(para_sine, text="has Parameter Amplitude and Offset only").grid(column=1, row=_row, rowspan=3, columnspan=3, sticky=W)


# Frame for pulse parameter
#
_row=8
para_pulse = ttk.Frame(mainframe, padding="5 20 5 20")
para_pulse.grid(column=0, row=_row, columnspan=6, rowspan=3, sticky=(N, W, E, S))

_row+=1
lbl_parameter = ttk.Label(para_pulse, text="Parameters").grid(column=0, row=_row, sticky=W)
lbl_para_function = ttk.Label(para_pulse, text="for Pulse").grid(column=1, row=_row, sticky=W)
ttk.Separator(para_pulse, orient="horizontal")
lbl_para_range = ttk.Label(para_pulse, text="Parameter").grid(column=2, row=_row, sticky=E)
lbl_para_range1 = ttk.Label(para_pulse, text="range 0...1").grid(column=3, row=_row, sticky=W)

# Create rise time entering widget
_row+=2
lbl_rise=ttk.Label(para_pulse, text="rise time:").grid(column=0, row=_row, sticky=W)
rise_time=StringVar()
rise_entry = ttk.Spinbox(para_pulse, width=5, textvariable=rise_time, from_=0, to=1, increment=0.01, command=calc_low_time, font=("Arial", 10, "bold")).grid(column=1, row=_row, sticky=(W))

# Create high time entering widget
lbl_high=ttk.Label(para_pulse, text="high time:").grid(column=2, row=_row, sticky=W)
high_time=StringVar()
high_entry = ttk.Spinbox(para_pulse, width=5, textvariable=high_time, from_=0, to=1, increment=0.01, command=calc_low_time, font=("Arial", 10, "bold")).grid(column=3, row=_row, sticky=(W))

# Create fall time entering widget
lbl_fall=ttk.Label(para_pulse, text="fall time:").grid(column=4, row=_row, sticky=W)
fall_time=StringVar()
fall_entry = ttk.Spinbox(para_pulse, width=5, textvariable=fall_time, from_=0, to=1, increment=0.01, command=calc_low_time, font=("Arial", 10, "bold")).grid(column=5, row=_row, sticky=(W))

# Create low time labels, low time is calculated
lbl_low=ttk.Label(para_pulse, text="low time(calc.):").grid(column=6, row=_row, padx=10, sticky=E)
low_display = ttk.Label(para_pulse, text="0.450")
low_display.grid(column=7, row=_row, sticky=(W))


# Frame for gauss parameter
#
_row=8
para_gauss = ttk.Frame(mainframe, padding="5 20 5 20")
para_gauss.grid(column=0, row=_row, columnspan=6, rowspan=3, sticky=(N, W, E, S))

_row+=1
lbl_parameter = ttk.Label(para_gauss, text="Parameters").grid(column=0, row=_row, sticky=W)
lbl_para_function = ttk.Label(para_gauss, text="for Gaussian ").grid(column=1, row=_row, sticky=W)
lbl_para_range = ttk.Label(para_gauss, text="Parameter").grid(column=2, row=_row, sticky=E)
lbl_para_range1 = ttk.Label(para_gauss, text="range 0.01...0.30").grid(column=3, row=_row, sticky=W)

# Create gauss Time entering widget
_row+=2
lbl_gauss=ttk.Label(para_gauss, text="gauss width:").grid(column=0, row=_row, sticky=W)
gauss_time=StringVar()
gauss_entry = ttk.Spinbox(para_gauss, width=6, textvariable=gauss_time, from_=0.01, to=0.3, increment=0.01, font=("Arial", 10, "bold")).grid(column=1, row=_row, sticky=(W))


# Frame for Sinc parameters
#
_row=8
para_sinc = ttk.Frame(mainframe, padding="5 20 5 20")
para_sinc.grid(column=0, row=_row, columnspan=6, rowspan=3, sticky=(N, W, E, S))

_row+=1
lbl_parameter = ttk.Label(para_sinc, text="Parameters").grid(column=0, row=_row, sticky=W)
lbl_para_function = ttk.Label(para_sinc, text="for Sinc ").grid(column=1, row=_row, sticky=W)
lbl_para_range = ttk.Label(para_sinc, text="Parameter").grid(column=2, row=_row, sticky=E)
lbl_para_range1 = ttk.Label(para_sinc, text="range 0.005...0.065").grid(column=3, row=_row, sticky=W)

# Create sinc Time entering widget
_row+=2
lbl_sinc=ttk.Label(para_sinc, text="sinc width:").grid(column=0, row=_row, sticky=W)
sinc_time=StringVar()
sinc_entry = ttk.Spinbox(para_sinc, width=6, textvariable=sinc_time, from_=0.005, to=0.065, increment=0.001, font=("Arial", 10, "bold")).grid(column=1, row=_row, sticky=(W))


# Frame for Expo parameters
#
_row=8
para_expo = ttk.Frame(mainframe, padding="5 20 5 20")
para_expo.grid(column=0, row=_row, columnspan=6, rowspan=3, sticky=(N, W, E, S))

_row+=1
lbl_parameter = ttk.Label(para_expo, text="Parameters").grid(column=0, row=_row, sticky=W)
lbl_para_function = ttk.Label(para_expo, text="for expo,  ").grid(column=1, row=_row, sticky=W)
lbl_para_range = ttk.Label(para_expo, text="Parameter").grid(column=2, row=_row, sticky=E)
lbl_para_range1 = ttk.Label(para_expo, text="range 0.005...0.155").grid(column=3, row=_row, sticky=W)

# Create expo Time and polarity entering widgets
_row+=1
lbl_expo=ttk.Label(para_expo, text="Expo width:").grid(column=0, row=_row, sticky=W)
expo_time=StringVar()
expo_time_entry = ttk.Spinbox(para_expo, width=6, textvariable=expo_time, from_=0.005, to=0.155, increment=0.001, font=("Arial", 10, "bold")).grid(column=1, row=_row, sticky=(W))
# Create direction entering widgets
lbl_expo_dir=ttk.Label(mainframe, text="Direction:").grid(column=2, row=_row, sticky=E)
expo_dir=StringVar()
expo_dir0 = ttk.Radiobutton(para_expo, text="raising", variable=expo_dir, value="-1").grid(column=3, row=_row, sticky=(W, E))
expo_dir1 = ttk.Radiobutton(para_expo, text="trailing", variable=expo_dir, value="+1").grid(column=4, row=_row, sticky=(W, E))


# Frame for noise parameters
#
_row=8
para_noise = ttk.Frame(mainframe, padding="5 20 5 20")
para_noise.grid(column=0, row=_row, columnspan=6, rowspan=3, sticky=(N, W, E, S))

_row+=1
lbl_parameter = ttk.Label(para_noise, text="Parameters").grid(column=0, row=_row, sticky=W)
lbl_para_function = ttk.Label(para_noise, text="for Noise ").grid(column=1, row=_row, sticky=W)
lbl_para_range = ttk.Label(para_noise, text="Parameter").grid(column=2, row=_row, sticky=E)
lbl_para_range1 = ttk.Label(para_noise, text="range 1...8").grid(column=3, row=_row, sticky=W)

# Create noise Time entering widget
_row+=2
lbl_noise=ttk.Label(para_noise, text="noise width:").grid(column=0, row=_row, sticky=W)
noise_time=StringVar()
#noise_entry = ttk.Entry(para_noise, width=4, textvariable=noise_time).grid(column=1, row=_row, sticky=(W))
noise_entry = ttk.Spinbox(para_noise, width=4, textvariable=noise_time, from_=1, to=8, increment=1, font=("Arial", 10, "bold")).grid(column=1, row=_row, sticky=(W))



# Frame for Welcome message
# must be last in Parameter frame, so it is displayed when program is started
#
_row=8
para_welcome = ttk.Frame(mainframe, padding="5 20 5 20", borderwidth=3)
para_welcome.grid(column=0, row=_row, columnspan=6, rowspan=3, sticky=(N, W, E, S))

_row+=1
lbl_welcome = ttk.Label(para_welcome, text=("Welcome to Arbitray Wave Form Generator remote control.")).grid(column=0, row=_row, sticky=W)
_row+=1
lbl_welcome0 = ttk.Label(para_welcome, text=("Power On AWG, then enter/change port and press connect to AWG button")).grid(column=0, row=_row, sticky=W)
_row+=1
lbl_welcome1 = ttk.Label(para_welcome, text=("Choose a Function, change frequency and adjust parameters as needed")).grid(column=0, row=_row, sticky=W)
_row+=1
lbl_welcome2 = ttk.Label(para_welcome, text=("Use setup and stop buttons to start/stop the generator")).grid(column=0, row=_row, sticky=W)

# Create frame for the setup generator button
#
_row=11
f_setup_gen = ttk.Frame(mainframe, padding="5 20 5 20", style="Gen.TFrame")
f_setup_gen.grid(column=0, row=_row, columnspan=8, rowspan=3, sticky=(N, W, E, S))

# Setup generator and stop generator buttons
_row+=1
lbl_setup = ttk.Label(f_setup_gen, text=(" AWG controls: ")).grid(column=0, row=_row, rowspan=3, padx=10, sticky=W)
btn_setup = ttk.Button(f_setup_gen, text="setup generator", default="active", command=setup_cb, state="disabled")
btn_setup.grid(column=1, row=_row, columnspan=2, sticky=W)
btn_stop = ttk.Button(f_setup_gen, text="stop generator", default="normal", command=stop_cb, state="disabled")
btn_stop.grid(column=3, row=_row, columnspan=2, sticky=W)


# AWG maximum number of samples
#ttk.Separator(f_setup_gen, orient="vertical").grid(column=5, row=_row, sticky=W)
lbl_maxsamp=ttk.Label(f_setup_gen, text=" max samples: ").grid(column=6, row=_row, padx=20, sticky=E)
maxsamp=StringVar()
maxsamp.set("512")
maxsamp_entry = ttk.Spinbox(f_setup_gen, width=5, textvariable=maxsamp, values=maxsamp_values, command=set_maxsamp, font=("Arial", 10, "bold")).grid(column=7, row=_row, sticky=(W))

_row+=4
lbl_message = ttk.Label(f_setup_gen, text=("current status:")).grid(column=0, row=_row, padx=10, pady=20, sticky=E)
message=StringVar()
message.set("AWG Remote-UI ready")
lbl_message_value = ttk.Label(f_setup_gen, textvariable=message, style="lbl_yellow.TLabel").grid(column=1, row=_row, padx=5, pady=20, columnspan=5, sticky=W)


# Create Status Frame
#
_row=16
f_status = ttk.Frame(mainframe, style="AWG.TFrame", padding="5 10 5 20")
f_status.grid(column=0, row=_row, columnspan=8, rowspan=2, sticky=(N, W, E, S))
#

lbl_AWG_heading = ttk.Label(f_status, text="Arbitrary Wave Form Generator settings and status:", style="AWGheading.TLabel").grid(column=0, row=_row, padx=5, pady=5, columnspan=8, sticky=W)

_row+=1
lbl_AWG_status = ttk.Label(f_status, text="AWG status:").grid(column=0, row=_row, padx=5, sticky=W)
AWG_status=StringVar()
AWG_status.set("not connected")
lbl_AWG_status_value = ttk.Label(f_status, textvariable=AWG_status, style="lbl_green.TLabel").grid(column=1, row=_row, padx=5, pady=10, sticky=E)
#
lbl_AWG_Fout = ttk.Label(f_status, text=("Frequency out:")).grid(column=3, row=_row, padx=5, sticky=E)
AWG_Fout=StringVar()
AWG_Fout.set("0000000")
lbl_AWG_Fout_value = ttk.Label(f_status, textvariable=AWG_Fout, style="lbl_green.TLabel").grid(column=4, row=_row, padx=5, sticky=E)
#
lbl_AWG_nsamp = ttk.Label(f_status, text=("Samples: ")).grid(column=5, row=_row, sticky=W)
AWG_nsamp=StringVar()
AWG_nsamp.set("0000")
lbl_AWG_nsamp_value = ttk.Label(f_status, textvariable=AWG_nsamp, style="lbl_green.TLabel").grid(column=6, row=_row, padx=5, sticky=W)
_row=18
lbl_connection_status = ttk.Label(f_status, text="AWG connection:").grid(column=0, row=_row, padx=5, pady=10, sticky=W)
Con_status=StringVar()
Con_status.set("not connected")
lbl_connection_status_value = ttk.Label(f_status, textvariable=Con_status).grid(column=1, row=_row, padx=5, sticky=W)
#
_row+=1
lbl_AWG_CPU = ttk.Label(f_status, text=("AWG RP2040 CPU frequency:")).grid(column=0, row=_row, columnspan=2, padx=5, sticky=W)
AWG_CPU=StringVar()
AWG_CPU.set("000")
lbl_AWG_CPU_value = ttk.Label(f_status, textvariable=AWG_CPU).grid(column=2, row=_row, padx=5, sticky=E)
#
lbl_AWG_Version = ttk.Label(f_status, text=("AWG Version:")).grid(column=7, row=_row, padx=5, sticky=W)
AWG_Version=StringVar()
AWG_Version.set("0.0.0")
lbl_AWG_Version_value = ttk.Label(f_status, textvariable=AWG_Version).grid(column=8, row=_row, padx=5, sticky=W)


# error frame to display exceptions
_row=2
f_error = ttk.Frame(mainframe, style="Error.TFrame", relief="ridge", padding="5 10 5 20")
f_error.grid(column=0, row=_row, columnspan=8, rowspan=10, sticky=(N, W, E, S))
#

lbl_Error_heading = ttk.Label(f_error, text="Program Exception:", style="lbl_red.TLabel").grid(column=0, row=_row, padx=5, pady=5, columnspan=6, sticky=W)

btn_exit = ttk.Button(f_error, text="Disconnect and close", command=exit_cb, style="Error.TButton").grid(column=7, row=_row, sticky=W)

_row+=1
Error_status=StringVar()
Error_status.set("no error")
lbl_Error_status_value = ttk.Label(f_error, textvariable=Error_status, style="lbl_red.TLabel").grid(column=0, row=_row, padx=5, pady=10, columnspan=8, rowspan=7, sticky=W)


# hide the frame until an Error occures
f_error.grid_remove()

#lbl_frequency.bind("<Key>", key_pressed)
#button.bind("<Button>", button_pressed)

# run the root windows main loop
# track and process exceptions


Tk.report_callback_exception = report_exception_cb
root.protocol("WM_DELETE_WINDOW", window_closing)
root.mainloop()


