import sys
import serial
import os
import argparse
import itertools
import serial.tools.list_ports


def devices():
    while True:
        foundPorts = serial.tools.list_ports.comports()
        ComPort = []
        Ch = []
        ComName = []
        
        for port in foundPorts:
            if "2A19" == port.hwid[12:16]:
                ComPort.append(port[0])
                Ch.append(int(port[1].split('-')[3].split(' ')[0])) if(port[1].find('-')>0) else Ch.append(int(port[1].split(' ')[2]))
                ComName.append(port[1][:port[1].find('(')-1]) if(port[1].find('(')) else ComName.append(port[1].split('-')[1])
        return ComPort,Ch,ComName
            

class Relay(object):
    def __init__(self, *, numberRelays=1, port="COM1", baudrate=921600, timeout=1, name):
        self.version = 0
        self.numberRelays= numberRelays
        self.stateRelays=[0]*numberRelays
        self.port=port
        self.name = name
        self.baudrate=baudrate
        self.timeout=timeout
        if(self.port == '/dev/ttyUSB1'):
            print("Please Enter Port argument....")
            exit(0)
        response =self.send_cmd(cmd="ver\r",noResponse= False, sizeResponse=25)
        self.version = response[5:-3]
        response =self.send_cmd(cmd="id get\r",noResponse= False, sizeResponse=25)
        self.id = response[8:-3]
        

    def send_cmd(self, cmd, *, noResponse=True, sizeResponse=0):
        #Open port for communication
        serPort = serial.Serial(port=self.port, baudrate=self.baudrate, timeout=self.timeout)

        # Send command
        serPort.write(cmd.encode())

        # Get response command
        if not(noResponse):
            ret = serPort.read(sizeResponse)
            response = ret.decode('UTF-8')

        #Close the port
        serPort.close()

        if not(noResponse):
            return response


    ##--------- Device Info Functions ---------##
        
    def ver(self):
        """ Return the device's version number as string value. """
        cmd="ver\r"
        response = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=25)
        version = response[len(cmd)+1:-3]
        return version
    
    def id_get(self):
        """ Return the device id as string value. """
        cmd="id get\r"
        response = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=25)
        Id = response[len(cmd)+1:-3]
        return Id

    def id_set(self, cmdId="xxxxxxxx"):
        """ Re-program the device id to the value in new_id. """
        if(len(cmdId)!=8):
            print("Device ID must be 8 characters in length\n")
            return
        cmd="id set {}\r".format(cmdId)
        response = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=25)
        
    def info(self):
        """ Return the Power-On Status """
        cmd="info\r"
        response = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=105)
        info = response[len(cmd)+1:-3]
        return info


    ##---------- Reset all Relays ---------##
        
    def reset(self):
        """ "Reset all relays """
        cmd="reset\r"
        self.send_cmd(cmd=cmd)


    ##--------- Functions for Single Relay ---------##
        
    def relay_on(self, indexRelay):
        if(self.numberRelays == 64):
            indexRelay = str(indexRelay).zfill(2)
        cmd="relay on "+ str(indexRelay) + "\r"
        self.send_cmd(cmd=cmd)

    def relay_off(self, indexRelay):
        if(self.numberRelays == 64):
            indexRelay = str(indexRelay).zfill(2)
        cmd="relay off "+ str(indexRelay) + "\r"
        self.send_cmd(cmd=cmd)

    def relay_read(self, indexRelay):
        if(self.numberRelays == 64):
            indexRelay = str(indexRelay).zfill(2)
        cmd="relay read "+str(indexRelay) + "\r"
        ret = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=50)
        post = ret.find("\n\r")
        if(self.numberRelays == 64):
            post = ret.find("\r\n")
        post1 = ret.find(">")
        if(post1>0):
            return ret[post+2:post1-2]


    
    ##--------- Functions for all Relay ---------##
            
    def relay_readall(self):
        if(self.numberRelays==1):
            return
        elif(self.numberRelays<=4 and (self.name!="powered" and self.name!="ssr")):
           return
        else:
            cmd="relay readall\r"
            response = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=50)
            return response[len(cmd)+1:-3]


    def relay_writeall(self,cmdRelay= "xxxx"):
        if(self.numberRelays==1):
           return
        elif(self.numberRelays<=4 and (self.name!="powered" and self.name!="ssr")):
            return
        elif((len(cmdRelay) == (self.numberRelays/4)) or (len(cmdRelay)==2 and cmdRelay[0]=="0")) :
            for i in range(0,len(cmdRelay)):
                int(cmdRelay[i],16)
            cmd="relay writeall {}\r".format(cmdRelay)
            response = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=50)        


    def relay_update(self,cmdRelay= "xxxxxxxx"):
        if(len(cmdRelay) > self.numberRelays):
            return
        
        for idxRelay in range(len(cmdRelay)):
            if(cmdRelay[idxRelay] !="o" and cmdRelay[idxRelay] !="c" and cmdRelay[idxRelay] !="x"):
                return
            
        for idxRelay in range(len(cmdRelay)):
            num=int(str(idxRelay),10)
            if(self.name == "USBR"):
                num = str(idxRelay).zfill(3)
            elif(idxRelay>=10 and self.numberRelays != 64):
                num=chr(num+55)
            elif(self.numberRelays == 64):
                num = str(idxRelay).zfill(2)
                
            if cmdRelay[idxRelay] == 'o':
                self.relay_on(num)
            elif cmdRelay[idxRelay] == 'c':
                self.relay_off(num)
        


    ##--------- Gpio & ADC Functions ---------##
                
    def gpio_set(self, indexGpio):
        cmd="gpio set "+ str(indexGpio) + "\r"
        self.send_cmd(cmd=cmd)

    def gpio_clear(self, indexGpio):
        cmd="gpio clear "+ str(indexGpio) + "\r"
        self.send_cmd(cmd=cmd)

    def gpio_read(self, indexGpio):
        if(self.name == "USBR" and indexGpio[2]<"4"):
            return
        cmd="gpio read "+ str(indexGpio) + "\r"
        response=self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=25)
        #print(response)
        post = response.find("\n\r")
        post1 = response.find(">")
        if(post1>0):
            return response[post+2:post1-2]
        

    def gpio_status(self, indexGpio):
        if(self.name=="USBR" and indexGpio[0]=="0" and indexGpio[1]=="0"):
            cmd="gpio status "+str(indexGpio)+"\r"
            response = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=25)
            post = response.find("\n\r")
            post1 = response.find(">")
            if(post1>0):
                return response[post+2:post1-2]
        
    def adc_read(self, indexAdc):
        if(self.name=="USBR" and (len(indexAdc)!=3 or indexAdc[0]!="0" or indexAdc[1]!="0" or indexAdc[2]>"2")):
            return
        cmd="adc read "+ str(indexAdc) + "\r"
        response= self.send_cmd(cmd=cmd,noResponse= False, sizeResponse=30)
        #print(response)
        post = response.find("\n\r")
        post1 = response.find(">")
        if(post1>0):
            return response[post+2:post1-2]
        


    ##---------- Power-On Functions ---------##
        
    def relay_poweron(self, cmdRelay="xx"):
        if(self.numberRelays==1 and (cmdRelay[0]!='0' or cmdRelay[1]>'1')):
            return
        elif((len(cmdRelay) == (self.numberRelays/4)) or (len(cmdRelay)==2 and cmdRelay[0]=="0")):
            for i in range(0,len(cmdRelay)):
                int(cmdRelay[i],16)
            cmd="relay poweron {}\r".format(cmdRelay)
            response = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=50)
            

    def gpio_poweron(self, cmdGpioDir="xxx", cmdGpioVal="xxx"):
        if(self.numberRelays == 8):
            return
        elif((self.numberRelays == 16) and (len(cmdGpioDir)!=3 or len(cmdGpioVal)!=3 or cmdGpioDir[0]>"3" or cmdGpioVal[0]>"3")):
            return
        elif((self.name=="powered" or self.name=="ssr") and (cmdGpioDir[0]!='0' or cmdGpioVal[0]!='0')):
            return
        elif(self.name=="USBR" and cmdGpioDir[0]!="0"):  #For USBR CmdGpioDir is CmdGpioVal
            return
        elif((len(cmdGpioDir)<2 or len(cmdGpioDir)>3) or (len(cmdGpioVal)<2 or len(cmdGpioVal)>3)):
            return
        else:
            if(self.name=="USBR"):
                int(cmdGpioDir[1],16)
                cmd="gpio poweron {}\r".format(cmdGpioDir)
            else:
                for i in range(0,len(cmdGpioDir)):
                    int(cmdGpioDir[i],16)
                    int(cmdGpioVal[i],16)
                cmd="gpio poweron {} {}\r".format(cmdGpioDir, cmdGpioVal)
            response = self.send_cmd(cmd=cmd, noResponse= False, sizeResponse=50)


    ##--------- Device Information ---------##
    def dev_info(self):
        """Return human readable string of the device's curent state."""
        return (
            "dev: {} | id: {} | ver: {} | ports: {} | eol: {}".format(
                self.port,
                self.id,
                self.version,
                self.numberRelays,
                repr("\r")
            )
        )
                
        
