import struct
import time
from datetime import datetime
import socket
import logging

logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')

class CircutorReader:
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.socket = None
        self.direccion = 1
        self.estado = 1
        self.valoresInstantaneos = {
            'E': {'activaImpo': 0, 'activaExpo': 0, 'reactivaQ1': 0, 'reactivaQ2': 0, 'reactivaQ3': 0, 'reactivaQ4': 0, 'tiempo': {}},
            'P': {'activaTotal': 0, 'reactivaTotal': 0, 'factorPotenciaTotal': 0, 'activaFases': [0, 0, 0], 'reactivaFases': [0, 0, 0], 'factorPotenciaFases': [0, 0, 0], 'tiempo': {}},
            'IV': {'tension': [0, 0, 0], 'corriente': [0, 0, 0], 'tiempo': {}}
        }

    def connect(self):
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect((self.ip, self.port))
        logging.info(f"Conectado a {self.ip}:{self.port}")

    def disconnect(self):
        if self.socket:
            self.socket.close()
            logging.info("Desconectado")

    def imprimir_valores(self):
        print("\n--- Valores Instantáneos ---")
        for grupo, datos in self.valoresInstantaneos.items():
            print(f"\nGrupo {grupo}:")
            for clave, valor in datos.items():
                if clave == 'tiempo':
                    timestamp = datos['tiempo']
                    print(f"  Timestamp: {timestamp['ano']}-{timestamp['mes']:02d}-{timestamp['dia']:02d} {timestamp['hora']:02d}:{timestamp['minutos']:02d}:00")
                elif isinstance(valor, list):
                    print(f"  {clave}: {', '.join([f'{v:.2f}' for v in valor])}")
                else:
                    print(f"  {clave}: {valor:.2f}")

    def procesar_etiqueta_tiempo(self, timestamp_bytes):
        minutos = timestamp_bytes[0] & 0x3F
        hora = timestamp_bytes[1] & 0x1F
        dia = timestamp_bytes[2] & 0x1F
        mes = timestamp_bytes[3] & 0x0F
        ano = 2000 + (timestamp_bytes[3] >> 4) + ((timestamp_bytes[4] & 0x7F) << 4)
        return {
            'minutos': minutos,
            'hora': hora,
            'dia': dia,
            'mes': mes,
            'ano': ano
        }

    def procesar_grupo_192(self, mensajeTX):
        data_start = 10
        self.valoresInstantaneos['E']['activaImpo'] = struct.unpack('>I', mensajeTX[data_start:data_start+4])[0] / 1000.0
        self.valoresInstantaneos['E']['activaExpo'] = struct.unpack('>I', mensajeTX[data_start+4:data_start+8])[0] / 1000.0
        self.valoresInstantaneos['E']['reactivaQ1'] = struct.unpack('>I', mensajeTX[data_start+8:data_start+12])[0] / 1000.0
        self.valoresInstantaneos['E']['reactivaQ2'] = struct.unpack('>I', mensajeTX[data_start+12:data_start+16])[0] / 1000.0
        self.valoresInstantaneos['E']['reactivaQ3'] = struct.unpack('>I', mensajeTX[data_start+16:data_start+20])[0] / 1000.0
        self.valoresInstantaneos['E']['reactivaQ4'] = struct.unpack('>I', mensajeTX[data_start+20:data_start+24])[0] / 1000.0

        timestamp_bytes = mensajeTX[-7:-2]
        self.valoresInstantaneos['E']['tiempo'] = self.procesar_etiqueta_tiempo(timestamp_bytes)

        self.imprimir_valores()

    def procesar_grupo_193(self, mensajeTX):
        data_start = 10
        self.valoresInstantaneos['P']['activaTotal'] = struct.unpack('>h', mensajeTX[data_start:data_start+2])[0] / 10.0
        self.valoresInstantaneos['P']['reactivaTotal'] = struct.unpack('>h', mensajeTX[data_start+2:data_start+4])[0] / 10.0
        self.valoresInstantaneos['P']['factorPotenciaTotal'] = struct.unpack('>h', mensajeTX[data_start+4:data_start+6])[0] / 1000.0

        for i in range(3):
            start = data_start + 6 + i * 6
            self.valoresInstantaneos['P']['activaFases'][i] = struct.unpack('>h', mensajeTX[start:start+2])[0] / 10.0
            self.valoresInstantaneos['P']['reactivaFases'][i] = struct.unpack('>h', mensajeTX[start+2:start+4])[0] / 10.0
            self.valoresInstantaneos['P']['factorPotenciaFases'][i] = struct.unpack('>h', mensajeTX[start+4:start+6])[0] / 1000.0

        timestamp_bytes = mensajeTX[-7:-2]
        self.valoresInstantaneos['P']['tiempo'] = self.procesar_etiqueta_tiempo(timestamp_bytes)

        self.imprimir_valores()

    '''
    def procesar_grupo_194(self, mensajeTX):
        try:
            data_start = 10
            
            for i in range(3):
                tension = struct.unpack('>h', mensajeTX[data_start + i*4:data_start + i*4 + 2])[0] / 10.0
                corriente = struct.unpack('>h', mensajeTX[data_start + i*4 + 2:data_start + i*4 + 4])[0] / 100.0
                self.valoresInstantaneos['IV']['tension'][i] = tension
                self.valoresInstantaneos['IV']['corriente'][i] = corriente

            timestamp_bytes = mensajeTX[-7:-2]
            self.valoresInstantaneos['IV']['tiempo'] = self.procesar_etiqueta_tiempo(timestamp_bytes)

            self.imprimir_valores()
        except Exception as e:
            logging.error(f"Error al procesar grupo 194: {str(e)}")
            
    '''
          
    def procesar_grupo_194(self, mensajeTX):
        try:
            print ('*'*100)
            print (f'El mensajeTX es: {mensajeTX}')
            print ('*'*100)
            
            data_start = 12  # Ajustado para coincidir con el inicio de los datos

            for i in range(3):
                # Extraer corriente (3 bytes, little endian)
                corriente = int.from_bytes(mensajeTX[data_start + i*6:data_start + i*6 + 3], 'little') / 10.0
                
                # Extraer tensión (3 bytes, little endian)
                tension = int.from_bytes(mensajeTX[data_start + i*6 + 3:data_start + i*6 + 6], 'little') / 10.0
                
                self.valoresInstantaneos['IV']['corriente'][i] = corriente
                self.valoresInstantaneos['IV']['tension'][i] = tension

            # Procesar la etiqueta de tiempo (los últimos 5 bytes antes del checksum y fin de trama)
            timestamp_bytes = mensajeTX[-7:-2]
            self.valoresInstantaneos['IV']['tiempo'] = self.procesar_etiqueta_tiempo(timestamp_bytes)

            self.imprimir_valores()
        except Exception as e:
            logging.error(f"Error al procesar grupo 194: {str(e)}")


    def create_fixed_frame(self, control):
        return struct.pack('>BBBBBB', 0x10, control, 0x00, 0x00, control, 0x16)

    def create_auth_request(self):
        start = 0x68
        length = 0x0E
        control = 0x53
        address = 0x0001
        type_id = 183  # C_AC_NA_2 para autenticación
        vsq = 0x01
        cot = 0x06  # Activación
        common_address = 0x0001
        password_bytes = b'\x01\x00\x00\x00'  # Clave de acceso (ajustar según sea necesario)
        
        frame = struct.pack('>BBHHBBHHH4s', start, length, length, control, address, type_id, vsq, cot, common_address, password_bytes)
        checksum = sum(frame[4:]) % 256
        frame += struct.pack('>BB', checksum, 0x16)
        return frame

    def send_receive(self, request, expected_response, timeout=5):
        logging.debug(f"Enviando: {request.hex()}")
        self.socket.sendall(request)
        self.socket.settimeout(timeout)
        try:
            response = self.socket.recv(1024)
            logging.debug(f"Recibido: {response.hex()}")
            if response[1] != expected_response:
                raise Exception(f"Respuesta inesperada: {response[1]}")
            return response
        except socket.timeout:
            logging.error(f"Timeout esperando respuesta después de {timeout} segundos")
            raise
        finally:
            self.socket.settimeout(None)

    def autenticarMedidor(self):
        try:
            # Paso 1: Iniciar comunicación
            init_request = self.create_fixed_frame(0x49)
            response = self.send_receive(init_request, 0x0B)
            logging.info("Paso 1 completado")

            # Paso 2: Reposición del enlace remoto
            reset_request = self.create_fixed_frame(0x40)
            response = self.send_receive(reset_request, 0x00)
            logging.info("Paso 2 completado")

            # Paso 3: Verificar estado del enlace
            status_request = self.create_fixed_frame(0x49)
            response = self.send_receive(status_request, 0x0B)
            logging.info("Paso 3 completado")

            # Paso 4: Enviar clave de acceso
            auth_request = self.create_auth_request()
            response = self.send_receive(auth_request, 0x00)
            logging.info("Paso 4 completado")

            # Paso 5: Solicitar confirmación de autenticación
            confirm_request = self.create_fixed_frame(0x5B)
            response = self.send_receive(confirm_request, 0x08)
            logging.info("Paso 5 completado")

            if response[0] == 0x68 and (response[7] != 0xB7 or response[9] != 0x07):
                raise Exception("Confirmación de autenticación fallida")

            logging.info("Autenticación completada con éxito")
            return True
        except Exception as e:
            logging.error(f"Error de autenticación: {e}")
            return False


    def leerInstantaneos(self, grupo):
        try:
            if self.estado == 0:
                return "NO_INTERROGA_ESTADO_0"

            if not self.miPuerto_getConectado():
                return "FALTA_PROXIMO_INTERVALO"

            rta = self.autenticarMedidor()
            if rta != 0:
                logging.error(f"AUTENTICACION INCORRECTA IDMODBUS:{self.direccion} PASO:{rta}")
                return 1

            logging.info(f"SOLICTIDUD DE INSTANTANEOS:{grupo}")

            mensajeTX = bytearray([
                0x68, 0x0A, 0x0A, 0x68, 0x73,
                self.direccion & 255, self.direccion >> 8,
                0xA2, 0x01, 0x05, 0x01, 0x00, 0x00, grupo
            ])
            mensajeTX.append(self.CRC8(mensajeTX[4:14]))
            mensajeTX.append(0x16)

            self.miPuerto_vaciarBuffer()
            self.miPuerto_enviaDatos(mensajeTX)

            response = self.esperaPaqueteADSU(1000)
            if response is None:
                logging.error("No se recibió respuesta a la solicitud de instantáneos")
                return 2
            if response[1] != 0x00:
                logging.error(f"Respuesta inesperada a la solicitud de instantáneos: 0x{response[1]:02x}")
                return 2

            logging.info("Recibido ACK, enviando solicitud de datos")
            self.enviarADSUfijo(0x5B)
            response = self.esperaPaqueteADSU(1000)
            if response is None:
                logging.error("No se recibió respuesta después de enviar ADSU fijo")
                return 3

            if len(response) < 10:
                logging.error(f"Respuesta demasiado corta: {response.hex()}")
                return 3

            if response[0] != 0x68 or response[7] != 0xA3 or response[9] != 0x05:
                logging.error(f"Formato de respuesta inesperado: {response.hex()}")
                return 3

            if grupo == 192:
                self.procesar_grupo_192(response)
            elif grupo == 193:
                self.procesar_grupo_193(response)
            elif grupo == 194:
                self.procesar_grupo_194(response)

            logging.info("COMUNICACION_EXITOSA-> Instantaneos:")
            return "COMUNICACION_EXITOSA"
        except Exception as e:
            logging.error(f"Error en leerInstantaneos: {str(e)}")
            return "ERROR_TRY_CATCH"

    def miPuerto_getConectado(self):
        return self.socket is not None and self.socket.fileno() != -1

    def CRC8(self, data):
        crc = 0
        for byte in data:
            crc += byte
        return crc % 256

    def miPuerto_vaciarBuffer(self):
        self.socket.setblocking(False)
        try:
            while True:
                self.socket.recv(1024)
        except:
            pass
        finally:
            self.socket.setblocking(True)

    def miPuerto_enviaDatos(self, data):
        self.socket.sendall(data)

    def enviarADSUfijo(self, control):
        msg = bytearray([0x10, control, self.direccion & 255, self.direccion >> 8])
        crc = self.CRC8(msg[1:])
        msg.extend([crc, 0x16])
        self.miPuerto_enviaDatos(msg)

    def esperaPaqueteADSU(self, timeout):
        self.socket.settimeout(timeout / 1000)
        try:
            response = self.socket.recv(1024)
            logging.debug(f"Respuesta ADSU recibida: {response.hex()}")
            return response
        except socket.timeout:
            logging.error("Timeout esperando respuesta ADSU")
            return None
        except Exception as e:
            logging.error(f"Error esperando respuesta ADSU: {str(e)}")
            return None

# Ejemplo de uso
if __name__ == "__main__":
    reader = CircutorReader("35.247.249.153", 58111)
    try:
        reader.connect()
        for grupo in [192, 193, 194]:
            result = reader.leerInstantaneos(grupo)
            print(f"\nResultado para grupo {grupo}: {result}")
            if result != "COMUNICACION_EXITOSA":
                print(f"Error al leer el grupo {grupo}")
    except Exception as e:
        print(f"Error: {str(e)}")
    finally:
        reader.disconnect()