FUNCTION_BLOCK "FB15105_ZC_CALCULO_CURVA"
TITLE = FB15105_ZC_CALCULO_CURVA
{ S7_Optimized_Access := 'TRUE' }
AUTHOR : ABH
FAMILY : ZeusControl
VERSION : 1.0
//Bloque de funcion para calculo curva de 10 puntos maximo.
   VAR_INPUT 
      ValorActual : Real;   // Valor actual
      NumeroPuntos : Int := 10;   // Numero de puntos
   END_VAR

   VAR_OUTPUT 
      Valor : Real;   // Valor resultante curva
   END_VAR

   VAR 
      s_ValorActual : Real;
      s_Valor : Real;
      s_NumeroPuntos : Int;
   END_VAR
   VAR RETAIN
      s_PuntoX : Array[1..#MAX_PUNTOS] of Real := [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0];   // Valor X
      s_PuntoY : Array[1..#MAX_PUNTOS] of Real := [10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0];   // Valor Y
   END_VAR

   VAR_TEMP 
      for_i : Int;
      dx : Real;
      dy : Real;
   END_VAR

   VAR CONSTANT 
      MAX_PUNTOS : Int := 10;   // Numero maximo de puntos
   END_VAR


BEGIN
	REGION DESCRIPCION
	//=======================================================================
	// (C)Copyright ZeusControl 2023 - 2023
	//=======================================================================
	(*
	### Información del Sistema
	| Hardware Compatible | Entorno de Ingeniería |
	|---------------------|-----------------------|
	| PLC serie 1200/1500 | TIA Portal 18         |
	
	---
	### Descripción Funcional
	El bloque `FB15105_ZC_CALCULO_CURVA` implementa una función de interpolación lineal por tramos sobre una curva configurable de hasta `#MAX_PUNTOS` puntos `(X, Y)`. A partir de un valor de entrada `ValorActual`, devuelve el valor de salida interpolado `Valor`, permitiendo linealizar señales no lineales como sondas de nivel, curvas de bombas o perfiles de consigna.
	
	Funcionalidades principales:
	- Validación del número de puntos activos: `#NumeroPuntos` se acota entre 2 (mínimo necesario para definir un segmento) y la constante de dimensionamiento `#MAX_PUNTOS`.
	- Relleno automático: los puntos no utilizados por encima de `#s_NumeroPuntos` se rellenan con el último valor válido, manteniendo la coherencia del array para los cálculos posteriores.
	- Corrección de monotonía: se fuerza la coordenada `X` a ser monótona creciente o igual hacia adelante, evitando inversiones de tramo que producirían divisiones por `dx` negativas o nulas.
	- Saturación de entrada: si `#ValorActual` está por debajo del primer punto, la salida toma directamente el primer `Y`; si está por encima del último, toma el último `Y`, abortando el resto del escaneo para optimizar el tiempo de ciclo.
	- Búsqueda del segmento activo: recorre los `#MAX_PUNTOS - 1` segmentos y detecta aquel en el que se encuentra el valor de entrada.
	- Interpolación lineal: calcula los deltas `dx` y `dy` del segmento y aplica la ecuación `Y = (X - X1) * (dY / dX) + Y1`. En caso de `dx` nulo (tramo vertical o degenerado) se asigna directamente el `Y` del extremo inferior para evitar división por cero.
	- Publicación del resultado en la salida `#Valor` una vez finalizado el cálculo.
	
	> **Restricciones:** Ninguna.
	
	---
	### Dependencias Requeridas
	| Tipo | Elementos |
	|------|-----------|
	| FC   | - |
	| FB   | - |
	| DB   | - |
	| UDT  | - |
	    
	---
	### Historial de Cambios
	| Versión  | Fecha      | Técnico | Cambios |
	|----------|------------|---------|---------|
	| 01.00.00 | 02.10.2023 | (ABH)   | Primera versión. |
	*)
	END_REGION DESCRIPCION
	
	
	// ===============================================================================================================
	REGION LIMITES_VALORES
	    
	    #s_ValorActual := #ValorActual;
	    #s_NumeroPuntos := #NumeroPuntos;
	    
	    //  Límite en configuración de número de puntos activos
	    IF #s_NumeroPuntos < 2 THEN
	        #s_NumeroPuntos := 2; //    Una curva necesita mínimo 2 puntos (X1, Y1) a (X2, Y2)
	    ELSIF #s_NumeroPuntos > #MAX_PUNTOS THEN
	        #s_NumeroPuntos := #MAX_PUNTOS;
	    END_IF;
	    
	    //  Relleno automático de los puntos no utilizados con el último valor válido
	    FOR #for_i := #s_NumeroPuntos TO #MAX_PUNTOS DO
	        #s_PuntoX[#for_i] := #s_PuntoX[#s_NumeroPuntos];
	        #s_PuntoY[#for_i] := #s_PuntoY[#s_NumeroPuntos];
	    END_FOR;
	    
	    //  Corrección de monotonía (Asegurar que X siempre sea creciente o igual)
	    FOR #for_i := #MAX_PUNTOS TO 2 BY -1 DO
	        IF #s_PuntoX[#for_i] < #s_PuntoX[#for_i - 1] THEN
	            #s_PuntoX[#for_i] := #s_PuntoX[#for_i - 1];
	        END_IF;
	    END_FOR;
	    
	    //  Saturación del valor de entrada a los extremos de la curva configurada
	    IF #s_ValorActual <= #s_PuntoX[1] THEN
	        #s_ValorActual := #s_PuntoX[1];
	        #s_Valor := #s_PuntoY[1];
	        #Valor := #s_Valor;
	        RETURN; //  Si está por debajo, asignamos y abortamos scan (Optimización)
	        
	    ELSIF #s_ValorActual >= #s_PuntoX[#MAX_PUNTOS] THEN
	        #s_ValorActual := #s_PuntoX[#MAX_PUNTOS];
	        #s_Valor := #s_PuntoY[#MAX_PUNTOS];
	        #Valor := #s_Valor;
	        RETURN; //  Si está por encima, asignamos y abortamos scan
	    END_IF;
	    
	END_REGION LIMITES_VALORES
	
	
	// ===============================================================================================================
	REGION CALCULO_SALIDA
	    
	    //  Búsqueda del segmento activo e interpolación lineal
	    FOR #for_i := 1 TO (#MAX_PUNTOS - 1) DO
	        
	        IF (#s_ValorActual >= #s_PuntoX[#for_i]) AND (#s_ValorActual <= #s_PuntoX[#for_i + 1]) THEN
	            
	            //  Cálculo de deltas
	            #dx := #s_PuntoX[#for_i + 1] - #s_PuntoX[#for_i];
	            #dy := #s_PuntoY[#for_i + 1] - #s_PuntoY[#for_i];
	            
	            //  Proteccion por división por Cero
	            IF #dx <= 0.0 THEN
	                #s_Valor := #s_PuntoY[#for_i]; // Tramo vertical o nulo
	            ELSE
	                //  Ecuación de la recta: Y = (X - X1) * (dY / dX) + Y1
	                #s_Valor := ((#s_ValorActual - #s_PuntoX[#for_i]) * (#dy / #dx)) + #s_PuntoY[#for_i];
	            END_IF;
	            
	            //  Segmento encontrado y calculado. Cortamos el bucle inmediatamente.
	            EXIT;
	        END_IF;
	        
	    END_FOR;
	    
	END_REGION CALCULO_SALIDA
	
	
	// ===============================================================================================================
	REGION SALIDA
	    
	    #Valor := #s_Valor;
	    
	END_REGION SALIDA
	
	
END_FUNCTION_BLOCK

