###########################################################################
# EyeRISUDPCLIent.py
#
# Monterey Bay Aquarium Research Institute   2019
# All rights reserved.
#
# Command-line Interface (CLI) client app for the EyeRIS controller.
# Intended to be a temporary client app for the controller while a
# more user-friendly GUI app is developed.
# A number of parameters are hard-coded and intended to be changed
# by the user:
#
# NLights  - currently set to two. Will eventually grow to six.
# UDP_IP   - currently set to 192.168.1.199. Must match controller IP.
# UDP_PORT - currently set to 1111. Must match controller port.
#
# Created:  May    2019      Paul Roberts
# Modified: Jul 26 2019      Rich Henthorn - Initial functionality/formats
#
###########################################################################

import time
import socket
from kbhit import KBHit
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
import numpy as np
import os
import lcm
from exlcm import total_focus_image
from exlcm import recorder_status
from scipy import ndimage
#import cv2

## Global variables for the App

pg.mkQApp()


## Stylesheets
styleNormalProg = 'QProgressBar {border: 2px solid grey;border-radius: 5px;text-align: center;font: 75 12pt "MS Shell Dlg 2"; } QProgressBar::chunk { background-color: #adcdff;}"'
styleWarnProg = 'QProgressBar {border: 2px solid grey;border-radius: 5px;text-align: center;font: 75 12pt "MS Shell Dlg 2"; } QProgressBar::chunk { background-color: #f5c842;}"'
styleErrProg = 'QProgressBar {border: 2px solid grey;border-radius: 5px;text-align: center;font: 75 12pt "MS Shell Dlg 2"; } QProgressBar::chunk { background-color: #f54242;}"'


## Define main window class from template
path = os.path.dirname(os.path.abspath(__file__))
uiFile = os.path.join(path, 'EyeRISCameraUI.ui')
WindowTemplate, TemplateBaseClass = pg.Qt.loadUiType(uiFile)

USE_GUI = True

# Global variables representing parsed EyeRIS data
eyeTS = eyeSW1 = eyeSW2 = eyeSW3 = eyeSW4 = eyeTEMP1 = eyeHUMID1 = eyePRESS1 = eyeTEMP2 = eyeHUMID2 = eyePRESS2 = eyeVOLT1 = eyeAMP1 = eyeVOLT2 = eyeAMP2 = '0'

eyeLevels = 0

WARN_TEMP = 35
ERROR_TEMP = 45

WARN_HUM = 20
ERROR_HUM = 40

# Light level request globals
Light_id = 0
L_id = None
NLights = 2
Levels = []
for i in range(0,NLights):
    Levels.append('-')

UDP_IP = "192.168.1.199"
UDP_PORT = 1111
STATUS_INTERVAL = 1500

# Open socket connection to controller
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
client_socket.settimeout(0.25)
addr = (UDP_IP, UDP_PORT)

# Create log file name
fname = time.ctime()
fname = fname.replace(' ', '_')
fname = fname.replace(':', '')
LOG_FILE = 'logs\\' + fname + '.log'

# send an interval command to start get things started
client_socket.sendto(("FFS" + str(STATUS_INTERVAL)).encode(), addr)

