from usr.common import Singleton
import usr.global_config as config
import ql_fs
import net
import utime    # Import timing module
import dataCall
import sim
import modem
import checkNet
import usocket
import ussl
import ubinascii
import _thread

# - - - - - - - - - - - - - - - - - - - - - 
def netCallback(args):
    config.RUN_MODE_NET_STATUS = args[1]

# - - - - - - - - - - - - - - - - - - - - - - - - - - 
# Decodifica as mensagens de erro do sistema
#   msg:   "[Errno 11] EAGAIN"
#
# Retorna
#   code: 11
#   name: "EAGAIN" 
def getERRORCode(msg):
    try:
        int(msg)
        return -1, str(msg)
    except:
        try:
            list = str(msg).split(' ')
            code = list[1][0:-1]
            if int(code) == None:
                code = -1
            name = list[2]
            return code, name
        except:
            return -1, str(msg)

# - - - - - - - - - - - - - - - - - - - - - 
# define Python user-defined exceptions
class GeneralError(Exception):
    # print('Exception')
    """Base class for other exceptions"""
    def __init__(self, type, message):
        super().__init__(message)


class Cmq151RunMode(Singleton):
    '''
      Controla maquina de estado para o modo de operacao RUN (operação do Modem)
    '''
    # - - - - - - - - - - - - - - - - - - - - - 
    def __init__(self, com_driver, logger):
        self.comm       = com_driver
        self.logger     = logger
        self.state_handle  = None
        self.old_state_handle = None
        self.ssl_ca     = None
        self.ssl_pk     = None
        self.IMEI       = 0
        self.QFWV       = 0
        self.ICCID      = 0
        self.MY_IP      = 0
        self.OPERATOR   = 0
        self.NET_QLT    = 0
        self.ERROR      = config.RC_SUCCESS
        self.socket     = None   
        self.waitNetReadyRtry = 0
        self.next_state = None
        self.wait_count = 0 
        self.ssl_mode   = 0
        self.error_state_start_time = -1
        self.ssl_buf    = None
        self.ssl_data_available = False
        self.thread_id  = -1
        self.tread_running = False
        dataCall.setCallback(netCallback)
        
    # - - - - - - - - - - - - - - - - - - - - - 
    def startMode(self):
        # print('\nstartMode')
        if config.LOG_ON: self.logger.logMessage("modo RUN ativado")
        self.state_handle = self.run_get_static_info
        # define o status iniciais das saidas digitais do módulo
        config.DEV_IO_OBJ.updateModemStatus(config.MDST_HARD_FAULT)
        config.DEV_IO_OBJ.updateSignalQuality(config.SIGQ_UNDEF)
        # reinicializa erro
        self.ERROR = config.RC_SUCCESS
        # reinicializa contador de tempo de erro
        self.error_state_start_time = -1

        if config.USR_CONFIG_RET_CODE != config.RC_SUCCESS:
            self.ERROR = config.RC_RE_INVALID   
            self.map_to_modem_state(self.ERROR)

        # verifica flag de autenticação SSL = 0 ou 1
        try:
            self.ssl_mode = int(config.USR_CONFIG["SS"])
        except:
            self.ssl_mode = 0

        # validação de OP (url do servidor de atualização) e TO (porta do servidor de atualização)
        if config.LOG_ON: self.logger.logMessage("config.USR_CONFIG = {}".format(config.USR_CONFIG))
            
    # - - - - - - - - - - - - - - - - - - - - - 
    def stopMode(self):
        self.state_handle = self.run_get_static_info
        # define o status iniciais das saidas digitais do módulo
        config.DEV_IO_OBJ.updateModemStatus(config.MDST_HARD_FAULT)
        if config.LOG_ON: self.logger.logMessage("modo RUN desativado")

        #self.config = None
        if self.socket != None:
            self.socket.close()
            self.socket = None

        if self.thread_id != -1:
            _thread.stop_thread(self.thread_id) # Termina thread de recepção do socket
            self.thread_id = -1

    # - - - - - - - - - - - - - - - - - - - - - 
        '''
          Contabiliza o tempo definido na chamada do metodo wait_for_next_state() e
          ao final do mesmo transfere o controle para o estado especificado.
        '''

    # - - - - - - - - - - - - - - - - - - - - - 
    def run_wait_for_next_state(self):
        if self.wait_count <= 0:
            self.state_handle = self.next_state
            if config.LOG_ON: self.logger.logMessage("Fim do delay")  
        else:
            self.wait_count -= 1

    # - - - - - - - - - - - - - - - - - - - - - 
    def wait_for_next_state(self, next_state, ms_tmo):
        '''
          Contabiliza o tempo definido e ao final transfere o controle para o estado especificado.
          Este tempo é contabilizado sem travar o metodo, permitindo o controle retornar para o 
          programa principal.
        '''
        if config.LOG_ON: self.logger.logMessage("Ativado delay de {} ms".format(ms_tmo))  
        self.next_state = next_state
        self.wait_count = ms_tmo / 20 # Note que o valor 20 se refere ao utime.sleep(0.02) no loop principal da aplicação 
        self.state_handle = self.run_wait_for_next_state

    # - - - - - - - - - - - - - - - - - - - - -             
    def map_to_modem_state(self, ret_code):
        """
            0 : 'HARD_FAULT'
            1 : 'INVALID_CFG'
            2 : 'NO_SIM_CARD'
            3 : 'NO_NETWORK'
            4 : 'CEL_AUTH_FAIL'
            5 : 'SERV_ACS_ERR'
            6 : 'SERV_CONNECTED'
        """
        if   ret_code == config.RC_RE_INVALID:
            ret_code = config.MDST_INV_CFG

        elif   ret_code == config.CN_SIM_CARD_ERR:
            ret_code = config.MDST_NO_SIM_CARD

        elif ret_code == config.CN_NO_NET: 
            ret_code = config.MDST_NO_NETWORK

        elif ret_code == config.CN_REGISTER_DENIED or \
             ret_code == config.CN_INV_AUTH_DATA:
            ret_code = config.MDST_CEL_AUTH_FAIL

        elif ret_code == config.RC_CO_CONEX_FAIL: 
            ret_code = config.MDST_SERV_ACS_ERR

        elif ret_code == config.RC_SUCCESS: 
            ret_code = config.MDST_SERV_CONNECTED
        else:
            ret_code = config.MDST_HARD_FAULT
        
        # atualiza também a qualidade do sinal
        self.NET_QLT = net.csqQueryPoll()
        config.DEV_IO_OBJ.updateModemStatus(ret_code)
        config.DEV_IO_OBJ.updateSignalQuality(sig_qlt=self.NET_QLT)
 
        if config.LOG_ON: self.logger.logMessage('atualiza PIN STS = {} | CSQ = {}'.format(ret_code, self.NET_QLT))

    # - - - - - - - - - - - - - - - - - - - - - 
    def run_get_static_info(self):
        #print('\n..run_get_static_info')
        #lê a configuração de operação (tipo de rede): This method gets the current 
        #           network mode and roaming configuration.
        net_cfg, _ = net.getConfig()
        if config.LOG_ON: self.logger.logMessage('net_cfg = {}'.format(net_cfg))
        if net_cfg != config.NET_MODE_ONLY_LTE :
            # desabilita as funcionalidades do modem (para não se conectar na rede celular)
            # sem resetar o modem 
            net.setModemFun(0, 0) 
            # Aguarda tempo
            utime.sleep(1)
            # configura modo de operacao = ONLY LTE
            net.setConfig(config.NET_MODE_ONLY_LTE, True) 
            # establish the Cellular Network Channel Automatically at Startup
            dataCall.setAutoActivate(config.PDP_CONTEXT_ID, 1)
            # automatically reconnect when the module disconnects from the network
            dataCall.setAutoConnect(config.PDP_CONTEXT_ID, 1)
            # habilita as funcionalidades do modem e realiza reset.
            net.setModemFun(1, 1)  

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        # Adquire informações do MODEM
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -         
        self.IMEI  = modem.getDevImei()
        if config.LOG_ON: self.logger.logMessage('IMEI = {}'.format(self.IMEI))
        self.QFWV = modem.getDevFwVersion()
        if config.LOG_ON: self.logger.logMessage('Quectel FW Version = {}'.format(self.QFWV))

        self.state_handle = self.run_get_simcard_info
        
    # - - - - - - - - - - - - - - - - - - - - - 
    def run_get_simcard_info(self):
        #print('\n..run_get_simcard_info')
        if self.ERROR != config.RC_SUCCESS:    
            self.map_to_modem_state(self.ERROR)

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        # verifica se o SIMCard foi detectado e está debloqueado
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        sim_card_ready = False
        sim_card = sim.getStatus()
        # 0	The SIM card does not exist/has been removed.
        # 1	The SIM card is ready.
        # 2 The SIM card is blocked by PIN
        if config.LOG_ON: self.logger.logMessage("Sim Card status = {}" .format(sim_card))  
        if sim_card == 1: # 1	The SIM card is ready.
            sim_card_ready = True
        elif sim_card == 2: # 2 The SIM card is blocked by PIN
            try:
                # sim_card está bloqueado pelo PIN
                if  int(config.USR_CONFIG['PI']) > 0:  
                    if sim.disablePin(config.USR_CONFIG['PI']):
                        sim_card_ready = True
            except:
                pass

        if not sim_card_ready:   
            # retorna erro de SIM card
            #@log self.logger.logMessage("ret = {}" .format(config.CN_SIM_CARD_ERR))
            if sim_card == 0: 
                self.ERROR = config.CN_SIM_CARD_ERR
                self.map_to_modem_state(self.ERROR)
        
        # Neste ponto se encontrou algum erro de configuração inválida ou Sim card não continua 
        if (self.ERROR == config.CN_SIM_CARD_ERR) or (self.ERROR == config.RC_RE_INVALID):   
            self.state_handle = self.run_error_state # vai para um estado de erro e não faz nada
            if config.LOG_ON: self.logger.logMessage('.. vai para estado de erro')
            return

        # ICCID
        self.ICCID = sim.getIccid()
        if config.LOG_ON: self.logger.logMessage("ICCID = {}" .format(self.ICCID))      
        
        # Qualidade do sinal    
        self.NET_QLT = net.csqQueryPoll()
        if config.LOG_ON: self.logger.logMessage('net_quality = {}'.format(self.NET_QLT))
        config.DEV_IO_OBJ.updateSignalQuality(sig_qlt=self.NET_QLT)

        # Nome da Operadora
        operator_name = net.operatorName()
        # retorna: (long_eons, short_eons, mcc, mnc)        
        if operator_name != -1:
            self.OPERATOR = operator_name[0]
            if config.LOG_ON: self.logger.logMessage('operator_name = {}'.format(operator_name))

        # atualiza proximo estado
        self.state_handle = self.run_get_net_status

    # - - - - - - - - - - - - - - - - - - - - - 
    def run_get_net_status(self):
        #('\n..run_get_net_status')
        # atualiza o pino de status
        if self.ERROR == config.RC_SUCCESS:
            self.ERROR = config.CN_NO_NET
            self.map_to_modem_state(self.ERROR)   

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        # Verifica o status atual da rede:
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
        pdp_info = dataCall.getInfo(config.PDP_CONTEXT_ID, 0) 
        #retorna: (profileID, ipType, [state, reconnect, addr, priDNS, secDNS])
        if config.LOG_ON: self.logger.logMessage('pdp_info = {}'.format(pdp_info))
        if pdp_info[2][0] == 1: 
            # rede conectada -> aguarda rede estar disponível
            self.MY_IP = pdp_info[2][2]
            if config.LOG_ON: self.logger.logMessage("cel network connected")  
            self.state_handle = self.run_wait_net_ready #atualiza proximo estado
            return

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        # Configura APN, USER , PASS da rede celular
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        pdpCtx = dataCall.getPDPContext(config.PDP_CONTEXT_ID) 
        # retorna: (ipType, apn, username, password, authType)
        if config.LOG_ON: self.logger.logMessage('pdpCtx = {}'.format(pdpCtx))
        try:
            # verifica se os parâmetros de configuração do arquivo são diferentes dos parâmetros corrente
            if (config.USR_CONFIG["AP"] != pdpCtx[1]) or (config.USR_CONFIG["US"] != pdpCtx[2]) or (config.USR_CONFIG["PS"] != pdpCtx[3]) :
                # desabilita as funcionalidades do modem (para não se conectar na rede celular) sem resetar o modem
                if config.LOG_ON: self.logger.logMessage(".. enviando comando = net.setModemFun(0, 0)")
                net.setModemFun(0, 0)
                if config.LOG_ON: self.logger.logMessage(".. comando enviado finalizado")
                #utime.sleep(1)  
                ret = dataCall.setPDPContext(config.PDP_CONTEXT_ID, 0, config.USR_CONFIG["AP"], config.USR_CONFIG["US"], config.USR_CONFIG["PS"], 0)
                if config.LOG_ON: self.logger.logMessage(".. programando nova configuração = {}" .format(config.USR_CONFIG)) 
                if ret == 0:
                    if config.LOG_ON: self.logger.logMessage(".. configurado novos parametros de APN, USR, PASS com sucesso") 
                    # habilita as funcionalidades do modem e sem reset.
                    net.setModemFun(1, 0)
                    #from misc import Power
                    #Power.powerRestart()  
                else :             
                    if config.LOG_ON: self.logger.logMessage("** Não foi possivel configurar parametros da rede celular**")
                    self.ERROR = config.CN_INV_AUTH_DATA
                    self.map_to_modem_state(self.ERROR) 
                    #self.state_handle = self.run_error_state # vai para um estado de erro e não faz nada
                    return
        except:
            pass

        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        # Ativa PDP Context        
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        ret = dataCall.activate(config.PDP_CONTEXT_ID)
        if config.LOG_ON: self.logger.logMessage('activate pdpCtx = {}'.format(ret))
        if ret == -1:
            # erro ao ativar PDP context 
            if config.LOG_ON: self.logger.logMessage('.. erro ao ativar PDP Context!')
            # Nesta situação pode ocorrer que o parametro de autoconexão
            # corrija um eventual erro. Nesta caso permite transferir
            # para o proximo estado que server para aguardar o processo ser
            # concluido

        # atualiza proximo estado
        self.state_handle = self.run_wait_net_ready

    # - - - - - - - - - - - - - - - - - - - - - 
    def run_wait_net_ready(self):
        #print('\n..run_wait_net_ready')
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        # Verifica o status atual da rede/aguarda conexão:
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        netReady = False
        if self.waitNetReadyRtry < config.NETWORKREADY_RTRY:
            net_stage, net_status = checkNet.waitNetworkReady(config.WAITNETWORKREADY_TMO)
            # Returns a tuple in (stage, state) format: If the network is ready, (3,1) will be returned.
            if config.LOG_ON: self.logger.logMessage('net_stage, net_status  = {}, {}'.format(net_stage, net_status))
            
            if net_stage != 3 or net_status != 1:
                self.waitNetReadyRtry = self.waitNetReadyRtry + 1
                if config.LOG_ON: self.logger.logMessage('waitNetReadyRtry = {}'.format(self.waitNetReadyRtry))                
                
                net_state = net.getState() # This interface gets the network registration information.
                if config.LOG_ON: self.logger.logMessage('net_state = {}'.format(net_state))
                # net_stage = ([1, 50119, 230503937, 7, 0, 0], [1, 50119, 230503937, 7, 0, 0])
                # Information of the phone registration : 
                #           voice_state, voice_lac, voice_cid, voice_rat, voice_reject_cause, voice_psc
                # Information of the current network registration: 
                #           data_state, data_lac, data_cid, data_rat, data_reject_cause, data_psc
                """
                data_state: 	Network registration status
                    0	    Not registered and MT is not searching for an operator again.
                *   1	    Registration completed. Local network.
                    2	    Not registered but MT is searching for an operator.
                    3	    Registration denied.
                    4	    Unknown.
                *   5	    Registration completed. Roaming network.
                    6/7	    Registered on "SMS only" local/roaming network (inapplicable).
                    8	    Emergency attachment is limited to emergency bearer service.
                    9/10	Registered on "Low priority of CSFB" local/roaming network (inapplicable).
                    11	    Emergency bearer service only.                 
                """
                data_state = net_state[1][0]

                if data_state == 1 or data_state == 5:
                    # sucesso na autenticação da rede celular
                    if config.LOG_ON: self.logger.logMessage('.. modem registrado na rede celular com sucesso.')
                    netReady = True
                    
                elif data_state == 2:
                    if config.LOG_ON: self.logger.logMessage('.. aguardando modem se registrar na rede')
                    self.ERROR = config.CN_NO_NET
                
                else:
                    if config.LOG_ON: self.logger.logMessage('.. erro ao registar na rede ! *****')
                    # retorna erro de registro na rede
                    self.ERROR = config.CN_REGISTER_DENIED

            else:
                netReady = True
        else:
            self.waitNetReadyRtry = 0
            self.state_handle = self.run_get_simcard_info # volta para inicio

        if netReady == True:
            self.ERROR = config.RC_SUCCESS
            self.waitNetReadyRtry = 0
            self.state_handle = self.run_connect_tcp
        else:
            self.map_to_modem_state(self.ERROR)

    # - - - - - - - - - - - - - - - - - - - - - 
    def run_connect_tcp(self):
        #print('\n..run_connect_tcp')
        # - - - - - - - - - - - - - - - - - - - - - 
        def open_ssl_connection(self):
            sock_created = False

            # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            # verifica se existe arquivo de certificado CA
            ret = ql_fs.path_exists(config.SSL_CA_FNAME)
            if ret == True:
                ssl_file = open(config.SSL_CA_FNAME,'r') # se existe arquivo carrega
                self.ssl_ca = ssl_file.read()
                ssl_file.close()
                if config.LOG_ON: self.logger.logMessage('..ssl_ca: {}'.format(self.ssl_ca[0:20])) 
            else:
                if config.LOG_ON: self.logger.logMessage('..sem CA certificate') 

                # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
                # verifica se existe arquivo de certificado
                ret = ql_fs.path_exists(config.SSL_CC_FNAME)               
                if ret == True:
                    ssl_file = open(config.SSL_CC_FNAME,'r') # se existe arquivo carrega
                    self.ssl_ca = ssl_file.read()
                    ssl_file.close()
                    if config.LOG_ON: self.logger.logMessage('..ssl_cert: {}'.format(self.ssl_ca[0:20])) 
                else:
                    if config.LOG_ON: self.logger.logMessage('..sem CC certificate') 

            # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            # verifica se arquivo privkey
            ret = ql_fs.path_exists(config.SSL_PK_FNAME)
            if ret == True:
                ssl_file = None
                ssl_file = open(config.SSL_PK_FNAME,'r') # se existe arquivo carrega
                self.ssl_pk = ssl_file.read()
                ssl_file.close()
                if config.LOG_ON: self.logger.logMessage('..ssl_privkey: {}'.format(self.ssl_pk[0:20])) 
            else: 
                if config.LOG_ON: self.logger.logMessage('..sem PRIVET KEY ') 

            if config.LOG_ON: self.logger.logMessage('..try ssl_sock ') 
            try:
                #cria um socket SSL com o parâmetros:
                # Create Secure Channel over SSL, supported by the server.
                self.socket = ussl.wrap_socket(self.socket, cert=self.ssl_ca, key=self.ssl_pk) 
                #self.socket = ussl.wrap_socket(self.socket, cert=None, key=None)
                sock_created = True

            except OSError as msg:
                err_code, err_name = getERRORCode(msg)
                if config.LOG_ON: self.logger.logMessage('SSL_ERR: cod.{} - nome.{}'.format(err_code, err_name)) 
                # Verifica se a exceção ocorrida é de : QUEC_PY_EAGAIN  11  Try again e ignora este erro
                if err_name == 'EAGAIN':
                    sock_created = True

            except ValueError as msg:
                if config.LOG_ON: self.logger.logMessage('SSL_ERR: {}}'.format(msg)) 
                sock_created = False
            except:
                if config.LOG_ON: self.logger.logMessage('SSL_ERR: outro erro')   
                pass

            return sock_created

        # - - - - - - - - - - - - - - - - - - - - - 
        def send_v1_authentication(self):
            if config.LOG_ON: self.logger.logMessage('envia autenticacao V1:')
            # lê arquivo de com string de autenticação v1
            ret = config.RC_SUCCESS
            try: 
                data = ql_fs.read_json(config.V1_AUT_FNAME)
                buf = ubinascii.unhexlify(data['value'])
                if config.LOG_ON: self.logger.logMessage('{}'.format(buf))
                self.socket.write(buf)
            except:
                ret = config.RC_FAIL
            return ret
            
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
        # Cria um socket TCP e tenta abrir conexão com IP e Porta destino
        # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

        self.map_to_modem_state(config.RC_CO_CONEX_FAIL) # atualiza status mesmo que ainda não conclui o processo           
        sock_created = False
        # cria o objeto socket
        self.socket = usocket.socket(usocket.AF_INET, usocket.SOCK_STREAM)

        # Validade de IP e porta destino
        try:
            addr = usocket.getaddrinfo(config.USR_CONFIG["IP"], config.USR_CONFIG["PO"])[0][-1]
            
        except:
            if config.LOG_ON: self.logger.logMessage('erro na conversao de IP e porta destino')
            self.ERROR = config.RC_RE_INVALID   
            self.state_handle = self.run_get_simcard_info
            return

        # tenta abrir conexao TCP com IP e Porta destino:
        try:
            # ativa aberta da conexao
            self.socket.connect(addr)
            # Sockets seguros sao abertos em modo blocking enquanto que sockets nao seguros
            # sao abertos em modo non-blocking
            block_tmo = 0
            if (self.ssl_mode):
              block_tmo = 1 # 1 segundo para realizar autenticação SSL

            self.socket.settimeout(block_tmo) # non blocking mode for socket
            #self.socket.setblocking(block_tmo) # non blocking mode for socket
            if config.LOG_ON: self.logger.logMessage('abriu conexão com blocking de {}'.format(block_tmo))
            
            # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            # testa se socket deve ser aberto com conexão segura
            sock_created = True
            if self.ssl_mode:
                sock_created = open_ssl_connection(self)

            # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
            # KEEPALIVE da conexão TCP:
            self.socket.setsockopt(usocket.SOL_SOCKET, usocket.TCP_KEEPALIVE, 30)

        except:
            pass

        # verifica se o socket foi não criado com sucesso
        if sock_created == False:         
            if config.LOG_ON: self.logger.logMessage('** Erro na criação do socket') 
            self.socket.close()
            self.socket = None
            self.map_to_modem_state(config.RC_CO_CONEX_FAIL) # atualiza status
            self.state_handle = self.run_get_simcard_info
            utime.sleep(0.5) # aguarda um tempo antes de tentar reconetar
            return 

        # verifica se há uma conexão TCP aberta:
        soc_sts = self.socket.getsocketsta()
        if config.LOG_ON: self.logger.logMessage('soc_sts = {} '.format(soc_sts)) 
        
        """
        socket status: 
            0	CLOSED	    The socket is created but not used.
            1	LISTEN	    The socket is listening to the connection.
            2	SYN_SENT	The socket is trying to establish a connection actively. That is, ACK has not been received after sending the SYN.
            3	SYN_RCVD	The socket is in the initial synchronization status of the connection. That is, the SYN sent from the opposite has been received, but the ACK of the sent SYN has not been received.
            4	ESTABLISHED	The socket has successfully established the connection.
            5	FIN_WAIT_1	The socket is closed and the TCP connection is closing. That is, the FIN is sent actively, but the ACK or FIN sent from the party closed passively has not been received.
            6	FIN_WAIT_2	The local socket is closed and the remote socket is waiting to be closed. That is, the ACK corresponding to the sent FIN is received in the FIN_WAIT_1 status.
            7	CLOSE_WAIT	The remote socket is closed and the local socket is waiting to be closed. The passively closed party receives the FIN.
            8	CLOSING	    The local socket is closed and the remote socket is closing. The close confirmation is suspended. That is, the FIN sent from the passively closed party has been received in FIN_WAIT_1 status.
            9	LAST_ACK	The remote socket is closed , and the close confirmation of the local socket is being waited. The passively closed party sends the FIN in CLOSE_WAIT status.
            10	TIME_WAIT	The remote socket is closed and the local socket is waiting to be closed. That is, the four-way wavehand FIN, ACK, FIN, and ACK are complete. The TCP connection is disconnected after 2MSL time.        
        """
        if soc_sts == config.SOCK_STATE_CONNECTED:
            # esta com a conexão aberta então finaliza.                
            self.map_to_modem_state(config.RC_SUCCESS)
            if config.LOG_ON: self.logger.logMessage('.. conexão TCP aberta') 

            # se tem autenticacao V1 vai para o processo de envio da string de autenticacao
            if config.USR_CONFIG["V1"] == 1:
                ret = send_v1_authentication(self)
            elif len(config.USR_CONFIG["CS"]) > 0:
                if config.LOG_ON: self.logger.logMessage('envia ID na conexao: {}'.format(config.USR_CONFIG["CS"])) 
                self.socket.write(config.USR_CONFIG["CS"])
                # TODO - Avaliar necessidade do delay a seguir
                utime.sleep_ms(200)

            # Se conexão SSL é criada uma thread em separado para tratar a 
            # recepção do socket pois o mesmo foi aberto em modo blocking.
            if self.ssl_mode:
                # cria thread para tratar os pacotes recebidos do socket
                if (self.thread_id != -1):
                    _thread.stop_thread(self.thread_id) # Termina thread de recepção do socket
                self.thread_id = _thread.start_new_thread(self.run_thread, ())

            # entrada em modo transparente
            self.state_handle = self.run_wire_mode

        else:
            # estao em algum processo intermediario ou com erro na conexao
            if config.LOG_ON: self.logger.logMessage('.. socket.connect nao retornou SOCK_STATE_CONNECTED') 
            self.socket.close()
            self.socket = None
            self.map_to_modem_state(config.RC_CO_CONEX_FAIL) # atualiza status
            # aguarda 2.5 segundos antes de reconectar
            self.wait_for_next_state(self.run_get_simcard_info, 2500)

    # - - - - - - - - - - - - - - - - - - - - - 
    def run_wire_mode(self):
        # verifica se nenuma conexcao TCP aberta:
        reconnect = True
        try :
            reconnect = (self.socket.getsocketsta() != config.SOCK_STATE_CONNECTED)
        except:
            pass

        if reconnect:
            self.tread_running = False  # sinaliza para terminar a Tread de recepção SSL 
            if config.LOG_ON: self.logger.logMessage('disconnect = {} '.format(self.socket.getsocketsta())) 
            self.socket.close()
            self.socket = None
            if config.LOG_ON: self.logger.logMessage('conexão TCP fechada')
            #atualiza o status do modem para erro de acesso
            self.map_to_modem_state(config.RC_CO_CONEX_FAIL)
            # aguarda 2.5 segundos antes de reconectar
            self.wait_for_next_state(self.run_get_simcard_info, 2500)
            return
            
        # Verifica se existem dados disponíveis no canal serial
        # Obtem os dados e reenvia pelo socket aberto na rede celular
        try:
            buf, error = self.comm.readData()
            if error == config.RC_SUCCESS:
                sent_bytes = self.socket.write(buf)
                if sent_bytes != len(buf) :
                    if config.LOG_ON: self.logger.logMessage('Socket sent fail = plan {:d}, done {:d}'.format(len(buf), sent_bytes)) 
                else:    
                    if config.LOG_ON: self.logger.logMessage('COM -> SOCKET')
                    if config.LOG_ON: self.logger.logMessage('{}'.format(buf)) 
        except:
            pass
        
        # Verifica se existem dados disponíveis no socket aberto na rede celular
        # Obtem o dados e reenvia para o canal serial 
        buf = None
        if (self.ssl_mode):
            # socket foi aberto no modo Blocking
            # A recepção é realizada por uma thread separada
            if self.ssl_data_available:
                buf = self.ssl_buf
                self.ssl_data_available = False
                self.ssl_buf = None # zera o buffer de recepção utilizado na thread
        else:
            try:
                # socket foi aberto no modo Non-Blocking
                buf = self.socket.recv(1024)  # aguarda chegada do pacote TCP
                if config.LOG_ON: self.logger.logMessage('..RECV')
            except OSError as msg:
                _, err_name = getERRORCode(msg)
                # Verifica se a excecao ocorrida nao de : QUEC_PY_EAGAIN  11  Try again
                if err_name != 'EAGAIN' and  err_name != 'ETIMEDOUT' : 
                    if config.LOG_ON: self.logger.logMessage('ERR: {}'.format(msg))

        try:
            if (buf != None) and len(buf) > 0:
                self.comm.writeData(buf) # encaminha pacote recebido via socket para o canal serial
                if config.LOG_ON: self.logger.logMessage('SOCKET -> COM')
                if config.LOG_ON: self.logger.logMessage('{}'.format(buf))
        except OSError as msg:
            if config.LOG_ON: self.logger.logMessage('COMM WRITE ERR: {}'.format(msg))

    # - - - - - - - - - - - - - - - - - - - - - 
    def run_error_state(self):
        # Estado de erro, a cada 1 min reiniicaliza máquina de estado

        # Armazena o tempo inicial ao entrar no estado
        if self.error_state_start_time == -1:
            self.error_state_start_time = utime.time()

        # Verifica se 1 minuto (60 segundos) se passou
        if utime.time() - self.error_state_start_time >= 60:
            # Reinicializa a máquina de estado
            self.error_state_start_time == -1  # Reseta o timer
            self.state_handle = self.startMode # Retorna para o estado de configuração
            if config.LOG_ON:  self.logger.logMessage('Reinicializando...')
            return

        # Caso contrário, permaneça no estado de erro
        utime.sleep(1)  # Aguarda 1 segundo antes de verificar novamente        
        pass

    # - - - - - - - - - - - - - - - - - - - - - 
    def run_thread(self):
        if config.LOG_ON: self.logger.logMessage('REC SOCKET Active') 
        self.tread_running = True
        while self.tread_running:

            # Esta implementação não é a adequada para o contexto.
            # Esta desta forma porque o firmware da QUECTEL possui bugs 
            # no processo de recepção de sockets quando aberto com SSL

            #self.socket.setblocking(False) # non blocking mode for socket
            # socket foi aberto no modo Blocking
            # OBS: Devido a erros na implementacao do firmware o pacote deve ser lido
            #      byte a byte. O fabricante (QuecTel) ficou de atualizar o firmware 
            #      com a correcao. Prazo: nao definido ????

            # Aguarda a chegada do primeiro byte do pacote pelo canal ethernet
            try:
                self.socket.setblocking(True) 
                self.ssl_buf = self.socket.read(1)
                self.socket.setblocking(False) 
                while self.tread_running:
                    try:
                        # Aguarda o restante dos bytes do pacote
                        self.ssl_buf += self.socket.read(1)
                    except OSError as msg:
                        _, err_name = getERRORCode(msg)
                        if err_name != 'ETIMEDOUT' and err_name != 'EAGAIN'  : 
                            self.ssl_buf = None # falha na recepcao, ignora o pacote
                            if config.LOG_ON: self.logger.logMessage('SSL PACK ERR:{}'.format(msg)) 
                        break  # terminou de receber ou erro

            except OSError as msg:
                _, err_name = getERRORCode(msg)
                if err_name != 'ETIMEDOUT' and err_name != 'EAGAIN'  : 
                    if config.LOG_ON: self.logger.logMessage('SSL REC ERR:{}'.format(msg)) 
                    break  # terminou de receber ou erro
            
            if self.ssl_buf != None:
                self.ssl_data_available = True

        self.thread_id = -1

    # - - - - - - - - - - - - - - - - - - - - - 
    def run(self):
        if self.state_handle != None:
            # if (self.old_state_handle != self.state_handle):
            #    if self.state_handle == self.run_get_static_info:
            #        print('run_get_static_info')
            #    elif self.state_handle == self.run_wire_mode:
            #        print('run_wire_mode')
            #    elif self.state_handle == self.run_get_simcard_info:
            #        print('run_get_simcard_info')
            #    elif self.state_handle == self.run_connect_tcp:
            #        print('run_connect_tcp')
            #    else:
            #        print('unknow...')
            # self.old_state_handle = self.state_handle
                
            self.state_handle()
