import serial
import serial.tools.list_ports
import sys
import getpass
import os

from loguru import logger
from time import sleep


class SCPI(object):

    def __init__(self, port, timeout=0.2, stop=1, baudrate=9600, reset=True):
        # open serial port
        self.com = serial.Serial(port, timeout=timeout, stopbits=stop, baudrate=baudrate)
        self.BUFFER_LEN = 1024
        self.remotcmd = 'SYST:REM'  # remote mode
        self.psstscmd = 'OUTP?'
        self.restcmd = '*RST'  # do not reset, default connection method is USB !!!
        self.clearcmd = '*CLS'  # clear error status
        if reset:
            self.reset()
        if self.get_result(self.psstscmd) != '':
            logger.info('power type is IT6132')
            self.power_type = 'IT6132'
            self.psoncmd = 'OUTP 1'
            self.psoffcmd = 'OUTP 0'
            self.errcmd = 'SYST: ERR?'
            self.getvolcmd = 'MEASure:VOLT?'
            self.getcurcmd = 'MEASure:CURRent?'
            self.setvolcmd = 'VOLT '
            self.setcurcmd = 'CURR '
        else:
            logger.info('power type is KA3005P')
            self.power_type = 'KA3005'
            self.psoncmd = 'OUT1'
            self.psoffcmd = 'OUT0'
            self.getsncmd = '*IDN?'
            self.errcmd = 'SYST: ERR?'
            self.getvolcmd = 'VOUT1?'
            self.getcurcmd = 'IOUT1?'
            self.setvolcmd = 'VSET1:'
            self.setcurcmd = 'ISET1:'

    def send(self, cmd):
        if not cmd.endswith("\n"):
            cmd += "\n"
        return self.com.write(cmd.encode())

    def get_result(self, cmd, decode=True):
        self.send(cmd)
        echo = self.com.read(self.BUFFER_LEN)
        if decode:
            try:
                # try to decode to utf8
                echo = echo.decode()
                # try to decode to float
                echo = float(echo)
            except:
                pass
        return echo

    def reset(self):
        logger.info('reset connection with power')
        # self.send(restcmd)
        # sleep(1)
        self.send(self.clearcmd)
        sleep(1)
        self.send(self.remotcmd)
        sleep(1)
        logger.info('Power connect')

    def close(self):
        return self.com.close()

    def power_on(self):
        self.send(self.psoncmd)
        logger.info('Set power ON')

    def power_off(self):
        self.send(self.psoffcmd)
        logger.info('Set power OFF')

    def get_powersts(self):
        if self.power_type == 'IT6132':
            sts = self.get_result(self.psstscmd)
            logger.info(sts)
            if sts == 1.0:
                logger.info('Power status is ON')
            else:
                logger.info('Power status is OFF')
            return sts
        elif self.power_type == 'KA3005':
            cur = self.get_result(self.getcurcmd)
            volt = self.get_result(self.getvolcmd)
            if cur and volt:
                if cur > 0.1 and volt > 0.5:
                    sts = True
                    logger.info('Power status is ON')
                else:
                    sts = False
                    logger.info('Power status is OFF')
            else:
                sts = False
                logger.info('Power status is OFF')
            return sts

    def set_voltage(self, V):
        self.send(self.setvolcmd + str(V))
        logger.info('Set voltage {}V'.format(V))

    def set_current(self, A):
        self.send(self.setcurcmd + str(A))
        logger.info('Set max current {}A'.format(A))

    def get_voltage(self):
        volt = self.get_result(self.getvolcmd)
        # logger.info("Power voltage is : {}V".format(volt))
        return volt

    def get_current(self):
        cur = self.get_result(self.getcurcmd)
        # comment below line, avoid logger.info too much logs during test
        # logger.info("Power current is : {}A".format(cur))
        return cur



class PowerDev(object):
    def __new__(cls, *args, **kwargs):
        if not hasattr(cls, '_instance'):
            cls._instance = super(PowerDev, cls).__new__(cls)
        return cls._instance


    def __init__(self):
        self.port_list = self.get_port()
        self.power = self.init_power_instance()

    def get_port(self):
        port_list = list(serial.tools.list_ports.comports())
        port_list_name = []
        if len(port_list) <= 0:
            port_list_name.append("Serial port can't find!")
            logger.info("Serial port can't find! Please check hardware connection")
        else:
            for each_port in port_list:
                port_list_name.append(each_port[0])
        logger.info("port list name: {}".format(port_list_name))
        return port_list_name

    def init_power_instance(self):
        powerdev = None
        if "Serial port can't find!" in self.port_list[0]:
            logger.info('Please check hardware connection')
        else:
            if 'linux' in sys.platform:
                for port in self.port_list:
                    if "ttyUSB" in port or "ttyACM" in port:
                        password = getpass.getuser()
                        command = f"sudo chmod 777 {port}"
                        os.system('echo %s | sudo -S %s' % (password, command))
                        sleep(3)
                        powerdev = SCPI(port)
                        break
            else:
                logger.info('windows system, use port 0')
                powerdev = SCPI(self.port_list[0])
        if not powerdev:
            raise "Power Init Fail"
        return powerdev

    def power_cycle(self, gap_time: int = 2, boot_time: int = 20):
        self.power.power_off()
        sleep(gap_time)
        self.power.power_on()
        sleep(boot_time)

    def current(self):
        return self.power.get_current()

    def voltage(self):
        return self.power.get_voltage()

    @property
    def status(self):
        return self.power.get_powersts()

    def switch(self):
        if self.status:
            return self.power.power_off()
        return self.power.power_on()

优化上面代码，通过with来调用方法