{
    S7_Author := "ABH";
    S7_BlockComment := "MLC_Qe";
    S7_BlockNumber := "15105";
    S7_BlockTitle := "MLC_4rc";
    S7_EditorMode := "SCL";
    S7_Family := "ZeusControl";
    S7_IECCheck := "TRUE";
    S7_Optimized := "TRUE";
    S7_Version := "1.0"
}
FUNCTION_BLOCK "FB15105_ZC_CALCULO_CURVA"
    VAR_INPUT
        { S7_MLC := "MLC_5Bz" }
        ValorActual : Real;
        { S7_MLC := "MLC_tn" }
        NumeroPuntos : Int := 10;
    END_VAR
    VAR_OUTPUT
        { S7_MLC := "MLC_3eH" }
        Valor : Real;
    END_VAR
    VAR
        s_ValorActual : Real;
        s_Valor : Real;
        s_NumeroPuntos : Int;
    END_VAR
    VAR RETAIN
        { S7_MLC := "MLC_4k7" }
        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
        ];
        { S7_MLC := "MLC_4Nw" }
        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
        ];
    END_VAR
    VAR_TEMP
        for_i : Int;
        dx : Real;
        dy : Real;
    END_VAR
    VAR CONSTANT
        { S7_MLC := "MLC_WH" }
        MAX_PUNTOS : Int := 10;
    END_VAR

    { S7_Language := "SCL" }
    NETWORK
        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_NETWORK
END_FUNCTION_BLOCK
