FUNCTION_BLOCK "FB15152_ZC_PARO_SEC"
TITLE = FB15152_ZC_PARO_SEC
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : ABH
FAMILY : ZeusControl
VERSION : 1.0
//Bloque de funcion para la gestion de paro secuencial de dispositivos
   VAR_INPUT 
      Pulso1Seg : Bool;   // Pulso de 1 segundo
      MarchaSecuenciaParo : Bool;   // Orden marcha secuencia de paro
      NumeroDispositivos { S7_HiddenAssignment := 'HideIfNoParamAssigned'} : Int := 4;   // Numero de dispositivos
      TiempoEntreEtapas { S7_HiddenAssignment := 'HideIfNoParamAssigned'} : Int := 2;   // Tiempo entre etapas
      ConfirmacionDisp { S7_HiddenAssignment := 'HideIfNoParamAssigned'} : Array[1..#N_MAX] of Bool;   // Confirmacion activado dispositivo
   END_VAR

   VAR_OUTPUT 
      SecParoFinalizada : Bool;   // Secuencia de paro completada
      SecReposo : Bool;   // Secuencia en reposo
      ParoDispositivo { S7_HiddenAssignment := 'HideIfNoParamAssigned'} : Array[1..#N_MAX] of Bool;   // Orden paro dispositivo
   END_VAR

   VAR 
      s_NumeroDispositivos { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Int;   // Numero de dispositivos
   END_VAR
   VAR RETAIN
      s_EtapaActual { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Int;   // Etapa actual
      s_Indice { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Int;   // Indice dispositivo actual
      s_TiempoActual { ExternalAccessible := 'False'; ExternalVisible := 'False'; ExternalWritable := 'False'} : Int;   // Tiempo actual
   END_VAR

   VAR_TEMP 
      for_i : Int;
   END_VAR

   VAR CONSTANT 
      N_MAX : Int := 8;
      REPOSO : Int := 0;
      PARANDO : Int := 10;
      REARMANDO : Int := 20;
      FINALIZADO : Int := 30;
   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 `FB15051_PARO_SECUENCIAL` implementa un secuenciador bidireccional para el paro ordenado de un array de dispositivos. Permite detener los dispositivos uno a uno esperando una confirmación por etapa, y rearmar la secuencia en sentido inverso si se retira la orden de marcha, continuando desde la última etapa alcanzada en lugar de reiniciar desde el inicio.
	
	Funcionalidades principales:
	- Validación del número de dispositivos: `#NumeroDispositivos` se acota entre 1 y la constante de dimensionamiento `#N_MAX`.
	- Estado `REPOSO`: bloque en espera. Al recibir `#MarchaSecuenciaParo := TRUE` se inicializa el índice y se transita a la etapa `PARANDO`.
	- Estado `PARANDO`: avanza secuencialmente por los dispositivos de menor a mayor índice. Cada dispositivo se considera parado cuando se cumple `#ConfirmacionDisp[i] = TRUE` durante `#TiempoEntreEtapas` segundos. Al confirmar, se incrementa el índice hasta superar `#s_NumeroDispositivos`, momento en el que se transita a `FINALIZADO`. Si durante esta etapa se desactiva `#MarchaSecuenciaParo`, se invierte el sentido y se pasa a `REARMANDO` sin perder la posición alcanzada.
	- Estado `REARMANDO`: retrocede secuencialmente por los dispositivos de mayor a menor índice. Cada dispositivo se considera rearmado cuando `#ConfirmacionDisp[i] = FALSE` durante `#TiempoEntreEtapas` segundos. Al rearmar, se decrementa el índice hasta alcanzar 1, momento en el que se transita a `REPOSO`. Si durante esta etapa se vuelve a activar `#MarchaSecuenciaParo`, se invierte nuevamente el sentido y se pasa a `PARANDO` desde la posición actual.
	- Estado `FINALIZADO`: secuencia de paro completa. Al retirar `#MarchaSecuenciaParo`, se rearman los dispositivos en orden inverso empezando desde el último, transitando a `REARMANDO`.
	- Mapeado dinámico de salidas: para cada dispositivo se activa `#ParoDispositivo[i] := TRUE` si su índice es menor o igual al puntero actual y la etapa es `PARANDO`; en el resto de casos se mantiene en `FALSE`. Esto garantiza que los dispositivos ya parados en la fase actual permanezcan parados durante el rearmado inverso de los posteriores.
	- Señales de estado: `#SecReposo` indica que la secuencia está en `REPOSO` y `#SecParoFinalizada` que ha terminado (`FINALIZADO`).
	- Contadores de tiempo por etapa del tipo `ZC_TIEMPO_ETAPA` con base de tiempo en segundos (`#Pulso1Seg`).
	
	> **Restricciones:** PLC serie 1200/1500.
	
	---
	### Dependencias Requeridas
	| Tipo | Elementos |
	|------|-----------|
	| FC   | - |
	| FB   | - |
	| DB   | - |
	| UDT  | `ZC_TIEMPO_ETAPA` |
	
	---
	### Historial de Cambios
	| Versión  | Fecha      | Técnico | Cambios |
	|----------|------------|---------|---------|
	| 00.00.01 | 24.05.2023 | (ABH)   | Primera versión. |
	| 00.00.02 | 15.12.2023 | (HCR)   | Se aumentan los dispositivos de 4 a 8. |
	| 00.00.03 | 18.12.2023 | (ABH)   | Se cambia UDT tiempo etapa a `ZC_TIEMPO_ETAPA`. Se añade salida `SecReposo` para indicar que está en reposo. Se añade gestión de etapas para que, en caso de estar con orden de paro y se dé marcha, siga la secuencia donde le toque evitando tener que pararla por completo y volverla a arrancar. |
	| 01.00.00 | 03.07.2026 | (ABH)   | Cambio a SCL. Se refactoriza logicas para estandarizar. Se utiliza constante N_MAX para redimensionar. |
	*)
	END_REGION DESCRIPCION
	
	
	// ==========================================================================================================
	REGION LIMITES
	    
	    #s_NumeroDispositivos := #NumeroDispositivos;
	    
	    // Limite de seguridad
	    IF #s_NumeroDispositivos > #N_MAX THEN
	        #s_NumeroDispositivos := #N_MAX;
	    ELSIF #s_NumeroDispositivos < 1 THEN
	        #s_NumeroDispositivos := 1;
	    END_IF;
	    
	END_REGION LIMITES
	
	
	// ==========================================================================================================
	REGION SECUENCIADOR_BIDIRECCIONAL
	    
	    CASE #s_EtapaActual OF
	            
	        #REPOSO:
	            #s_Indice := 1;
	            #s_TiempoActual := 0;
	            
	            IF #MarchaSecuenciaParo THEN
	                #s_EtapaActual := #PARANDO;
	            END_IF;
	            
	        #PARANDO:
	            
	            //  Contador de tiempo con confirmacion de dispositivo
	            IF #ConfirmacionDisp[#s_Indice] AND #Pulso1Seg THEN
	                #s_TiempoActual += 1;
	            END_IF;
	            
	            //  Tiempo cumplido
	            IF (#s_TiempoActual >= #TiempoEntreEtapas) THEN
	                #s_TiempoActual := 0;
	                #s_Indice += 1; //  Avanzamos al siguiente dispositivo
	                
	                //  Comprobación de fin de secuencia de paro
	                IF #s_Indice > #s_NumeroDispositivos THEN
	                    #s_EtapaActual := #FINALIZADO;
	                END_IF;
	            END_IF;
	            
	            //  Si se desactiva la orden de marcha de la secuencia, comenzamos el rearme inverso
	            IF NOT #MarchaSecuenciaParo THEN
	                #s_TiempoActual := 0;
	                #s_EtapaActual := #REARMANDO;
	            END_IF;
	            
	            
	        #REARMANDO:
	            
	            //  Límite de seguridad inferior
	            IF #s_Indice < 1 THEN
	                #s_EtapaActual := #REPOSO;
	                RETURN;
	            END_IF;
	            
	            //  Contador de tiempo sin confirmacion de dispositivo
	            IF NOT #ConfirmacionDisp[#s_Indice] AND #Pulso1Seg THEN
	                #s_TiempoActual += 1;
	            END_IF;
	            
	            //  Tiempo cumplido
	            IF (#s_TiempoActual >= #TiempoEntreEtapas) THEN
	                #s_TiempoActual := 0;
	                #s_Indice -= 1; //  Avanzamos al dispositivo anterior
	                
	                IF #s_Indice < 1 THEN
	                    #s_EtapaActual := #REPOSO;
	                END_IF;
	            END_IF;
	            
	            //  Si se vuelve a dar marcha, invertimos dirección nuevamente
	            IF #MarchaSecuenciaParo THEN
	                #s_TiempoActual := 0;
	                #s_EtapaActual := #PARANDO;
	            END_IF;
	            
	            
	        #FINALIZADO:
	            
	            IF NOT #MarchaSecuenciaParo THEN
	                #s_TiempoActual := 0;
	                //  Empezamos a rearmar desde el último dispositivo que se paro
	                #s_Indice := #s_NumeroDispositivos;
	                #s_EtapaActual := #REARMANDO;
	            END_IF;
	            
	        ELSE
	            #s_EtapaActual := #REPOSO;
	    END_CASE;
	    
	END_REGION SECUENCIADOR_BIDIRECCIONAL
	
	
	// ==========================================================================================================
	REGION MAPEADO_DINAMICO_DE_SALIDAS
	    
	    #SecReposo := (#s_EtapaActual = #REPOSO);
	    #SecParoFinalizada := (#s_EtapaActual = #FINALIZADO);
	    
	    //  Mapeo vectorial de comandos físicos
	    FOR #for_i := 1 TO #N_MAX DO
	        
	        //  Si el índice iterativo es menor que nuestro puntero de etapa actual, 
	        //  significa que ya hemos pasado por él en la secuencia de paro y debe estar detenido.
	        IF #for_i < #s_Indice THEN
	            #ParoDispositivo[#for_i] := TRUE;
	            
	            //  Si el índice coincide con el puntero y estamos parando, forzamos la salida a TRUE
	        ELSIF (#for_i = #s_Indice) AND (#s_EtapaActual = #PARANDO) THEN
	            #ParoDispositivo[#for_i] := TRUE;
	            
	            //  Para el resto de condiciones
	        ELSE
	            #ParoDispositivo[#for_i] := FALSE;
	        END_IF;
	        
	    END_FOR;
	    
	END_REGION MAPEADO_DINAMICO_DE_SALIDAS
	
END_FUNCTION_BLOCK