def main(args=None):
    scriptname = os.path.basename(__file__)
    parser = argparse.ArgumentParser(scriptname)
    parser.add_argument('--relays', default=1, type=int)
    parser.add_argument('--port', default="/dev/ttyUSB1", type=str)
    parser.add_argument('--timeout', default=1, type=int)
    parser.add_argument('--baudrate', default=921600, type=int)
    parser.add_argument('--cmd', default="x", type=str)
    parser.add_argument('--name', default="usb", type=str)

    options = parser.parse_args()
    (port, ch, name)= devices()
    length=len(port)

    if(options.cmd=="x"):
        print("\nDiscovered devices:\n")
        
    for (port, ch, name) in itertools.zip_longest(port,ch,name):
        if((int(ch)==4 or int(ch)==2 or int(ch)==1) and (name.find("Powered")==25)):
            options.name = "powered"
        elif((int(ch)==4 or int(ch)==2 or int(ch)==1) and (name.find("SSR")==25)):
            options.name = "ssr"
        elif(int(ch)==1 and (name.find("USBR")==14)):
            options.name = "USBR"

        if(options.cmd=="x"):
            numatoRelay= Relay(numberRelays=ch, port=port, baudrate=options.baudrate, timeout=options.timeout, name = options.name)
            print(numatoRelay.dev_info())
            
            
    if((options.cmd!="x" and length>1) or options.port!="/dev/ttyUSB1" or (options.cmd!="x" and length==1)):
        if(options.cmd!="x" and length==1):
            options.port=port
            options.relays=ch
        module = Relay(numberRelays=options.relays, port=options.port, baudrate=options.baudrate, timeout=options.timeout, name = options.name)
        module.relay_update(cmdRelay=options.cmd)

    return 0

if __name__=="__main__":
    ret=main()
    os._exit(ret)