class SystemStatus(QtCore.QThread):

    # example data: Wed Aug  7 11:19:38 2019  SWITCHES:[ -  -  -  + ]  ENV 1:[ 23.53 C  !!48%!!  101 kpa ]  ENV 2:[ 23.5 C  !!47%!!  101 kpa ]  PWR 1:[ -0.002 V  0.038 A ]  PWR 2:[ 0.130 V  24.200 A ]  LIGHTS: [ 2 2 - - ]
    temp1Signal = QtCore.Signal(float)
    temp2Signal = QtCore.Signal(float)
    hum1Signal = QtCore.Signal(float)
    hum2Signal = QtCore.Signal(float)
    press1Signal = QtCore.Signal(float)
    press2Signal = QtCore.Signal(float)

    lightVoltage = QtCore.Signal(float)
    lightCurrent = QtCore.Signal(float)
    camVoltage = QtCore.Signal(float)
    camCurrent = QtCore.Signal(float)

    lightStateSignal = QtCore.Signal(bool)
    cameraStateSignal = QtCore.Signal(bool)

    lightLevelSignal = QtCore.Signal(int)



    def __init__(self):
    
        QtCore.QThread.__init__(self)
        self.temp1 = 0.0
        self.temp2 = 0.0
        self.hum1 = 0.0
        self.hum2 = 0.0
        self.press1 = 0.0
        self.press2 = 0.0
        self.switches = [0,0,0,0]
        self.power1 = [0.0,0.0]
        self.power2 = [0.0,0.0]

        self.lightsOn = False
        self.cameraOn = False
    
    def __del__(self):
        self.wait()

    def sendSwitchCmd(self,state):
        cmd = 'FFR'
        for i in range(0,4):
            if state[i] == 1:
                cmd = cmd + '1'
            else:
                cmd = cmd + '0'
        cmd = cmd + '\r\n'
        client_socket.sendto(cmd.encode(), addr)
        print(cmd)

    def setLightPower(selfself,power):
        cmd = "!000" + ":LOUT=" + str(power)
        cmd = cmd + '\r\n'
        client_socket.sendto(cmd.encode(), addr)

    def toggleLights(self):
        if self.lightsOn:
            print('Turning Lights Off')
            self.lightsOn = False
            self.sendSwitchCmd([self.cameraOn,False,False,self.lightsOn])

        else:
            print('Turning Lights ON')
            self.lightsOn = True
            self.sendSwitchCmd([self.cameraOn,False,False,self.lightsOn])

    def toggleCamera(self):
        if self.cameraOn:
            print('Turning Camera Off')
            self.cameraOn = False
            self.sendSwitchCmd([self.cameraOn,False,False,self.lightsOn])
        else:
            print('Turning Camera ON')
            self.cameraOn = True
            self.sendSwitchCmd([self.cameraOn,False,False,self.lightsOn])

    def run(self):
        # your logic here
        while self.isRunning:
    
            try:
                data, server = client_socket.recvfrom(1024)
                utf8_data = data.decode('utf-8').rstrip('\r\n')
                #print(utf8_data, end = '\r')
                parse_eyeris_string(utf8_data) ## print(utf8_data, end = '\r')

                # signal the UI components from the globals set in parse_eyeris_string
                self.temp1Signal.emit(int(float(eyeTEMP1)))
                self.temp2Signal.emit(int(float(eyeTEMP2)))
                self.hum1Signal.emit(int(float(eyeHUMID1)))
                self.hum2Signal.emit(int(float(eyeHUMID2)))
                self.press1Signal.emit(int(float(eyePRESS1)/1000.))
                self.press2Signal.emit(int(float(eyePRESS2)/1000.))

                self.lightVoltage.emit(int(float(eyeVOLT2)))
                self.lightCurrent.emit(int(float(eyeAMP2)*1000))
                self.camVoltage.emit(int(float(eyeVOLT1)))
                self.camCurrent.emit(int(float(eyeAMP1)*1000))

                self.cameraStateSignal.emit(eyeSW1 == "On")
                self.cameraOn = eyeSW1 == "On"
                self.lightStateSignal.emit(eyeSW4 == "On")
                self.lightsOn = eyeSW4 == "On"

                self.lightLevelSignal.emit(eyeLevels)

                with open(LOG_FILE,'a+') as f:
                    f.write(str(time.ctime()) + ' : ' + utf8_data + '\n')
                    #f.write(str(time.time()) + ' : ' + lreq + '\n')
            except socket.timeout:
                pass
                #print('REQUEST TIMED OUT')
            
            time.sleep(0.25)
            
