FUNCTION_BLOCK "FB15056_ZC_SIVE_MEMBRANA_CARE"
TITLE = FB15056_ZC_REGISTRO_MEMBRANAS
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : HCR
FAMILY : ZeusControl
VERSION : 1.0
//Bloque de funcion para la gestion del registro de membranas
   VAR_INPUT 
      Pulso1Seg : Bool;   // Pulso 1 Segundo
      OrdenRegistroGeneral : Bool;   // OrdenRegistroGeneral
      FechaHora {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;   // Fecha y hora actual
      Iniciar : Bool;   // Orden contador de tiempo
   END_VAR
   VAR_INPUT RETAIN
      Usuario : String[25];   // Usuario
   END_VAR
   VAR_INPUT 
      TecnicoRegistroGeneral : String[20];   // Tecnico registro general
   END_VAR
   VAR_INPUT RETAIN
      TiempoAvisoOn : Int;   // Tiempo indicación aviso On
      TiempoAvisoOff : Int;   // Tiempo indicación aviso Off
      TiempoAlarmaOn : Int;   // Tiempo indicación alarma On
      TiempoAlarmaOff : Int;   // Tiempo indicación alarma Off
      HorasAviso : Int;   // Horas para aviso
      HorasAlarma : Int;   // Horas para alarma
      HorasAlarmaCritica : Int;   // Horas para alarma critica
   END_VAR

   VAR_OUTPUT 
      Aviso : Bool;   // Estado de aviso
      Alarma : Bool;   // Estado de alarma
      AlarmaCritica : Bool;   // Estado de alarma critica
      Estado : Int;   // Estado
      TiempoMembranaRestante : UDInt;   // Tiempo membrana restante
   END_VAR

   VAR_IN_OUT 
      OrdenBorrar : Bool;   // Ordrn borrar registros
   END_VAR

   VAR RETAIN
      Registros { S7_SetPoint := 'False'} : Array[1..#N_REGISTROS] of "UDT_ZC_SIVE_MEMBRANA_CARE";
   END_VAR
   VAR 
      Pulsador { S7_SetPoint := 'False'} : "UDT_ZC_PULSADOR";   // Pulsador nuevo registro
   END_VAR
   VAR RETAIN
      Tiempo { S7_SetPoint := 'False'} : "UDT_ZC_CONTADOR_TIEMPO";   // Tiempo actual membranas
      s_PuestaMarchaAno : UInt;   // Fecha de puesta en marcha
      s_PuestaMarchaMes : Int;   // Fecha de puesta en marcha
      s_PuestaMarchaDia : Int;   // Fecha de puesta en marcha
      s_Indice : Int := 1;   // Indice registro
      s_TecnicoNuevoRegistro : String[20];   // Nombre tecnico nuevo registro
      s_FlipFlopAviso { S7_SetPoint := 'False'} : "UDT_ZC_DISP_FF";
      s_FlipFlopAlarma { S7_SetPoint := 'False'} : "UDT_ZC_DISP_FF";
   END_VAR

   VAR_TEMP 
      for_i : Int;   // Bucle para recorrer los registros
      t_OrdenReset : Bool;   // Orden Reset contador de horas
   END_VAR

   VAR CONSTANT 
      N_REGISTROS : Int := 10;   // Número de registros
   END_VAR


BEGIN
	REGION DESCRIPCION
	//=======================================================================
	// (C)Copyright ZeusControl 2023 - 2026
	//=======================================================================
	(*
	### Información del Sistema
	| Hardware Compatible | Entorno de Ingeniería |
	|---------------------|-----------------------|
	| PLC serie 1200/1500 | TIA Portal 18         |
	
	---
	### Descripción Funcional
	El bloque `FB15056_ZC_REGISTRO_MEMBRANAS` implementa la gestión integral del registro y vida útil de las membranas. Mantiene un histórico circular de intervenciones (`Registros[i]`), controla el tiempo de uso acumulado y genera los avisos, alarmas y alarma crítica en función de las horas restantes, incluyendo la posibilidad de registrar cambios generales de forma masiva.
	
	Funcionalidades principales:
	- Registro de intervención por pulsador: el bloque gestiona un pulsador (`FC15026_ZC_PULSADOR`) con permiso dependiente de que haya un técnico introducido en `#s_TecnicoNuevoRegistro`. Al confirmarse la pulsación (`Pulsador.Plc`), se almacena en `#Registros[#s_Indice]` la fecha/hora convertida con `FC15005_ZC_DTL_TO_ZC_FECHA`, el técnico y el tiempo de membrana (`#Tiempo.Hora`), se limpia el técnico y se dispara la orden interna `#t_OrdenReset` para reiniciar el contador de tiempo. El índice avanza de forma circular hasta `#N_REGISTROS`.
	- Registro general: la entrada `#OrdenRegistroGeneral` permite registrar un cambio de membrana sin pulsador físico, asociando el técnico definido en `#TecnicoRegistroGeneral` y siguiendo la misma lógica circular de índice.
	- Contador de tiempo de membrana: mediante `FC15025_ZC_CONTADOR_TIEMPO` se acumula el tiempo de uso en horas, con posibilidad de reset a través de `#t_OrdenReset`.
	- Cálculo de horas restantes: `#TiempoMembranaRestante` se calcula como la diferencia entre `#HorasAlarmaCritica` y `#Tiempo.Hora`, acotado a `0` para evitar desbordamientos cuando se supera el umbral crítico.
	- Borrado de registros: la entrada `#OrdenBorrar` limpia completamente el array de registros (fecha, hora, técnico y tiempo de membrana) y reinicia el índice de escritura a `1`.
	- Estado global por tramos: clasificación del estado en `#Estado` (0: Normal, 1: Aviso, 2: Alarma, 3: Alarma crítica) según el rango de horas acumulado frente a `#HorasAviso`, `#HorasAlarma` y `#HorasAlarmaCritica`.
	- Aviso y Alarma intermitentes: se generan mediante dos Flip-Flop (`FC2926_ZC_FF_INDV`) con tiempos de encendido/apagado configurables (`#TiempoAvisoOn/Off`, `#TiempoAlarmaOn/Off`) para producir señales parpadeantes. La alarma crítica (`AlarmaCritica`) es continua y se activa definitivamente al alcanzar `#HorasAlarmaCritica`.
	
	> **Restricciones:** Ninguna.
	
	---
	### Dependencias Requeridas
	| Tipo | Elementos |
	|------|-----------|
	| FC   | `FC15026_ZC_PULSADOR`, `FC15025_ZC_CONTADOR_TIEMPO`, `FC2926_ZC_FF_INDV`, `FC15005_ZC_DTL_TO_ZC_FECHA` |
	| FB   | - |
	| DB   | - |
	| UDT  | `ZC_FECHA`, `UDT_ZC_PULSADOR`, `UDT_ZC_CONTADOR_TIEMPO`. `UDT_ZC_SIVE_MEMBRANA_CARE` |
	    
	---
	### Historial de Cambios
	| Versión  | Fecha      | Técnico | Cambios |
	|----------|------------|---------|---------|
	| 01.00.00 | 10.05.2023 | (HCR)   | Primera versión. |
	| 01.00.01 | 02.10.2023 | (ABH)   | Cambio nombre a SIVE MEMBRANA CARE. Se corrigen permisos variables estáticas. Se corrigen nombres de variables. Se crea UDT `registro membranas`. Se cambia tipo de dato fecha/hora string a `ZC_FECHA`. Se quitan temporizadores y se utiliza Flip-Flop individual. Se eliminan variables innecesarias. |
	| 01.00.02 | 16.01.2024 | (ABH)   | Se limita el tiempo restante de las membranas para que no se desborde. |
	| 01.00.03 | 02.12.2025 | (ABH)   | Se corrige cálculo tiempo horas restantes. |
	| 01.00.04 | 10.01.2026 | (CNR)   | Se añade orden y gestión de registro general. |
	*)
	END_REGION DESCRIPCION
	
	
	//  =============================================================================
	REGION ORDEN_REGISTRO
	    
	    #Pulsador.Permiso := (#s_TecnicoNuevoRegistro <> '');
	    "FC15026_ZC_PULSADOR"(Pulso1Seg := #Pulso1Seg,
	                          SP_Tiempo := 2,
	                          TrazaCategoria:=0,
	                          TrazaIdProceso:=0,
	                          TrazaIdPulsador:=0,
	                          Usuario := #Usuario,
	                          FechaHoraActual := #FechaHora,
	                          Pulsador := #Pulsador);
	    
	    //  Orden pulsador
	    IF #Pulsador.Plc THEN
	        
	        #Registros[#s_Indice].FechaHora := "FC15005_ZC_DTL_TO_ZC_FECHA"( #FechaHora);
	        #Registros[#s_Indice].Tecnico := #s_TecnicoNuevoRegistro;
	        #Registros[#s_Indice].TiempoMembrana := #Tiempo.Hora;
	        
	        //  Reset valor técnico y orden reset tiempo
	        #s_TecnicoNuevoRegistro := '';
	        
	        #t_OrdenReset := TRUE;
	        
	        //  Incrementamos indice y si es mayor que el número máximo de registro lo ponemos en la posición inicial
	        #s_Indice += 1;
	        IF #s_Indice > #N_REGISTROS THEN
	            #s_Indice := 1;
	        END_IF;
	    END_IF;
	    
	    //  Orden registro general
	    IF #OrdenRegistroGeneral THEN
	        
	        #Registros[#s_Indice].FechaHora := "FC15005_ZC_DTL_TO_ZC_FECHA"(#FechaHora);
	        #Registros[#s_Indice].Tecnico := #TecnicoRegistroGeneral;
	        #Registros[#s_Indice].TiempoMembrana := #Tiempo.Hora;
	        
	        //  Reset valor técnico y orden reset tiempo
	        #t_OrdenReset := TRUE;
	        
	        //  Incrementamos indice y si es mayor que el número máximo de registro lo ponemos en la posición inicial
	        #s_Indice += 1;
	        IF #s_Indice > #N_REGISTROS THEN
	            #s_Indice := 1;
	        END_IF;
	    END_IF;
	    
	END_REGION ORDEN_REGISTRO
	
	
	// =============================================================================
	REGION TIEMPO_MEMBRANAS
	    
	    //  Contador de tiempo
	    "FC15025_ZC_CONTADOR_TIEMPO"(Pulso1seg := #Pulso1Seg,
	                                 Iniciar := #Iniciar,
	                                 Contador := #Tiempo,
	                                 Reset := #t_OrdenReset);
	    
	    //  Horas Restante
	    IF INT_TO_UDINT(#HorasAlarmaCritica) > #Tiempo.Hora THEN
	        #TiempoMembranaRestante := INT_TO_UDINT(#HorasAlarmaCritica) - #Tiempo.Hora;
	    ELSE
	        #TiempoMembranaRestante := 0;
	    END_IF;
	    
	END_REGION TIEMPO_MEMBRANAS
	
	
	//  =============================================================================
	REGION BORRAR_REGISTROS
	    
	    IF #OrdenBorrar THEN
	        
	        FOR #for_i := 1 TO #N_REGISTROS DO
	            
	            #Registros[#for_i].FechaHora.Ano := 0;
	            #Registros[#for_i].FechaHora.Mes := 0;
	            #Registros[#for_i].FechaHora.Dia := 0;
	            #Registros[#for_i].FechaHora.Hora := 0;
	            #Registros[#for_i].FechaHora.Minuto := 0;
	            #Registros[#for_i].FechaHora.Segundo := 0;
	            #Registros[#for_i].FechaHora.NanoSegundo := 0;
	            
	            #Registros[#for_i].Tecnico := '';
	            #Registros[#for_i].TiempoMembrana := 0;
	            #s_Indice := 1;
	            
	        END_FOR;
	        
	        #OrdenBorrar := FALSE;
	    END_IF;
	    
	END_REGION BORRAR_REGISTROS
	
	
	//  =============================================================================
	REGION ESTADO
	    
	    #s_FlipFlopAviso.Orden_Activar := FALSE;
	    #s_FlipFlopAviso.Config_SetPointTiempoFlip := #TiempoAvisoOn;
	    #s_FlipFlopAviso.Config_SetPointTiempoFlop := #TiempoAvisoOff;
	    #s_FlipFlopAlarma.Orden_Activar := FALSE;
	    #s_FlipFlopAlarma.Config_SetPointTiempoFlip := #TiempoAlarmaOn;
	    #s_FlipFlopAlarma.Config_SetPointTiempoFlop := #TiempoAlarmaOff;
	    
	    //  Estado Aviso
	    #Estado := 0;
	    
	    IF #Tiempo.Hora >= #HorasAviso AND #Tiempo.Hora < #HorasAlarma THEN
	        #s_FlipFlopAviso.Orden_Activar := TRUE;
	        #Aviso := #s_FlipFlopAviso.#Estado_Flip;
	        #Estado := 1;
	    ELSE
	        #Aviso := FALSE;
	    END_IF;
	    
	    
	    //  Estado Alarma
	    IF #Tiempo.Hora >= #HorasAlarma AND #Tiempo.Hora < #HorasAlarmaCritica THEN
	        #s_FlipFlopAlarma.Orden_Activar := TRUE;
	        #Alarma := #s_FlipFlopAlarma.#Estado_Flip;
	        #Estado := 2;
	    ELSE
	        #Alarma := FALSE;
	    END_IF;
	    
	    
	    //  Estado Alarma Crítica
	    IF #Tiempo.Hora >= #HorasAlarmaCritica THEN
	        #AlarmaCritica := TRUE;
	        #Estado := 3;
	    ELSE
	        #AlarmaCritica := FALSE;
	    END_IF;
	    
	END_REGION ESTADO
	
	
	//  =============================================================================
	REGION GESTION_FF
	    
	    //  =============================================================================
	    //  Flip Flop aviso
	    "FC2926_ZC_FF_INDV"(Pulso1Seg := #Pulso1Seg,
	                 FF := #s_FlipFlopAviso);
	    
	    
	    //  =============================================================================
	    //  Flip Flop alarma
	    "FC2926_ZC_FF_INDV"(Pulso1Seg := #Pulso1Seg,
	                 FF := #s_FlipFlopAlarma);
	    
	   
	END_REGION GESTION_FF
	
	
END_FUNCTION_BLOCK

