FUNCTION "FC2025_ZC_TOT" : Void
TITLE = FC2025_TOT
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : ABH
FAMILY : ZeusControl
VERSION : 1.0
//Bloque de funcion para gestion de totalizadores.
   VAR_INPUT 
      Pulso100ms : Bool;   // Pulso de 100 milisegundos
      Simulacion : Bool;   // Simulacion
      Arranque : Bool;   // Primer arranque
   END_VAR

   VAR_TEMP 
      for_i : Int;
      t_N_MAX : Int;
      t_CaudalSegundos : LReal;
      t_LReal : LReal;
      t_RT_Dummy : LReal;
   END_VAR

   VAR CONSTANT 
      VALOR_MAXIMO_CONTADORES : Real := 1000000.0;   // VALOR MÁXIMO DE ACUMULADOR
      NUMERO_MAXIMO_DESBORDE : Int := 32000;   // VALOR MÁXIMO NUMERO DE DESBORDE
   END_VAR


BEGIN
	REGION DESCRIPCION
	(*
	###### (C)Copyright ZeusControl 2020 - 2026
	
	---
	### Información del Sistema
	| Hardware Compatible | Entorno de Ingeniería |
	|---------------------|-----------------------|
	| PLC serie 1200/1500 | TIA Portal 18         |
	
	---
	> **Restricciones:** Ninguna.
	
	---
	### Descripción Funcional
	
	Funcion para la gestion de dispositivo de tipo Totalizador.
	
	En ella se realizan las siguientes acciones:
	
	- Se gestiona el multiplexado de los dispositivos para su uso en sistemas HMI/SCADA.
	- Se gestiona el estado del dispositivo
	- Se gestiona el contaje del totalizador por entrada digital o por entrada analogica
	
	---
	### Dependencias Requeridas
	| Tipo | Elementos |
	|------|-----------|
	| FC   | - |
	| FB   | - |
	| DB   | - |
	| UDT  | - |
	    
	---
	### Historial de Cambios
	| Versión  | Fecha      | Técnico | Cambios |
	|----------|------------|---------|---------|
	| 00.00.01 | 15.10.2020 | (ABH)   | Primera version. |
	| 00.00.02 | 28.01.2022 | (HCR)   | Se añade corrección. |
	| 00.00.03 | 05.09.2023 | (HCR)   | Se añade contador por desborde  |
	| 00.00.04 | 18.09.2023 | (ABH)   | Se estandariza nombre variables para gestion de tiempo de ciclo para integracion de EA. |
	| 00.00.05 | 28.02.2024 | (ABH)   | Se añade gestion multiplexado para Scada (no indexado) |
	| 00.00.06 | 08.01.2025 | (ABH)   | Se añade entrada de primer arranque del PLC para resetear indices de multiplexado. Se añade movimiento de TOT[0] a multiplexado en caso de que el indice sea 0.     |
	| 00.00.07 | 24.01.2025 | (ABH)   | Se modifica la logica del multiplexado, la asignacion de oldindex se cambia de sitio ya que de vez en cuando (dependiendo de cuando el HMI realiza la comunicacion asincrona con el PLC) no detectaba el cambio y se machacaban valores. |
	| 01.00.00 | 28.07.2025 | (ABH)   | Se extrae el multiplexado a FC independiente. Se corrige reset de ordenes reset totalizadores en multiplexado. Se añade totalizador usuario. |
	*)
	END_REGION DESCRIPCION
	
	
	REGION TIEMPO_CICLO_INTEGRACION
	    
	    //  Tiempo ciclo para integración EA
	    "DB2025_TOT".TiempoCiclo.Actual := LREAL_TO_REAL(RUNTIME("DB2025_TOT".TiempoCiclo.auxMem));
	    "DB2025_TOT".TiempoCiclo.Actual := ("DB2025_TOT".TiempoCiclo.Actual * 1000.0);
	    
	END_REGION TIEMPO_CICLO_INTEGRACION
	
	
	REGION LOGICA_DEL_DISPOSITIVO
	    
	    //  Numero maximo de dispositivos para limitar en HMI/Scada
	    "DB2025_TOT".Mux.NumDispositivos := "N_MAX_DISP_TOT";
	    
	    REGION COMPROBACION_N_MAX
	        
	        //  Si no hay dispositivos declarados, salimos del bloque inmediatamente
	        IF "N_MAX_DISP_TOT" <= 0 THEN
	            RETURN;
	        END_IF;
	        
	        //  Traspaso de N_MAX a variable temporal para evitar fallos de compilacion
	        #t_N_MAX := "N_MAX_DISP_TOT";
	        
	    END_REGION COMPROBACION_N_MAX
	    
	    FOR #for_i := 1 TO #t_N_MAX DO
	        
	        //  Dispositivo habilitado
	        IF "DB2025_TOT".TOT[#for_i].Config_Habilitar THEN
	            
	            REGION LECTURA_VALORES
	                
	                REGION ENTRADA_PULSOS
	                    
	                    IF NOT #Simulacion THEN
	                        
	                        IF "DB2025_TOT".TOT[#for_i].Config_DireccionEntradaByte > 0 THEN
	                            
	                            //  Lectura de la entrada digital
	                            "DB2025_TOT".TOT[#for_i].Estado_Pulso := PEEK_BOOL(area := 16#81,
	                                                                               dbNumber := 0,
	                                                                               byteOffset := "DB2025_TOT".TOT[#for_i].Config_DireccionEntradaByte,
	                                                                               bitOffset := "DB2025_TOT".TOT[#for_i].Config_DireccionEntradaBit);
	                        END_IF;
	                    ELSE
	                        "DB2025_TOT".TOT[#for_i].Estado_Pulso := #Pulso100ms;
	                    END_IF;
	                    
	                END_REGION ENTRADA_PULSOS
	                
	                
	                REGION CAUDAL
	                    
	                    IF "DB2025_TOT".TOT[#for_i].Estado_Valor > 0.0 THEN
	                        #t_CaudalSegundos := (("DB2025_TOT".TOT[#for_i].Estado_Valor / 3600000.0 * "DB2025_TOT".TiempoCiclo.Actual));
	                    ELSE
	                        #t_CaudalSegundos := 0.0;
	                    END_IF;
	                    
	                END_REGION CAUDAL
	                
	
	                
	            END_REGION LECTURA_VALORES
	            
	            
	            REGION MODO_TRABAJO
	                
	                REGION TOTALIZADORES
	                    
	                    //  Orden activacion totalizadores
	                    IF "DB2025_TOT".TOT[#for_i].Orden_Activar THEN
	                        
	                        //  0 = TOTALIZADOR POR PULSOS
	                        //  1 = TOTALIZADOR POR EA
	                        IF NOT "DB2025_TOT".TOT[#for_i].Config_Tipo THEN    //  Caudalímetro por Pulso
	                            
	                            IF "DB2025_TOT".TOT[#for_i].Estado_Pulso AND NOT "DB2025_TOT".TOT[#for_i].Aux_oldActivado THEN
	                                
	                                IF NOT "DB2025_TOT".TOT[#for_i].Config_IncrDecr THEN
	                                    "DB2025_TOT".TOT[#for_i].Estado_Parcial += "DB2025_TOT".TOT[#for_i].Config_LitroXPulso;
	                                    "DB2025_TOT".TOT[#for_i].Estado_Total += "DB2025_TOT".TOT[#for_i].Config_LitroXPulso;
	                                    "DB2025_TOT".TOT[#for_i].Estado_Proceso += "DB2025_TOT".TOT[#for_i].Config_LitroXPulso;
	                                ELSE
	                                    "DB2025_TOT".TOT[#for_i].Estado_Parcial -= "DB2025_TOT".TOT[#for_i].Config_LitroXPulso;
	                                    "DB2025_TOT".TOT[#for_i].Estado_Total -= "DB2025_TOT".TOT[#for_i].Config_LitroXPulso;
	                                    "DB2025_TOT".TOT[#for_i].Estado_Proceso -= "DB2025_TOT".TOT[#for_i].Config_LitroXPulso;
	                                END_IF;
	                                
	                            END_IF;
	
	                            
	                        ELSE
	                            
	                            //  Totalizador por EA
	                            IF NOT "DB2025_TOT".TOT[#for_i].Config_IncrDecr THEN
	                                "DB2025_TOT".TOT[#for_i].Estado_Parcial += #t_CaudalSegundos;
	                                "DB2025_TOT".TOT[#for_i].Estado_Total += #t_CaudalSegundos;
	                                "DB2025_TOT".TOT[#for_i].Estado_Proceso += #t_CaudalSegundos;
	                            ELSE
	                                "DB2025_TOT".TOT[#for_i].Estado_Parcial -= #t_CaudalSegundos;
	                                "DB2025_TOT".TOT[#for_i].Estado_Total -= #t_CaudalSegundos;
	                                "DB2025_TOT".TOT[#for_i].Estado_Proceso -= #t_CaudalSegundos;
	                            END_IF;
	                            
	                        END_IF;
	                        
	                    END_IF;
	                    
	                END_REGION TOTALIZADORES
	                
	
	                REGION TOTALIZADOR_USUARIO
	                    
	                    IF "DB2025_TOT".TOT[#for_i].Orden_Usuario THEN
	                        
	                        //  0 = TOTALIZADOR POR PULSOS
	                        //  1 = TOTALIZADOR POR EA
	                        IF NOT "DB2025_TOT".TOT[#for_i].Config_Tipo THEN    //  Caudalímetro por Pulso
	                            
	                            IF "DB2025_TOT".TOT[#for_i].Estado_Pulso AND NOT "DB2025_TOT".TOT[#for_i].Aux_oldActivado THEN
	                                
	                                IF NOT "DB2025_TOT".TOT[#for_i].Config_IncrDecr THEN
	                                    "DB2025_TOT".TOT[#for_i].Estado_Usuario += "DB2025_TOT".TOT[#for_i].Config_LitroXPulso;
	                                ELSE
	                                    "DB2025_TOT".TOT[#for_i].Estado_Usuario -= "DB2025_TOT".TOT[#for_i].Config_LitroXPulso;
	                                END_IF;
	                                
	                            END_IF;
	                            
	                        ELSE
	                            
	                            //  Totalizador por EA
	                            IF NOT "DB2025_TOT".TOT[#for_i].Config_IncrDecr THEN
	                                "DB2025_TOT".TOT[#for_i].Estado_Usuario += #t_CaudalSegundos;
	                            ELSE
	                                "DB2025_TOT".TOT[#for_i].Estado_Usuario -= #t_CaudalSegundos;
	                            END_IF;
	                            
	                        END_IF;
	                        
	                    END_IF;
	                    
	                END_REGION TOTALIZADOR_USUARIO
	                
	                
	                REGION CALCULO_CAUDAL_TEORICO                    
	                    
	                    IF NOT "DB2025_TOT".TOT[#for_i].Config_Tipo AND "DB2025_TOT".TOT[#for_i].Orden_Activar THEN
	                        
	                        //  Calculo teorico L/H
	                        IF #Pulso100ms THEN
	                            "DB2025_TOT".TOT[#for_i].Aux_AcuTiempo += 1;
	                            
	                            IF "DB2025_TOT".TOT[#for_i].Aux_AcuTiempo > 10 THEN
	                                #t_LReal := "DB2025_TOT".TOT[#for_i].Estado_Total - "DB2025_TOT".TOT[#for_i].Aux_TotalizadorMem;
	                                
	                                "DB2025_TOT".TOT[#for_i].Estado_Valor := LREAL_TO_REAL(#t_LReal) * 36000.0 / "DB2025_TOT".TOT[#for_i].Aux_AcuTiempo;
	                                
	                                "DB2025_TOT".TOT[#for_i].Aux_AcuTiempo := 0;
	                                "DB2025_TOT".TOT[#for_i].Aux_TotalizadorMem := "DB2025_TOT".TOT[#for_i].Estado_Total;
	                            END_IF;
	                        END_IF;
	                        
	                    ELSIF NOT "DB2025_TOT".TOT[#for_i].Config_Tipo AND "DB2025_TOT".TOT[#for_i].Orden_Usuario THEN
	                        
	                        //  Calculo teorico L/H
	                        IF #Pulso100ms THEN
	                            "DB2025_TOT".TOT[#for_i].Aux_AcuTiempo += 1;
	                            
	                            IF "DB2025_TOT".TOT[#for_i].Aux_AcuTiempo > 10 THEN
	                                #t_LReal := "DB2025_TOT".TOT[#for_i].Estado_Usuario - "DB2025_TOT".TOT[#for_i].Aux_TotalizadorMem;
	                                
	                                "DB2025_TOT".TOT[#for_i].Estado_Valor := LREAL_TO_REAL(#t_LReal) * 36000.0 / "DB2025_TOT".TOT[#for_i].Aux_AcuTiempo;
	                                
	                                "DB2025_TOT".TOT[#for_i].Aux_AcuTiempo := 0;
	                                "DB2025_TOT".TOT[#for_i].Aux_TotalizadorMem := "DB2025_TOT".TOT[#for_i].Estado_Usuario;
	                            END_IF;
	                        END_IF;
	                        
	                    ELSE
	                        
	                        "DB2025_TOT".TOT[#for_i].Aux_TotalizadorMem := 0.0;
	                        
	                    END_IF;
	                    
	                END_REGION
	                
	                //  Estado anterior del pulso
	                "DB2025_TOT".TOT[#for_i].Aux_oldActivado := "DB2025_TOT".TOT[#for_i].Estado_Pulso;
	                
	            END_REGION MODO_TRABAJO
	            
	            
	            REGION DIAGNOSIS
	                
	                //  Reset totalizador total por desbordamiento
	                IF "DB2025_TOT".TOT[#for_i].Estado_Total >= #VALOR_MAXIMO_CONTADORES THEN
	                    "DB2025_TOT".TOT[#for_i].Estado_DesbordeTotal += 1;
	                    "DB2025_TOT".TOT[#for_i].Estado_Total := 0.0;
	                END_IF;
	                
	                //  Reset totalizador parcial por desbordamiento
	                IF "DB2025_TOT".TOT[#for_i].Estado_Parcial >= #VALOR_MAXIMO_CONTADORES THEN
	                    "DB2025_TOT".TOT[#for_i].Estado_DesbordeParcial += 1;
	                    "DB2025_TOT".TOT[#for_i].Estado_Parcial := 0.0;
	                END_IF;
	                
	                //  Reset totalizador usuario por desbordamiento
	                IF "DB2025_TOT".TOT[#for_i].Estado_Usuario >= #VALOR_MAXIMO_CONTADORES THEN
	                    "DB2025_TOT".TOT[#for_i].Estado_DesbordeUsuario += 1;
	                    "DB2025_TOT".TOT[#for_i].Estado_Usuario := 0.0;
	                END_IF;
	                
	                //  Reset totalizador parcial
	                IF "DB2025_TOT".TOT[#for_i].Orden_ResetParcial OR "DB2025_TOT".TOT[#for_i].Estado_DesbordeParcial > #NUMERO_MAXIMO_DESBORDE THEN
	                    "DB2025_TOT".TOT[#for_i].Estado_Parcial := 0.0;
	                    "DB2025_TOT".TOT[#for_i].Estado_DesbordeParcial := 0;
	                    "DB2025_TOT".TOT[#for_i].Orden_ResetParcial := FALSE;
	                END_IF;
	                
	                //  Reset totalizador total
	                IF "DB2025_TOT".TOT[#for_i].Orden_ResetTotal OR "DB2025_TOT".TOT[#for_i].Estado_DesbordeTotal > #NUMERO_MAXIMO_DESBORDE THEN
	                    "DB2025_TOT".TOT[#for_i].Estado_Total := 0.0;
	                    "DB2025_TOT".TOT[#for_i].Estado_DesbordeTotal := 0;
	                    "DB2025_TOT".TOT[#for_i].Orden_ResetTotal := FALSE;
	                END_IF;
	                
	                //  Reset totalizador proceso
	                IF "DB2025_TOT".TOT[#for_i].Orden_ResetProceso THEN
	                    "DB2025_TOT".TOT[#for_i].Estado_Proceso := 0.0;
	                    "DB2025_TOT".TOT[#for_i].Orden_ResetProceso := FALSE;
	                END_IF;
	                
	                //  Reset totalizador usuario
	                IF "DB2025_TOT".TOT[#for_i].Orden_ResetUsuario THEN
	                    "DB2025_TOT".TOT[#for_i].Estado_Usuario := 0.0;
	                    "DB2025_TOT".TOT[#for_i].Orden_ResetUsuario := FALSE;
	                END_IF;
	                
	            END_REGION
	            
	        ELSE
	            
	            //  Dispositivo no habilitado
	            "DB2025_TOT".TOT[#for_i].Orden_Activar := FALSE;
	            "DB2025_TOT".TOT[#for_i].Orden_ResetParcial := FALSE;
	            "DB2025_TOT".TOT[#for_i].Orden_ResetProceso := FALSE;
	            "DB2025_TOT".TOT[#for_i].Orden_ResetTotal := FALSE;
	            "DB2025_TOT".TOT[#for_i].Orden_ResetUsuario := FALSE;
	            "DB2025_TOT".TOT[#for_i].Estado_Valor := 0.0;
	            "DB2025_TOT".TOT[#for_i].Estado_Parcial := 0.0;
	            "DB2025_TOT".TOT[#for_i].Estado_Total := 0.0;
	            "DB2025_TOT".TOT[#for_i].Estado_Proceso := 0.0;
	            "DB2025_TOT".TOT[#for_i].Estado_Usuario := 0.0;
	            
	        END_IF;
	        
	    END_FOR;
	    
	END_REGION
	
END_FUNCTION