class ImageHandler(QtCore.QThread):

    signal = QtCore.Signal(object)
    estimatedFPS = QtCore.Signal(float)
    estimatedDataRate = QtCore.Signal(float)
    fpsPlot = QtCore.Signal(object)

    # example data: Wed Aug  7 11:19:38 2019  SWITCHES:[ -  -  -  + ]  ENV 1:[ 23.53 C  !!48%!!  101 kpa ]  ENV 2:[ 23.5 C  !!47%!!  101 kpa ]  PWR 1:[ -0.002 V  0.038 A ]  PWR 2:[ 0.130 V  24.200 A ]  LIGHTS: [ 2 2 - - ]

    def __init__(self):
        QtCore.QThread.__init__(self)

        #self.imgs = []

        #for i in range(0,10):
        #    self.imgs.append((40*np.random.random((1024,1024))).astype(np.uint8))
        
        #self.imageIndex = 0

        # set LCM handling and messaging
        self.lc = lcm.LCM()
        self.tfSub = self.lc.subscribe("EYERISIMAGE", self.tfImageHandler)
        
        self.fpsLog = []
        self.fpsLogHistory = 512

        self.offset = 0

        self.lastTimestamp = 0

    
    def __del__(self):
        self.wait()

    def tfImageHandler(self,channel,data):
        msg = total_focus_image.decode(data)
        if msg.pixelType == 1:
            img = np.frombuffer(msg.data,dtype='B').reshape(msg.width,msg.height)
        else:
            img = np.frombuffer(msg.data, dtype='B').reshape(3,msg.width, msg.height).transpose()
        #img = cv2.GaussianBlur(img,(11,11),0)
        #img = img + self.offset
        #self.offset = (self.offset + 1) % 255
        self.signal.emit(img)

        print(msg.timestamp)

        if self.lastTimestamp != 0:
            delta_t = time.time()-self.lastTimestamp
            if delta_t > 0:
                fpsEst = 1.0/delta_t
                
                if (len(self.fpsLog)) > 10:
                    newVal = np.mean(self.fpsLog[-10:] + [fpsEst])
                else:
                    newVal = fpsEst
                if len(self.fpsLog) >= self.fpsLogHistory:
                    self.fpsLog = self.fpsLog[1:] + [newVal]
                    
                else:
                    self.fpsLog = self.fpsLog + [newVal]
                
                self.estimatedFPS.emit(newVal)
                self.estimatedDataRate.emit(msg.width*msg.height*newVal/1000000)
                
                self.fpsPlot.emit(self.fpsLog)

        self.lastTimestamp = time.time()

    def run(self):
        # your logic here
        while self.isRunning:
            self.lc.handle()

