from tkinter import *
from tkinter import ttk
from tkinter import messagebox

from cview_model import *
from cview_view import *
from cview_io import *
from cview_log import *


class Controller:
    def __init__(self, parent):
        self.window = Toplevel(parent)
        self.window.title("Controller")
        self.window.protocol('WM_DELETE_WINDOW', parent.destroy)
        self.window.withdraw()

        ####################
        # Bind C libraries

        self.model = Model()
        self.view1 = View(parent)

        ####################
        # CWG serial device
        self.ser = CwgIO()
        self.ser.set_speed(19200)

        self.view1.btn_ser_refresh.config(command=self.UpdateSerialDevices)
        self.view1.btn_ser_connect.config(command=self.SerialConnect)
        self.view1.btn_ser_disconnect.config(command=self.SerialDisconnect)
        self.model.serial_device_list.addCallback(self.SerialDevicesChanged)
        self.view1.cbox_ser_device.bind("<<ComboboxSelected>>", self.SerialDeviceSelected)
        self.model.serial_speed_list.addCallback(self.SerialSpeedChanged)
        self.view1.cbox_ser_speed.bind("<<ComboboxSelected>>", self.SerialSpeedSelected)

        ####################
        # File Upload
        self.view1.btn_up_src.config(command=self.UpdateUploadSource)
        self.view1.str_up_src.trace_add("write",lambda name,index,mode,sv=self.view1.str_up_src.get():self.UploadSourceChanged(name,index,mode,self.view1.str_up_src.get()))
        self.view1.btn_up_swap.config(command=self.UploadSwapSrcDest)

        self.view1.btn_up_dest.config(command=self.UpdateCwgFiles)

        self.view1.mode_sel_up.trace_add("write",lambda name,index,mode,sv=self.view1.mode_sel_up.get():self.UploadModeChanged(name,index,mode,self.view1.mode_sel_up.get()))

        self.view1.btn_up_upload.config(command=self.VUploadFile)
        self.model.cwg_files.addCallback(self.CwgFilesChanged)

        # this method binds a controller callback to the ComboboxSelected event
        # but that doesn't respond to user entries
        # self.view1.cbox_up_dest.bind("<<ComboboxSelected>>", self.UploadDestSelected)
        # this method binds a trace function to the combobox string variable,
        # so it handles all combobox changes
        self.view1.str_up_dest.trace_add("write",lambda name,index,mode,sv=self.view1.str_up_dest.get():self.UploadDestChanged(name,index,mode,self.view1.str_up_dest.get()))

        # register trace function to detect changes to plot control stream log option
        self.view1.stream_sel_log.trace_add("write",lambda name,index,mode,sv=self.view1.stream_sel_log.get():self.PlotStreamLogChanged(name,index,mode,self.view1.stream_sel_log.get()))

        # register trace function to detect changes to plot control stream passive option
        self.view1.stream_sel_passive.trace_add("write",lambda name,index,mode,sv=self.view1.stream_sel_passive.get():self.PlotStreamPassiveChanged(name,index,mode,self.view1.stream_sel_passive.get()))

        # register trace function to detect changes to plot control period option
        self.view1.ent_plot_period_sec.bind("<Return>",self.PlotPeriodChanged)

        ####################
        # File Download
        self.view1.btn_down_dest.config(command=self.UpdateDownloadDest)
        self.view1.str_down_dest.trace_add("write",lambda name,index,mode,sv=self.view1.str_down_dest.get():self.DownloadDestChanged(name,index,mode,self.view1.str_down_dest.get()))
        self.view1.btn_down_swap.config(command=self.DownloadSwapSrcDest)

        self.view1.btn_down_src.config(command=self.UpdateCwgFiles)

        self.view1.mode_sel_down.trace_add("write",lambda name,index,mode,sv=self.view1.mode_sel_down.get():self.DownloadModeChanged(name,index,mode,self.view1.mode_sel_down.get()))

        self.view1.btn_down_download.config(command=self.VDownloadFile)

        self.view1.str_down_src.trace_add("write",lambda name,index,mode,sv=self.view1.str_down_src.get():self.DownloadSourceChanged(name,index,mode,self.view1.str_down_src.get()))

        ####################
        # LI-COR Control
        self.view1.btn_licor_power_on.config(command=self.LicorPowerOn)
        self.view1.btn_licor_power_off.config(command=self.LicorPowerOff)
        self.view1.btn_cron_en.config(command=self.CronEnable)
        self.view1.btn_cron_di.config(command=self.CronDisable)
        self.view1.btn_send_escape.config(command=self.SendEscape)

        ####################
        # Plot Control
        self.view1.btn_plot_toggle.config(command=self.TogglePlot)
        self.view1.btn_plot_clear.config(command=self.ClearPlots)

        ####################
        # MDIO Control
        # register trace function to detect changes to plot control stream passive option
        self.view1.mdio_preset_dir.trace_add("write",lambda name,index,mode,sv=self.view1.mdio_preset_dir.get():self.MDIOPresetDirChanged(name,index,mode,self.view1.mdio_preset_dir.get()))
        self.view1.btn_mdio_io.config(command=self.MDIORunIO)
        self.view1.btn_mdio_preset.config(command=self.MDIORunPreset)
        self.view1.btn_mdio_macro.config(command=self.MDIORunMacro)
        ####################
        # MMEAS Control
        self.view1.btn_mmeas_tm.config(command=self.MMEASRunTM)
        self.view1.btn_mmeas_cyc.config(command=self.MMEASRunCyc)

        ####################
        # Quit Control
        self.view1.btn_quit.config(command=self.QuitApp)

        ####################
        # Initialization
        self.UpdateSerialDevices()
        return

    def ClearPlots(self):

        if self.model.plot_state.get() == PLOT_ON:
            # plot running,
            # set plot clear flag (handled by UpdatePlot)
            self.model.plot_clear.set(True)
        else:
            # call ClearPlots. reset flag
            self.view1.ClearPlots()
            self.model.plot_clear.set(False)

        return

    def UpdatePlot(self):

        if self.model.plot_state.get() == PLOT_ON:
            self.model.plot_state.set(PLOT_UPDATING)
            test= False #True

            rval=0
            crlf = self.ser.cmd_bytes(CRLF)
            if not test:
                rval,br = self.ser.read_until(crlf,256,2)

            if test or br > 0 and len(rval) > 0:
                cwg_log.debug("update plot rval:["+str(rval)+"/"+str(br)+"]")
                # parse the string
                if not test :
                    dlist = rval.decode().split(",")
                else:
                    # fake code for testing
                    self.view1.yval=10*random.random()
                    dstr=dt.datetime.now().strftime('%Y-%m-%dT%H:%M:%S.%f')+","+'{:e}'.format(self.view1.yval)
                    dlist = dstr.split(",")
                cwg_log.debug("dlist:{"+str(dlist)+"}")

                if self.view1.UpdatePlot(dlist, test) != 0:
                    rval,br = self.ser.read_until(crlf,256,4)

            if self.model.plot_clear.get():
                self.view1.ClearPlots()
                self.model.plot_clear.set(False)

            self.model.plot_state.set(PLOT_ON)

        else:
            cwg_log.debug("plot OFF")

        if self.model.plot_state.get() == PLOT_ON:
            if self.model.plot_stream_passive.get():
                if self.ser.rx_waiting() > 0:
                    period_ms=0
                else:
                    period_ms=int(self.model.plot_period_sec.get()*1000)
                    if period_ms < 500:
                        period_ms = int(500)
                self.view1.btn_plot_toggle.after(period_ms,self.UpdatePlot)
            else:
                period_ms=int(self.model.plot_period_sec.get()*1000)
                if period_ms < 500:
                    period_ms = int(500)
                self.view1.btn_plot_toggle.after(period_ms,self.UpdatePlot)
        return

    def StopPlot(self):
        if self.model.serial_connected.get():
            cwg_log.debug("stopping plot")
            while(self.model.plot_state.get() == PLOT_UPDATING):
                pass
            self.model.plot_state.set(PLOT_OFF)

            self.ser.send_esc(3)
            self.ser.drain_rx(5)
        return

    def SetEcho(self,enabled):
        self.ser.set_echo(enabled)
        return

    def SetPrompt(self,enabled):
        self.ser.set_prompt(enabled)
        return

    def StartStreaming(self,period_sec,stream_log):
        self.ser.start_streaming(period_sec,stream_log)
        return

    def StartPlot(self):
        test=0
        if not self.model.serial_connected.get():
            test = self.SerialConnect()
        if test == 0:
            cwg_log.debug("starting plot")
            if self.model.plot_period_sec.get() < 0.5:
                self.model.plot_period_sec.set(1.0)

            if not self.model.plot_stream_passive.get():
                self.ser.licor_power(True)
                self.ser.set_echo(True)
                self.ser.set_prompt(True)
                self.ser.start_streaming(self.model.plot_period_sec.get(),self.model.plot_stream_log.get())
            self.ser.drain_rx(5)
            self.model.plot_state.set(PLOT_ON)
            self.UpdatePlot()
            cwg_log.debug("StartPlot DONE:"+str(self.model.plot_state.get()))
        else:
            cwg_log.debug("StartPlot not connected ["+str(test)+"]")

        return

    def TogglePlot(self):
        test=0
        if not self.model.serial_connected.get():
            test = self.SerialConnect()

        if test == 0:
            if self.model.plot_state.get() == PLOT_ON or self.model.plot_state.get() == PLOT_UPDATING:
                self.model.plot_state.set(PLOT_OFF)
                self.StopPlot()
                self.view1.btxt_plot_toggle.set("Start")
            else:
                self.model.plot_state.set(PLOT_ON)
                self.StartPlot()
                self.view1.btxt_plot_toggle.set("Stop")
        else:
            cwg_log.debug("TogglePlot not connected ["+str(test)+"]")
        return

    def SerialConnect(self):
        cwg_log.debug("connecting >>>:"+self.model.serial_device.get())
        self.ser.set_device(self.model.serial_device.get())
        ser_speed = int(self.model.serial_speed.get())
        self.ser.set_speed(ser_speed)
        test = self.ser.connect()
        self.ser.show()
        if test == 0:
            self.model.serial_connected.set(True)
        else:
            cwg_log.debug("SerialConnect failed")
        return test

    def SerialDisconnect(self):
        cwg_log.debug("disconnecting")
        test = self.ser.disconnect()
        self.model.serial_connected.set(False)
        return

    def LicorPowerOn(self):
        cwg_log.debug("licor power ON")
        if not self.model.serial_connected.get():
            test = self.SerialConnect()
        self.ser.licor_power(True)
        self.ser.drain_rx(2)
        self.model.li_power_state.set(LI_POWER_ON)
        return

    def LicorPowerOff(self):
        cwg_log.debug("licor power OFF")
        if not self.model.serial_connected.get():
            test = self.SerialConnect()
        self.ser.licor_power(False)
        self.ser.drain_rx(2)
        self.model.li_power_state.set(LI_POWER_OFF)
        return

    def ToggleLicorPower(self):
        if self.model.li_power_state.get() == LI_POWER_ON :
            self.LicorPowerOff()
        else:
            self.LicorPowerOn()
        return

    def CronEnable(self):
        cwg_log.debug("cron ENABLE")
        if not self.model.serial_connected.get():
            test = self.SerialConnect()
        self.ser.set_cron(True)
        self.model.cron_state.set(CRON_EN)
        self.ser.drain_rx(2)
        return

    def CronDisable(self):
        cwg_log.debug("cron DISABLE")
        if not self.model.serial_connected.get():
            test = self.SerialConnect()
        self.ser.set_cron(False)
        self.model.cron_state.set(CRON_DI)
        self.ser.drain_rx(2)
        return

    def SendEscape(self):
        cwg_log.debug("send escape")
        if not self.model.serial_connected.get():
            test = self.SerialConnect()
        self.ser.send_esc(3)
        self.ser.drain_rx(2)
        return

    def ReadCwgDir(self):
        if not self.model.serial_connected.get():
            test = self.SerialConnect()

        rbytes,br = self.ser.read_dir()
        # split dir output to get file names
        ftoks = rbytes.decode().split("\r\n")
        flist = list()
        if len(ftoks) > 1 :
            # validate and add files
            for xfile in ftoks:
                cwg_log.debug("xfile:"+xfile)
                if self.model.ValidateCWGFile(xfile) == 1:
                    flist.append(xfile)
        self.ser.drain_rx(2)
        if self.model.debug.get():
            cwg_log.debug("flist:"+flist)
        return flist

    def UploadFile(self, dest, src, binary):
        if not self.model.serial_connected.get():
            test = self.SerialConnect()

        test = self.ser.upload_file(dest.encode(),src.encode(),binary)
        return test

    def VUploadFile(self):
        val = self.model.ValidateUpload()
        if self.model.debug.get() :
            info = "src:"+self.model.upload_src.get()+"\n"
            info +="dest:"+self.model.upload_dest.get()+"\n"
            info +="port:"+self.model.serial_device.get()+"\n"
            info +="mode:"+str(self.model.upload_mode.get())+"\n"
            messagebox.askokcancel(title = "upload", message = info)
        if val :
            self.UploadFile(self.model.upload_dest.get(),self.model.upload_src.get(),self.model.upload_mode.get())
        return

    def DownloadFile(self, dest, src, binary):
        if not self.model.serial_connected.get():
            test = self.SerialConnect()

        test = self.ser.download_file(dest.encode(),src.encode(),binary)
        return test

    def VDownloadFile(self):
        val = self.model.ValidateDownload()
        if self.model.debug.get() :
            info = "src:"+self.model.download_src.get()+"\n"
            info +="dest:"+self.model.download_dest.get()+"\n"
            info +="port:"+self.model.serial_device.get()+"\n"
            info +="mode:"+str(self.model.download_mode.get())+"\n"
            messagebox.askokcancel(title = "download", message = info)
        if val :
            self.DownloadFile(self.model.download_dest.get(),self.model.download_src.get(),self.model.download_mode.get())
        return

    def QuitApp(self):
        try:
            self.StopPlot()
            cwg_log.debug("disconnecting...")
            self.ser.disconnect()
        except Exception as e:
            cwg_log.debug("shutdown error")
        cwg_log.debug("done")
        exit()
        return

    def UpdateSerialDevices(self):
        self.model.updateSerialDevices()
        return

    def SerialDevicesChanged(self, devlist):
        self.view1.setSerialDeviceList(devlist)
        return

    def SerialDeviceSelected(self,event):
        current_serial = self.model.serial_device.get()
        if self.view1.cbox_ser_device.get() != current_serial:
            cwg_log.debug("SerialDeviceSelected:"+self.view1.cbox_ser_device.get())
            self.model.serial_device.set(self.view1.cbox_ser_device.get())
            self.SerialDisconnect()
        else:
            cwg_log.debug("SerialDevice n/c:"+str(current_serial))
        return

    def SerialSpeedChanged(self, devlist):
        self.view1.setSerialSpeedList(devlist)
        return

    def SerialSpeedSelected(self,event):
        current_speed = self.model.serial_speed.get()
        if self.view1.cbox_ser_speed.get() != current_speed:
            cwg_log.debug("SerialSpeedSelected:"+self.view1.cbox_ser_speed.get())
            self.model.serial_speed.set(self.view1.cbox_ser_speed.get())
            self.SerialDisconnect()
        else:
            cwg_log.debug("SerialSpeed n/c:"+str(current_speed))
        return

    def UpdateUploadSource(self):
        self.model.updateUploadSource()
        self.view1.setUploadSource(self.model.upload_src.get())
        return

    def UploadSourceChanged(self,name,index,mode,sv):
        cwg_log.debug("UploadSourceChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
        self.model.upload_src.set(sv)
        self.view1.setUploadSource(sv)
        return

    def UpdateCwgFiles(self):
        cwg_files = self.ReadCwgDir()
        self.model.cwg_files.set(cwg_files)
        return

    def CwgFilesChanged(self, cwg_files):
        self.view1.setUploadDest(cwg_files,0)
        self.view1.setDownloadSource(cwg_files,0)
        return

    def UploadDestChanged(self,name,index,mode,sv):
        cwg_log.debug("UploadDestChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
        self.model.upload_dest.set(sv)
        return

    def UploadModeChanged(self,name,index,mode,sv):
        cwg_log.debug("UploadModeChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
        self.model.upload_mode.set(sv)
        return

    def UploadSwapSrcDest(self):
        tmp = self.model.upload_src.get()
        self.UploadSourceChanged(None,None,None,self.model.upload_dest.get())
        self.model.upload_dest.set(tmp)
        self.view1.setSelectUploadDest(tmp)
        return

    def UpdateDownloadDest(self):
        self.model.updateDownloadDest()
        self.view1.setDownloadDest(self.model.download_dest.get())
        return
    def DownloadDestChanged(self,name,index,mode,sv):
        cwg_log.debug("DownloadDestChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
        self.model.download_dest.set(sv)
        self.view1.setDownloadDest(sv)
        return

    def DownloadSourceChanged(self,name,index,mode,sv):
        cwg_log.debug("DownloadSourceChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
        self.model.download_src.set(sv)
        return

    def DownloadModeChanged(self,name,index,mode,sv):
        cwg_log.debug("DownloadModeChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
        self.model.download_mode.set(sv)
        return

    def DownloadSwapSrcDest(self):
        tmp = self.model.download_dest.get()
        self.DownloadDestChanged(None,None,None,self.model.download_src.get())
        self.model.download_src.set(tmp)
        self.view1.setSelectDownloadSource(tmp)
        return

    def PlotStreamLogChanged(self,name,index,mode,sv):
        cwg_log.debug("PlotStreamLogChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
        self.model.plot_stream_log.set(sv)
        return

    def PlotStreamPassiveChanged(self,name,index,mode,sv):
        cwg_log.debug("PlotStreamPassiveChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
        self.model.plot_stream_passive.set(sv)
        return

#    def PlotPeriodChanged(self,name,index,mode,sv):
#        cwg_log.debug("PlotPeriodChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
#        self.model.plot_period_sec.set(sv)
#        return

    def PlotPeriodChanged(self,event):
        cwg_log.debug("PlotPeriodChanged: sv:"+str(self.view1.ent_plot_period_sec.get()))
        self.model.plot_period_sec.set(float(self.view1.ent_plot_period_sec.get()))
        return

    def MDIOPresetDirChanged(self,name,index,mode,sv):
        cwg_log.debug("MDIOPresetDirChanged: name:"+name+" idx:"+str(index)+" mode:"+mode+" sv:"+str(sv))
        self.model.preset_dir.set(sv)
        return

    def MDIORunIO(self):
        act = self.view1.sbox_mdio_op.get().upper()
        port = self.view1.sbox_mdio_port_type.get().upper()
        port_n = self.view1.sbox_mdio_port_n.get()
        sel = self.view1.sbox_mdio_port_sel.get().upper()
        th = self.view1.ent_mdio_th.get()
        tl = self.view1.ent_mdio_tl.get()
        cyc = self.view1.ent_mdio_cyc.get()
        pdel = self.view1.ent_mdio_pdel.get()
        cwg_log.debug("MDIORunIO: port:%s n:%s sel:%s act:%s, th:%s tl:%s cyc:%s pdel:%s", \
        port, port_n, sel, act, th, tl, cyc, pdel)


        valid = True
        emsg = "OK"
        cmd_str = "mdio"
        try:
            if act == '' or not act in self.view1.sbox_mdio_op['values']:
                valid = False
                emsg = "invalid action"
            elif not th.isnumeric() or int(th)<0:
                if act != 'IOL' and act !='IOH':
                    valid = False
                    emsg = "invalid Th (>=0 required for P*/S*)"
            elif not tl.isnumeric() or int(tl)<0:
                if act != 'IOL' and act !='IOH':
                    valid = False
                    emsg = "invalid Tl (>=0 required for P*/S*)"
            elif not cyc.isnumeric() or int(cyc)<=0:
                if act == 'PLH' and act =='PHL':
                    valid = False
                    emsg = "cycles >0  required for P*"
            elif pdel.isnumeric() and int(pdel)<0:
                valid = False
                emsg = "invalid pdel (>=0 expected)"

            if port == 'V':
                if port_n.isnumeric():
                    port_str = "%s%s%s" % (port, port_n, sel)
                else:
                    emsg = "invalid port number (0:9 expected)"
                    valid = False
            elif port == 'P':
                if (port_n.isnumeric() and int(port_n) <= 3) or port_n == '*':
                    port_str = "%s%s" % (port, port_n)
                else:
                    emsg = "invalid port number (0:3|* expected)"
                    valid = False
            elif port == 'A':
                if (port_n.isnumeric() and int(port_n) <= 2) or port_n == '*':
                    port_str = "%s%s" % (port, port_n)
                else:
                    emsg = "invalid port number (0:2|* expected)"
                    valid = False
            else:
                emsg = "invalid port (A|P|V expected)"
                valid = False

            if valid:
                if act == "PLH" or act == "PHL":
                        cmd_str += " %s:%s,%s,%s,%s" % (act,port_str,th,tl,cyc)
                if act == "SLH" or act == "SHL":
                    cmd_str += " %s:%s,%s,%s" % (act,port_str,th,tl)
                if act == "IOL" or act == "IOH":
                    cmd_str += " %s:%s" % (act,port_str)

                if pdel != "" and int(pdel) > 0:
                    cmd_str += ",%s" % (pdel)
                self.ser.send_cmd(cmd_str)
            cwg_log.debug("cmd:%s valid:%s emsg:%s", cmd_str, str(valid), emsg)
        except Exception as ex:
            cwg_log.exception("MDIORunIO - %s", str(ex))
        return

    def MDIORunPreset(self):
        if self.model.preset_dir.get():
            dir = '+'
        else:
            dir = '-'

        preset = self.view1.cbox_mdio_preset.get().upper()
        vh = self.view1.ent_mdio_ovr_vh.get().strip()
        ph = self.view1.ent_mdio_ovr_ph.get().strip()
        pl = self.view1.ent_mdio_ovr_pl.get().strip()
        pn = self.view1.ent_mdio_ovr_pn.get().strip()
        cwg_log.debug("MDIORunIO: preset:%s%s", dir, preset)

        valid = True

        cmd_str = "mdio"
        emsg = "OK"
        try:
            if preset == '':
                valid = False
                emsg = "no preset selected"
            elif preset not in self.view1.cbox_mdio_preset['values']:
                valid = False
                emsg = "invalid preset"

            if valid:
                if vh.isnumeric():
                    if int(vh) > 0:
                        cmd_str += " PT%s" % (vh)
                    else:
                        emsg += " WARN - invalid PT"
                if ph.isnumeric():
                    if int(ph) > 0:
                        cmd_str += " PH%s" % (ph)
                    else:
                        emsg += " WARN - invalid PH"
                if pl.isnumeric():
                    if int(pl) > 0:
                        cmd_str += " PL%s" % (pl)
                    else:
                        emsg += " WARN - invalid PL"
                if pn.isnumeric():
                    if int(pn) > 0:
                        cmd_str += " PN%s" % (pn)
                    else:
                        emsg += " WARN - invalid PN"
                cmd_str += " %s%s" % (dir, preset)
                self.ser.send_cmd(cmd_str)
            cwg_log.debug("cmd:%s valid:%s emsg:%s", cmd_str, str(valid), emsg)
        except Exception as ex:
            cwg_log.exception("MDIORunPreset - %s", str(ex))
        return

    def MDIORunMacro(self):
        macro = self.view1.cbox_mdio_macro.get().upper()
        vh = self.view1.ent_mdio_ovr_vh.get().strip()
        ph = self.view1.ent_mdio_ovr_ph.get().strip()
        pl = self.view1.ent_mdio_ovr_pl.get().strip()
        pn = self.view1.ent_mdio_ovr_pn.get().strip()

        cwg_log.debug("MDIORunMacro: macro:%s", macro)
        valid = True
        emsg = "OK"
        cmd_str = "mdio"
        try:
            if macro == '':
                valid = False
                emsg = "no macro selected"
            elif macro not in self.view1.cbox_mdio_macro['values']:
                valid = False
                emsg = "invalid macro"
            if valid:
                if vh.isnumeric():
                    if int(vh) > 0:
                        cmd_str += " MT%s" % (vh)
                    else:
                        emsg += " WARN - invalid MT"
                if ph.isnumeric():
                    if int(ph) > 0:
                        cmd_str += " MH%s" % (ph)
                    else:
                        emsg += " WARN - invalid MH"
                if pl.isnumeric():
                    if int(pl) > 0:
                        cmd_str += " ML%s" % (pl)
                    else:
                        emsg += " WARN - invalid ML"
                if pn.isnumeric():
                    if int(pn) > 0:
                        cmd_str += " MN%s" % (pn)
                    else:
                        emsg += " WARN - invalid MN"
                cmd_str += " %s" % (macro)
                self.ser.send_cmd(cmd_str)
            cwg_log.debug("cmd:%s valid:%s emsg:%s", cmd_str, str(valid), emsg)
        except Exception as ex:
            cwg_log.exception("MDIORunMacro - %s", str(ex))
        return

    def MMEASOvrStr(self, ovr):
        ovr_str = ""
        try:
            f = float(ovr['p'])
            if f >= 0.5:
                ovr_str += ",P%.1f" % (f)
        except ValueError:
            valid = False
            emsg = "invalid ovr P value"

        try:
            ovr_str += ",T%d" % (int(ovr['t']))
        except ValueError:
            valid = False
            emsg = "invalid ovr T value"

        try:
            ovr_str += ",N%d" % (int(ovr['n']))
        except ValueError:
            valid = False
            emsg = "invalid ovr N value"

        try:
            ovr_str += ",B%d" % (int(ovr['b']))
        except ValueError:
            valid = False
            emsg = "invalid ovr B value"

        try:
            ovr_str += ",W%d" % (int(ovr['w']))
        except ValueError:
            valid = False
            emsg = "invalid ovr W value"

        return ovr_str

    def mmeas_fo_str(self, options):
        out_str = ""
        fmt_str = ""
        if options['en']:
            out_str = options['id']
            if options['b']:
                fmt_str += "B"
            if options['c']:
                fmt_str += "C"
            if options['p']:
                fmt_str += "P"
            if options['x']:
                fmt_str += "X"
            if options['xx']:
                fmt_str += "XX"
            if options['xxx']:
                fmt_str += "XXX"
            if options['r']:
                fmt_str += "R"
            if options['d']:
                fmt_str += "d"
            if options['h']:
                fmt_str += "H"
            if options['v']:
                fmt_str += "V"
            if fmt_str:
                out_str += ":"+fmt_str
        return out_str

    def mmeas_outx_str(self, options):
        out_str = ""
        fmt_str = ""
        if options['cons'] or options['host'] or options['log'] or options['queue']:
            out_str = "OUT"+options['id'].upper()
            if options['cons']:
                fmt_str += "C"
            if options['host']:
                fmt_str += "H"
            if options['log']:
                fmt_str += "L"
            if options['queue']:
                fmt_str += "Q"

            if options['cons'] or options['host']:
                fmt_str += ","
                if options['b']:
                    fmt_str += "B"
                if options['c']:
                    fmt_str += "C"
                if options['p']:
                    fmt_str += "P"
                if options['x']:
                    fmt_str += "X"
                if options['xx']:
                    fmt_str += "XX"
                if options['xxx']:
                    fmt_str += "XXX"
                if options['r']:
                    fmt_str += "R"
                if options['d']:
                    fmt_str += "d"
                if options['h']:
                    fmt_str += "H"
                if options['v']:
                    fmt_str += "V"
            if fmt_str:
                out_str += ":"+fmt_str
        return out_str

    def MMEASRunTM(self):

        meas = self.view1.sbox_mmeas_tm.get()
        handler = self.view1.sbox_mmeas_handler.get()
        rmid = self.view1.sbox_mmeas_rmid.get()
        mmid = self.view1.sbox_mmeas_mmid.get()

        ovr = {\
        'p':self.view1.ent_mmeas_ovr_p.get().strip().upper(), \
        't':self.view1.ent_mmeas_ovr_t.get().strip().upper(), \
        'n':self.view1.ent_mmeas_ovr_n.get().strip().upper(), \
        'b':self.view1.ent_mmeas_ovr_b.get().strip().upper(), \
        'w':self.view1.ent_mmeas_ovr_w.get().strip().upper(), \
        'rmid':self.view1.sbox_mmeas_rmid.get().strip().upper()\
        }


        fo_log = self.view1.sbox_mmeas_fo_log.get()

        fo_cons = {\
        'en':self.view1.var_mmeas_fo_cons.get(),
        'id':'C',
        'c':self.view1.var_mmeas_fo_cfmt_c.get(), \
        'p':self.view1.var_mmeas_fo_cfmt_p.get(), \
        'b':self.view1.var_mmeas_fo_cfmt_b.get(), \
        'x':self.view1.var_mmeas_fo_cfmt_x.get(), \
        'xx':self.view1.var_mmeas_fo_cfmt_xx.get(), \
        'xxx':self.view1.var_mmeas_fo_cfmt_xxx.get(), \
        'h':self.view1.var_mmeas_fo_cfmt_h.get(), \
        'd':self.view1.var_mmeas_fo_cfmt_d.get(), \
        'r':self.view1.var_mmeas_fo_cfmt_r.get(), \
        'v':self.view1.var_mmeas_fo_cfmt_v.get()\
        }

        fo_host = {\
        'en':self.view1.var_mmeas_fo_host.get(),
        'id':'H',
        'c':self.view1.var_mmeas_fo_hfmt_c.get(), \
        'p':self.view1.var_mmeas_fo_hfmt_p.get(), \
        'b':self.view1.var_mmeas_fo_hfmt_b.get(), \
        'x':self.view1.var_mmeas_fo_hfmt_x.get(), \
        'xx':self.view1.var_mmeas_fo_hfmt_xx.get(), \
        'xxx':self.view1.var_mmeas_fo_hfmt_xxx.get(), \
        'h':self.view1.var_mmeas_fo_hfmt_h.get(), \
        'd':self.view1.var_mmeas_fo_hfmt_d.get(), \
        'r':self.view1.var_mmeas_fo_hfmt_r.get(), \
        'v':self.view1.var_mmeas_fo_hfmt_v.get()\
        }

        fz_log = self.view1.sbox_mmeas_fz_log.get()

        fz_outc = {\
        'id':"C",
        'log':self.view1.var_mmeas_fz_cbuf_l.get(), \
        'cons':self.view1.var_mmeas_fz_cbuf_c.get(), \
        'host':self.view1.var_mmeas_fz_cbuf_h.get(), \
        'queue':self.view1.var_mmeas_fz_cbuf_q.get(), \
        'c':self.view1.var_mmeas_fz_cfmt_c.get(), \
        'p':self.view1.var_mmeas_fz_cfmt_p.get(), \
        'b':self.view1.var_mmeas_fz_cfmt_b.get(), \
        'x':self.view1.var_mmeas_fz_cfmt_x.get(), \
        'xx':self.view1.var_mmeas_fz_cfmt_xx.get(), \
        'xxx':self.view1.var_mmeas_fz_cfmt_xxx.get(), \
        'h':self.view1.var_mmeas_fz_cfmt_h.get(), \
        'd':self.view1.var_mmeas_fz_cfmt_d.get(), \
        'r':self.view1.var_mmeas_fz_cfmt_r.get(), \
        'v':self.view1.var_mmeas_fz_cfmt_v.get()\
        }

        fz_outm = {\
        'id':"M",
        'log':self.view1.var_mmeas_fz_mbuf_l.get(), \
        'cons':self.view1.var_mmeas_fz_mbuf_c.get(), \
        'host':self.view1.var_mmeas_fz_mbuf_h.get(), \
        'queue':self.view1.var_mmeas_fz_mbuf_q.get(), \
        'c':self.view1.var_mmeas_fz_mfmt_c.get(), \
        'p':self.view1.var_mmeas_fz_mfmt_p.get(), \
        'b':self.view1.var_mmeas_fz_mfmt_b.get(), \
        'x':self.view1.var_mmeas_fz_mfmt_x.get(), \
        'xx':self.view1.var_mmeas_fz_mfmt_xx.get(), \
        'xxx':self.view1.var_mmeas_fz_mfmt_xxx.get(), \
        'h':self.view1.var_mmeas_fz_mfmt_h.get(), \
        'd':self.view1.var_mmeas_fz_mfmt_d.get(), \
        'r':self.view1.var_mmeas_fz_mfmt_r.get(), \
        'v':self.view1.var_mmeas_fz_mfmt_v.get()\
        }

        valid = True
        emsg = "OK"
        cmd_str = ""
        ovr_str = ""
        outm_str = ""
        outc_str = ""
        log_str = ""
        foc_str = ""
        foh_str = ""

        if meas == '' or not meas in self.view1.sbox_mmeas_tm['values']:
            valid=False
            emsg = "invalid meas: "+meas

        if not handler == '' and not handler in self.view1.sbox_mmeas_handler['values']:
            valid=False
            emsg = "invalid handler: "+handler

        if valid:
            cmd_str = "MMEAS"
            if rmid:
                cmd_str += " "+rmid
            if mmid:
                cmd_str += " "+mmid
            cmd_str += " TM:"+meas
            cmd_str += self.MMEASOvrStr(ovr)
            if handler:
                cmd_str += ","+handler
            if handler == "FO":
                if self.view1.sbox_mmeas_fo_log.get().strip().upper() == "OREC":
                    log_str = "LO"
                elif self.view1.sbox_mmeas_fo_log.get().strip().upper() == "RAW":
                    log_str = "LR"
                elif self.view1.sbox_mmeas_fo_log.get().strip().upper() == "*":
                    log_str = "L*"
                foc_str = self.mmeas_fo_str(fo_cons)
                foh_str = self.mmeas_fo_str(fo_host)
                if log_str:
                    cmd_str += ","+log_str
                if foc_str:
                    cmd_str += ","+foc_str
                if foh_str:
                    cmd_str += ","+foh_str

            elif handler == "FZ" or handler == '':
                if self.view1.sbox_mmeas_fz_log.get().strip().upper() == "OREC":
                    log_str = "LO"
                elif self.view1.sbox_mmeas_fz_log.get().strip().upper() == "RAW":
                    log_str = "LR"
                elif self.view1.sbox_mmeas_fz_log.get().strip().upper() == "*":
                    log_str = "L*"
                outm_str = self.mmeas_outx_str(fz_outm)
                outc_str = self.mmeas_outx_str(fz_outc)
                if log_str:
                    cmd_str += ","+log_str
                if outm_str:
                    cmd_str += " "+outm_str
                if outc_str:
                    cmd_str += " "+outc_str

#            self.ser.send_cmd(cmd_str)
        self.view1.var_mmeas_status.set("cmd:%s valid:%s emsg:%s" % (cmd_str, str(valid), emsg))
        cwg_log.debug("cmd:%s valid:%s emsg:%s", cmd_str, str(valid), emsg)

        return

    def MMEASRunCyc(self):

        rtid = self.view1.sbox_mmeas_cyc_rtid.get()
        rmid = self.view1.sbox_mmeas_cyc_rmid.get()
        cyc_op = {\
        'reset':self.view1.var_mmeas_cyc_rst.get(), \
        'start':self.view1.var_mmeas_cyc_start.get(), \
        'end':self.view1.var_mmeas_cyc_end.get(), \
        }

        cmd_str = ""
        opt_str = ""
        emsg = ""
        valid = True

        if cyc_op['start'] and cyc_op['end']:
            valid = False
            emsg = "start and end are mutually exclusive"

        if valid:
            if cyc_op['reset'] or cyc_op['start']:
                if cyc_op['reset']:
                    opt_str += "RST"
                if cyc_op['start']:
                    if opt_str:
                        opt_str += ","
                    opt_str += "START"
            if rtid:
                if opt_str:
                    opt_str += ","
                opt_str += rtid
            if rmid:
                if opt_str:
                    opt_str += ","
                opt_str += rmid
            if cyc_op['end']:
                if opt_str:
                    opt_str += ","
                opt_str += "END"

            if opt_str:
                cmd_str = "CYC:"+opt_str
#                self.ser.send_cmd(cmd_str)

        self.view1.var_mmeas_status.set("cmd:%s valid:%s emsg:%s" % (cmd_str, str(valid), emsg))
        cwg_log.debug("cmd:%s valid:%s emsg:%s", cmd_str, str(valid), emsg)
        return