class MainWindow(TemplateBaseClass):  
    def __init__(self):
        TemplateBaseClass.__init__(self)
        self.setWindowTitle('pyqtgraph example: Qt Designer')
        
        self.recording = False
        self.rawImageDisplayScale = 255
        
        self.imgs = []
        
        self.simulate = True
        
        # Create the main window
        self.ui = WindowTemplate()
        self.ui.setupUi(self)
        self.ui.recordButton.clicked.connect(self.record)
        self.ui.cameraOnButton.clicked.connect(self.toggleCamera)
        self.ui.lightsOnButton.clicked.connect(self.toggleLights)
        self.ui.setLightPower.clicked.connect(self.setLightPower)

        # sensor monitors
        self.ui.temp1.valueChanged.connect(self.checkTemp1)
        #self.ui.temp2.valueChanged.connect(self.checkTemp2)
        #self.ui.hum1.valueChanged.connect(self.checkHum1)
        #self.ui.hum2.valueChanged.connect(self.checkHum2)

        # System status thread
        self.system_status = SystemStatus()
        self.system_status.temp1Signal.connect(self.ui.temp1.setValue)
        self.system_status.temp2Signal.connect(self.ui.temp2.setValue)
        self.system_status.hum1Signal.connect(self.ui.hum1.setValue)
        self.system_status.hum2Signal.connect(self.ui.hum2.setValue)
        self.system_status.press1Signal.connect(self.ui.press1.setValue)
        self.system_status.press2Signal.connect(self.ui.press2.setValue)
        self.system_status.lightVoltage.connect(self.ui.lightVoltage.setValue)
        self.system_status.lightCurrent.connect(self.ui.lightCurrent.setValue)
        self.system_status.camVoltage.connect(self.ui.camVoltage.setValue)
        self.system_status.camCurrent.connect(self.ui.camCurrent.setValue)
        self.system_status.lightLevelSignal.connect(self.ui.lightPowerIndicator.setValue)
        self.system_status.cameraStateSignal.connect(self.setCameraState)
        self.system_status.lightStateSignal.connect(self.setLightState)
        self.system_status.start()

        # image input thread
        self.image_handler = ImageHandler()
        self.image_handler.signal.connect(self.image_update)
        self.image_handler.estimatedFPS.connect(self.ui.estimatedFPS.setValue)
        self.image_handler.estimatedDataRate.connect(self.ui.estimatedDataRate.setValue)
        self.image_handler.fpsPlot.connect(self.fpsPlotUpdate)
        self.image_handler.start()

        #self.imageUpdateTimer = QtCore.QTimer()
        #self.imageUpdateTimer.timeout.connect(self.imageUpdate)
        #self.imageUpdateTimer.start(13)
        
        self.ui.rawDisplayScale.valueChanged.connect(self.setScale)

        self.lc = lcm.LCM()

        self.recStat = recorder_status()

        self.recStat.recording = False

        
        vb = pg.ViewBox()
        self.ui.graphicsView.setCentralItem(vb)
        vb.setAspectLocked()
        self.rawimg = pg.ImageItem()
        vb.addItem(self.rawimg)
        vb.setRange(QtCore.QRectF(0, 0, 512, 512))
        
        self.show()
        
    def fpsPlotUpdate(self,data):
        self.ui.fpsPlot.plot(data,clear=True)

    def setLightPower(self):
        self.system_status.setLightPower(self.ui.lightPowerSlider.value())

    def checkTemp1(self):
        if WARN_TEMP <= self.ui.temp1.value() < ERROR_TEMP:
            self.ui.temp1.setStyleSheet(styleWarnProg)
        elif ERROR_TEMP <= self.ui.temp1.value():
            self.ui.temp1.setStyleSheet(styleErrProg)
        else:
            self.ui.temp1.setStyleSheet(styleNormalProg)

    def setLightState(self,lightsOn):
        if lightsOn:
            self.ui.lightsOnButton.setStyleSheet("background-color: red")
        else:
            self.ui.lightsOnButton.setStyleSheet("")

    def setCameraState(self,cameraOn):
        if cameraOn:
            self.ui.cameraOnButton.setStyleSheet("background-color: red")
        else:
            self.ui.cameraOnButton.setStyleSheet("")

    def toggleLights(self):
        if self.system_status.lightsOn:
            choice = QtGui.QMessageBox.question(self, 'Confirm Lights',
                                                "Turn OFF power to Lights?",
                                                QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
            if choice == QtGui.QMessageBox.Yes:
                self.system_status.toggleLights()
        else:
            choice = QtGui.QMessageBox.question(self, 'Confirm Lights',
                                                 "Turn ON power to Lights?",
                                                 QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
            if choice == QtGui.QMessageBox.Yes:
                self.system_status.toggleLights()
    def toggleCamera(self):
        if self.system_status.cameraOn:
            choice = QtGui.QMessageBox.question(self, 'Confirm Lights',
                                                "Turn OFF power to Camera?",
                                                QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
            if choice == QtGui.QMessageBox.Yes:
                self.system_status.toggleCamera()
        else:
            choice = QtGui.QMessageBox.question(self, 'Confirm Lights',
                                                 "Turn ON power to Camera?",
                                                 QtGui.QMessageBox.Yes | QtGui.QMessageBox.No)
            if choice == QtGui.QMessageBox.Yes:
                self.system_status.toggleCamera()
        
    def record(self):
        if self.recording:
            print('Stopping recording.')
            self.recording = False
            self.recStat.recording = False
            self.lc.publish("RECSTAT",self.recStat.encode())
            self.ui.recordButton.setStyleSheet("")
        else:
            print('Starting recording.')
            self.recording = True
            self.recStat.recording = True
            self.lc.publish("RECSTAT", self.recStat.encode())
            self.ui.recordButton.setStyleSheet("background-color: red")
            
    def setScale(self):
        self.rawImageDisplayScale = self.ui.rawDisplayScale.value()
        # print(self.rawImageDisplayScale)

    def image_update(self, img):
        scale = [0,self.rawImageDisplayScale]
        self.rawimg.setImage(img,autoLevels=False, levels=scale, autoDownsample=False)
        
        
        

###########################################################################
# parse_eyeris_string() function.
# Parse the EYERIS string and set new variable values
#
def parse_eyeris_string(eyeris_str):

    global NLights
    global L_id
    global Light_id
    global Levels

    # Split the string by commas
    comp = eyeris_str.split(",")

    # Gotta be at least components
    if (len(comp) < 2):
        return

    # EYERIS data packets begin with '$EYERIS'
    if (comp[0] == '$EYERIS'):

        if (len(comp) < 16):
            print("Invalid EYERIS string: " + eyeris_str)
            return

        global eyeTS
        eyeTS = comp[1]
        
        global eyeSW1
        eyeSW1 = comp[2]
        global eyeSW2
        eyeSW2 = comp[3]
        global eyeSW3
        eyeSW3 = comp[4]
        global eyeSW4
        eyeSW4 = comp[5]

        global eyeTEMP1
        eyeTEMP1  = comp[6]
        global eyePRESS1
        eyePRESS1 = comp[7]
        global eyeHUMID1
        eyeHUMID1 = comp[8]
        
        global eyeTEMP2
        eyeTEMP2  = comp[9]
        global eyePRESS2
        eyePRESS2 = comp[10]
        global eyeHUMID2
        eyeHUMID2 = comp[11]

        global eyeAMP1
        eyeAMP1  = comp[12]
        global eyeVOLT1 
        eyeVOLT1 = comp[13]

        global eyeAMP2
        eyeAMP2  = comp[14]
        global eyeVOLT2
        eyeVOLT2 = comp[15]


        display_eyeris_data()

        # Request light levels. Sequence through the number of lights
        # Bit of a hack, but the level values are read the next time
        # this function is called - see below
        #
        L_id = Light_id % NLights
        Light_id = Light_id + 1
        if (Light_id == NLights): Light_id = 0
        lreq = "!00" + str(L_id+1) + ":LOUT?"
        client_socket.sendto(lreq.encode(), addr)

    # Light level requests are read here and stored in the levels array
    #
    elif (comp[0] == '$RS485'):

        if (len(comp) < 2):
            print("Unknown RS485 string: " + eyeris_str)
            return

        if (len(comp) == 2):
            Levels[L_id] = str(comp[1])
            if Light_id == NLights-1:
                global eyeLevels
                eyeLevels = 0.0
                for i in range(0,NLights):
                    if Levels[i] != '-':
                        eyeLevels = eyeLevels + int(Levels[i])
                eyeLevels = eyeLevels/NLights
                print(eyeLevels)

# end parse_eyeris_string()        
###########################################################################


###########################################################################
# adorn_pressure() function.
# Int pressure value kPa passed in
# String returned with possible adornments (e.g. "!!" to indicate warning level)
#
def adorn_pressure(pressure_val):
    # Set limits and adorn if limits are exceeded
    PLLIMIT = 80
    PHLIMIT = 120
    adorn = "!!" if (pressure_val < PLLIMIT or pressure_val > PHLIMIT) else ""
    rval = adorn + str(pressure_val) + " kpa" + adorn
    return rval

# end adorn_pressure()        
###########################################################################


###########################################################################
# adorn_temperature() function
# Float temperature value C passed in
# String returned with possible adornments (e.g. "!!" to indicate warning level)
#
def adorn_temperature(temp_val):
    # Set limit and adorn if limit is exceeded
    TLIMIT = 40
    adorn = "!!" if (temp_val > TLIMIT) else ""
    rval = adorn + str(temp_val) + " C" + adorn
    return rval

# end adorn_temperature()        
###########################################################################


###########################################################################
# adorn_humidity()        
# Int humidity value % passed in
# String returned with possible adornments (e.g. "!!" to indicate warning level)
#
def adorn_humidity(humid_val):
    # Set limit and adorn if limit is exceeded
    HLIMIT = 35
    adorn = "!!" if (humid_val > HLIMIT) else ""
    rval = adorn + str(humid_val) + "%" + adorn
    return rval

# end adorn_humidity()        
###########################################################################


###########################################################################
# display_eyeris_data()
# Display the contents of the latest data from the Eyeris to a terminal.
# Also triggers requests for light level data to be read next iteration.
#
def display_eyeris_data():

    # Start with a timestamp
    print(time.ctime(), end='  ')

    # Display switch state on/off as +/-
    s1 = "+" if (eyeSW1 == "On") else "-"
    s2 = "+" if (eyeSW2 == "On") else "-"
    s3 = "+" if (eyeSW3 == "On") else "-"
    s4 = "+" if (eyeSW4 == "On") else "-"
    switches = "SWITCHES:[ " + s1 + "  " + s2 + "  " + s3 + "  " + s4 + " ]"
    print(switches, end='  ')

    # Environmental sensor 1
    temp = float(eyeTEMP1)
    kpa  = int(float(eyePRESS1)/1000.)
    rh   = int(float(eyeHUMID1))
    bme  = "ENV 1:[ " + adorn_temperature(temp) + "  " + adorn_humidity(rh) + "  " + adorn_pressure(kpa) + " ]"
    print(bme, end='  ')

    # Environmental sensor 2
    temp = float(eyeTEMP2   )
    kpa  = int(float(eyePRESS2)/1000.)
    rh   = int(float(eyeHUMID2))
    bme  = "ENV 2:[ " + adorn_temperature(temp) + "  " + adorn_humidity(rh) + "  " + adorn_pressure(kpa) + " ]"
    print(bme, end='  ')

    # Both power sensors
    pwr = "PWR 1:[ " + eyeVOLT1 + " V  " + eyeAMP1 + " A ]  PWR 2:[ " + eyeVOLT2 + " V  " + eyeAMP2 + " A ]"
    print(pwr, end='  ')

    # Light levels returned from light level requests in parse_eyeris_string()
    lights = "LIGHTS: [ "
    for i in range(0,NLights):
        lights = lights + Levels[i] + " "
    lights = lights + "]"
    print(lights)

# end display_eyeris_data()
###########################################################################


###########################################################################
# switch_menu()
# Get the light switch command.
# Returns "none" on invalid input (e.g., no input)
# Otherwies, returns a valid command string (e.g., "FFR0011") that can be
# sent to the controller.
#
def switch_menu():
    rval = "none"

    switches = ["0", "0", "0", "0"]

    switches[0] = "1" if (eyeSW1 == 'On') else "0"
    switches[1] = "1" if (eyeSW2 == 'On') else "0"
    switches[2] = "1" if (eyeSW3 == 'On') else "0"
    switches[3] = "1" if (eyeSW4 == 'On') else "0"

    l_id = input("Switch number (1=>Camera 4=>Lights) [1..4] > ")
    if (len(l_id) <= 0):
        return rval

    try:
        i = int(l_id)
        if (i < 1 or i > 4):
            return rval
        else:
            l_id = str(i)

    except ValueError:
        return rval

    val  = input("O or 1 > ")
    if (len(val) <= 0):
        return rval

    if (val != "1"):
        val = "0"

    # Set the switch for the command string
    switches[i-1] = val

    # Build the command string
    rval = "FFR" + switches[0] + switches[1] + switches[2] + switches[3]
    return rval

# end switch_menu()
###########################################################################

###########################################################################
# light_menu()
# Get the light level command.
# Returns "none" on invalid input (e.g., no input)
# Otherwies, returns a valid command string (e.g., "!002:LOUT=30") that can be
# sent to the controller.
#
def light_menu():
    rval = "none"

    # Get the light id
    l_id = input("Light number [1..2] (0 for all) > ")
    if (len(l_id) < 0):
        return rval

    # Must be an int
    try:
        i = int(l_id)
        if (i < 0 or i > 2):
            return rval
        else:
            l_id = str(i)     # This handles blanks in user input (e.g., " 1 ")

    except ValueError:
        return rval

    # Get light level (0 to 100)
    val  = input("Intensity Level [O..100] > ")
    if (len(val) <= 0):
        return rval

    try:
        i = int(val)
        # Enforce 0 to 100 limit
        if (i < 0):
            i = 0

        if (i > 100):
            i = 100

        val = str(i)           # Handle blanks in user input (e.g., "   20 ")

    except ValueError:
        return rval

    # Build the command string
    rval = "!00" + l_id + ":LOUT=" + val
    return rval

# end light_menu()
###########################################################################


###########################################################################
# main_menu()
# Get the controller command.
# Returns "none" on invalid input (e.g., no input)
# Otherwies, returns a valid command string (e.g., "!002:LOUT=30") that can be
# sent to the controller.
#
def main_menu():
    global kb
    rval = "none"

    cmd = input('(A)ll off  (S)witches  (L)ight Levels > ')

    if (len(cmd) < 1):                        # User just pressed enter
        rval = "none"

    elif (cmd[0] == 'a' or cmd[0] == 'A'):    # All components off
        rval = "FFR0000"

    elif (cmd[0] == 's' or cmd[0] == 'S'):    # Toggle a switch
        rval = switch_menu()

    elif (cmd[0] == 'l' or cmd[0] == 'L'):    # Set light levels
        rval = light_menu()

    return rval

# end main_menu()
###########################################################################


###########################################################################
# command_menu()
# Get a command from the main menu.
# Returns "none" if no valid command is provided.
# Otherwies, returns the command string (e.g., "!002:LOUT=30") that can be
# sent to the controller.
#
def command_menu():
    cmd = main_menu()
    print(cmd)
    return cmd

# end command_menu()
###########################################################################


###########################################################################
#  Main program
###########################################################################

win = MainWindow()

## Start Qt event loop unless running in interactive mode or using pyside.
if __name__ == '__main__':
    import sys
    
    if USE_GUI:
        if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
            QtGui.QApplication.instance().exec_()
    else:

        # Banner

        print("###########################################################################")
        print("")
        print("                 EyeRIS UDP Command-line CLIent")
        print("            Monterey Bay Aquarium Research Institute   2019")
        print("")
        print("            - Stretch window width for better viewing")
        print("")
        print("            - ESC to exit")
        print("")
        print("            - ENTER to get the command menu")
        print("")
        print("            - Any other key to enter a direct controller command")
        print("")
        print("            - ESC to exit")
        print("")
        print("            - Log file: " + LOG_FILE)
        print("")
        print("###########################################################################")

        kb = KBHit()
        while True:

            kb.set_new_term()

            # check for user input
            if kb.kbhit():
                c = kb.getch()
                kb.set_normal_term()
                if (ord(c) == 27): # ESC
                    # turn off status messages before exit
                    client_socket.sendto("FFS0".encode(), addr)
                    print("")
                    break
                elif (ord(c) == 10 or ord(c) == 13): # CR
                    print("\r\nGetting menu command...")
                    cmd = command_menu()
                    if (cmd != 'none'):    # Not a null command
                        client_socket.sendto(cmd.encode(), addr)

                else:
                    cmd = input("\r\nCMD: ")
                    client_socket.sendto(cmd.encode(), addr)

                print("")
                print("")


            try:
                data, server = client_socket.recvfrom(1024)
                utf8_data = data.decode('utf-8').rstrip('\r\n')

                parse_eyeris_string(utf8_data) ## print(utf8_data, end = '\r')

                with open(LOG_FILE,'a+') as f:
                    f.write(str(time.ctime()) + ' : ' + utf8_data + '\n')
                    #f.write(str(time.time()) + ' : ' + lreq + '\n')
            except socket.timeout:
                pass
                #print('REQUEST TIMED OUT')

            time.sleep(0.200)

        # end main
        ###########################################################################
