Функции (формулы) для Rapid SCADA

Просмотр 15 сообщений - с 1 по 15 (из 29 всего)
  • Автор
    Сообщения
  • #3862
    manjey73
    Участник

    См. ниже…

    • Эта тема была изменена 4 года, 10 месяцев назад от Mikhail.
    • Эта тема была изменена 4 года, 10 месяцев назад от Mikhail.
    #3867
    manjey73
    Участник

    Конечный вариант функции Toggle

    Исходный код:

    int[] toggleN = new int[1];
    bool[] toggleB = new bool[1];
    bool Toggle (double clk)
    {
    bool q = Val(CnlNum) > 0;
    bool b = clk > 0;

    int res = Array.IndexOf(toggleN, CnlNum);
    if (res == -1)
    {
    res = toggleN.Length;
    Array.Resize(ref toggleN, res+1);
    Array.Resize(ref toggleB, res+1);
    toggleN[res] = CnlNum;
    }

    bool edge = toggleB[res];
    if (b && !edge) q = !q;
    toggleB[res] = b;
    return q;
    }

    Вызов функции, Исп.формулу Toggle(Val(№ канала сигнала))

    Другие переменные, кроме проверяемого канала не нужны.

    #3868
    manjey73
    Участник

    Ну и RS Trigger только с двумя входными переменными SET и Reset

    bool RStrig (double set, double res)
    {
    bool s = set > 0;
    bool r = res > 0;
    bool q = r ? q = false : Val(CnlNum) > 0 || s;
    return q ;
    }

    Исп. формулу — RStrig(Val(№ канала set), Val(№ канала reset))

    #3869
    manjey73
    Участник

    Функция масштабирования сигнала с плавающей точкой

    Наименование — Scale_R
    Исходный код

    double Scaler (double input, double in_min, double in_max, double out_min, double out_max)

    {
    double out1 = 0;
    double out2 = 0;
    double output = 0;
    double diff = in_max — in_min;

    if (diff != 0)
    {
    if (input > in_max) out1 = in_max;
    else out1 = input;
    if (in_min > out1) out2 = in_min;
    else out2 = out1;
    output = (out_max — out_min) / diff * (out2 — in_min) + out_min;
    }
    return output;
    }

    Пример: Исп.формулу — Scaler(Val(№ канала измерения), 4,20,0.3,100.5)
    4 — input min
    20 — input max
    0.3 — output min
    100.5 — output max
    Сигнал диапазона 4-20 будет масштабирован в диапазон 0.3 — 100.5

    Если in_min будет равен in_max то на выходе получим 0
    Если больше, то на выходе будет output min.
    Если сигнал будет выходить за пределы input min и input max то будут приняты данные значения и соответственно на выходе будет output min или output max соответственно.

    • Этот ответ был изменен 4 года, 10 месяцев назад от manjey73.
    • Этот ответ был изменен 4 года, 10 месяцев назад от manjey73.
    #3971
    manjey73
    Участник

    Функция возвращает количество тиков в миллисекундах.
    Собственно функция стандартная из C#.

    Наименование — Ticks
    Исходный код

    public static long Ticks()
    {
    DateTime now = DateTime.Now;
    long time = now.Ticks/10000;
    return time;
    }

    Пример смотреть ниже в следующей формуле (вызывается непосредственно из формулы)

    #3972
    manjey73
    Участник

    Функция среднего плавающим окном, для работы требуется наличие Ticks

    Наименование — Average
    Исходный код

    int [] aN = new int[1];
    long [] at = new long[1];
    float[] aD = new float[8];
    double Average (double val, Int64 tx)
    {
    float q = Convert.ToSingle(val); float s=0; float a;
    float [] ar = new float[8];
    long t2 = Ticks();
    int rd = 8;
    int r = 1;
    r = Array.IndexOf(aN, CnlNum);
    if (r == -1)
    {
    r = aN.Length;
    Array.Resize(ref aN, r+1);
    Array.Resize(ref aD, r*rd);
    Array.Resize(ref at, r+1);
    aN[r] = CnlNum;
    at[r] = Ticks();
    aD[r*rd-8] = q;
    }
    long t1 = at[r];
    long df = t2 — t1;
    Array.Copy (aD, r*rd-8, ar, 0, 8);
    if (df >= tx)
    {
    at[r] = Ticks();
    for (int i = 7; i > 0; i—)
    {
    ar[i]= ar[i-1];
    }
    ar[0] = q;
    }
    int p = Array.IndexOf(ar, 0);
    p-=1;
    if (p<0) p=7;
    for (int i = 0; i <= p; i++)
    {
    s = s + ar[i];
    }
    a = s / (p + 1);
    Array.Copy (ar, 0, aD, r*rd-8, 8);
    return a;
    }

    Пример Дорасчетный, Исп.формулу — Average(Val(№ канала), фильтр)
    где фильтр например = 500 — время в миллисекундах, после которого заносится значение в массив для измерения. Average(Val(310), 500)

    Можно сразу сделать округление значения, если необходимо, тогда формула будет выглядеть так Math.Round((Average(Val(309), 1000)),1)

    p.s. Не знаю зачем делал, так, чтобы было переделал кем-то написанный макрос из Овен ПР200, с FBD на С#

    #4024
    Mikhail
    Модератор
    // Словарь значений каналов
    Dictionary<int, double> CnlValDict = new Dictionary<int, double>();
    
    // Получить значение канала
    double CnlValGet(int cnlNum)
    {
        double val;
        return CnlValDict.TryGetValue(cnlNum, out val) ?
            val : Val();
    }
    
    // Установить значение канала
    double CnlValSet(int cnlNum)
    {
        CnlValDict[cnlNum] = Cmd;
        return double.NaN;
    }
    
    // Вывод в лог
    void WriteToLog(string s)
    {
      using (System.IO.StreamWriter writer = 
        new System.IO.StreamWriter(@"C:\SCADA\mylog.log", true, Encoding.UTF8))
      {
        writer.WriteLine(s);
      }
    }
    
    // Отправка команды для модуля ОВЕН
    double OWEN_Cmd(double cmdVal)
    {
      return cmdVal > 0 ? 
        BitConverter.ToDouble(new byte[] { 0xFF, 0, 0, 0, 0, 0, 0, 0 }, 0) :
        0;
    }
    
    #4060
    manjey73
    Участник

    Функция сброса канала Дорасчетный ТИ в 0.
    Например для сброса RS триггера от кнопки управления или подачи команды, после сброса триггера канал переводится в 0. Эдакий Rtrigger, импульс логической единицы.
    Требуется наличие формулы CnlValGetSet постом выше от Михаила (первый код).

    Наименование — ResKey
    Исходный код

    double ResKey()
    {
    double val = CnlValGet(CnlNum);
    bool key = Val(CnlNum) > 0;
    if (key) CnlValDict[CnlNum] = 0;
    return CnlValGet(CnlNum);
    }

    Пример Дорасчетный, Исп.формулу — ResKey()
    Устанавливать желательно после RS триггера.
    Канал можно потом не отображать в таблице, команду управления вешать непосредственно на канал RS триггера.
    Создаем канал управления, пишем в нем формулу CnlValSet(№ канала с формулой ResKey)
    Номер канала управления прописываем в канале RS триггера.

    В табличном представлении пока необходимо использовать команду Стандартная, Откл-Вкл и использовать кнопку Включено.
    Как поправят в очередном релизе команды, можно будет использовать команду Вкл с одной кнопкой. Ну или сделать по аналогии одну кнопку с надписью «Квитирование» например.

    В схеме не проверял.

    #5012
    Mikhail
    Модератор

    Переставить байты для отправки стандартной команды по Модбас. Передаётся значение из 2 байт.

    double SwapCmd()
    {
      ushort val = (ushort)Cmd;
      byte[] buf = BitConverter.GetBytes(val);
      return BitConverter.ToDouble(new byte[] { buf[1], buf[0], 0, 0, 0, 0, 0, 0 }, 0);
    }
    
    #5856
    manjey73
    Участник

    Инвертирование bool переменной для обработки и записи. Например при использовании с Raspberry релейной платы, которая управляется через минус. (0 — реле включено, 1 — реле выключено). Чтобы в таблице показывалось наоборот 0-выкл, 1 -включено.
    А так же, чтобы подавать команды Откл — Вкл так же наоборот.

    public double NotValue(double val)
    {
        bool boolVal = val > 0;
        return Convert.ToDouble(!boolVal);
    }

    Во входном канале используем формулу NotValue(CnlVal), в канале управления соответственно NotValue(CmdVal)

    #5936
    manjey73
    Участник

    Фигово, что нельзя так же формулу прописать в Модуле Авт. управления……. ладно, погнали дальше…

    Таймер задержки на включение Ton

    int[] TonNum = new int[1];
    long[] TonST = new long[1];
    bool[] TonFlag = new bool[1];
    public bool Ton(double TonIn, long TonPT)
    {
    long ET = 0L;
    bool q = Val(CnlNum) > 0;
    bool ton_in = TonIn > 0;

    int res = Array.IndexOf(TonNum, CnlNum);
    if (res == -1)
    {
    res = TonNum.Length;
    Array.Resize(ref TonNum, res+1);
    Array.Resize(ref TonST, res+1);
    Array.Resize(ref TonFlag, res+1);
    TonNum[res] = CnlNum;
    }

    if (!ton_in)
    {
    q = false;
    TonFlag[res] = false;
    TonST[res] = 0L;
    }
    else
    {
    if (!TonFlag[res])
    {
    TonFlag[res] = true;
    TonST[res] = Ticks();
    }
    else
    {
    if (!q) ET = Ticks() — TonST[res];
    }
    if (ET >= TonPT) q = true;
    }
    return q;
    }

    Формула простая Ton(значение канала в double, время в миллисекундах)
    Например в канале 152 используем формулу Ton(Val(150), 5000) — при появлении 1 на входном канале 150, появится единица на канале 152 (собственно сам Ton),

    Забыл, для работы нужна еще формула Ticks

    • Этот ответ был изменен 4 года, 4 месяца назад от manjey73.
    • Этот ответ был изменен 4 года, 4 месяца назад от manjey73.
    #5979
    manjey73
    Участник

    Лучше в качестве выходного значения иметь переменную double, тогда формулы можны писать внутри других формул без дополнительных телодвижений, так как они прописываются в самой формуле. Например на примере Ton

    Замените public bool Ton(double TonIn, long TonPT) на public double Ton(double TonIn, long TonPT) и вместо return q; пропишите return Convert.ToDouble(q);

    Дальше — Гистерезис

    int[] HysNum = new int[1];
    bool[] Hys = new bool[1];
    public double Hysteresis(double inCnl, double low, double high)
    {
    bool q = Val(CnlNum) > 0;
    int res = Array.IndexOf(HysNum, CnlNum);
    if (res == -1)
    {
    res = HysNum.Length;
    Array.Resize(ref HysNum, res+1);
    Array.Resize(ref Hys, res+1);
    HysNum[res] = CnlNum;
    Hys[res] = q;
    }
    if (inCnl < low) Hys[res] = true;
    if (inCnl > high) Hys[res] = false;
    return Convert.ToDouble(Hys[res]);
    }

    Вызов формулы Hysteresis(Val(414),23.5,25.7) номер канала значения аналогового датчика например и нижний, верхний предел.

    #6277
    manjey73
    Участник

    TOF — таймер с задержкой на выключение

    int[] TofNum = new int[1];
    long[] TofST = new long[1];
    bool[] TofFlag = new bool[1];
    public double Tof(double TofIn, long TofPT)
    {
    long ET = 0L;
    bool q = Val(CnlNum) > 0;
    bool tof_in = TofIn > 0;

    int res = Array.IndexOf(TofNum, CnlNum);
    if (res == -1)
    {
    res = TofNum.Length;
    Array.Resize(ref TofNum, res+1);
    Array.Resize(ref TofST, res+1);
    Array.Resize(ref TofFlag, res+1);
    TofNum[res] = CnlNum;
    }

    if (tof_in)
    {
    q = true;
    TofFlag[res] = true;
    TofST[res] = 0L;
    ET = 0L;
    }
    else
    {
    if (TofFlag[res])
    {
    TofFlag[res] = false;
    TofST[res] = Ticks();
    ET = 0L;
    }
    else
    {
    if (q) ET = Ticks() — TofST[res];
    }
    if (ET >= TofPT) q = false;
    }
    return Convert.ToDouble(q);
    }

    Вызов формулы Tof(Val(414),10000) номер контролируемого канала, время в миллисекундах.
    Для работы требуется формула Ticks

    • Этот ответ был изменен 4 года, 4 месяца назад от manjey73.
    #6290
    manjey73
    Участник

    Ftrig — появление импульса (лог 1) в канале по спаду контролируемого сигнала
    Можно отловить модулем автоматического управления, визуализация бывает не показывает.
    ———————————————

    int[] FtrigN = new int[1];
    bool[] FtrigM = new bool[1];
    double Ftrig (double clk)
    {
    bool q = Val(CnlNum) > 0;
    bool c = clk > 0;
    int res = Array.IndexOf(FtrigN, CnlNum);
    if (res == -1)
    {
    res = FtrigN.Length;
    Array.Resize(ref FtrigN, res+1);
    Array.Resize(ref FtrigM, res+1);
    FtrigN[res] = CnlNum;
    FtrigM[res] = true;
    }

    q = !c && !FtrigM[res];
    FtrigM[res] = !c;
    return Convert.ToDouble(q);
    }

    Собственно применение, канал 187 — использование формулы — Ftrig(Val(186)) — 186 — номер контролируемого сигнала. Модулем ловим 187-й канал.

    • Этот ответ был изменен 4 года, 4 месяца назад от manjey73.
    #6338
    manjey73
    Участник

    Tp — Таймер импульса заданной длительности, для работы требуется Ticks

    __________________________________________________________
    int[] TpNum = new int[1];
    long[] TpST = new long[1];
    bool[] TpFlag = new bool[1];
    public double Tp(double TpIn, long TpPT)
    {
    long ET = 0L;
    bool q = Val(CnlNum) > 0;
    bool tp_in = TpIn > 0;

    int res = Array.IndexOf(TpNum, CnlNum);
    if (res == -1)
    {
    res = TpNum.Length;
    Array.Resize(ref TpNum, res+1);
    Array.Resize(ref TpST, res+1);
    Array.Resize(ref TpFlag, res+1);
    TpNum[res] = CnlNum;
    }

    if (!TpFlag[res])
    {
    if (tp_in)
    {
    TpFlag[res] = true;
    TpST[res] = Ticks();
    if (ET < TpPT) q = true;
    }
    }
    else
    {
    if (q)
    {
    ET = Ticks() — TpST[res];
    if (ET >= TpPT) q = false;
    }
    else
    {
    if(!tp_in)
    {
    TpFlag[res] = false;
    ET = 0L;
    }
    }
    }
    return Convert.ToDouble(q);
    }

    ___________________________________________________________

    Вызов формулы Tp(Val(414),10000) номер контролируемого канала 414, время в миллисекундах.
    При появлении 1 в канале 414 включится выход таймера, по истечении 10 секунд выключится для данного примера. Таймер начинает работать по фронту сигнала, не требуется удержание 1 в контролируемом канале.

    з.ы. во всех таймерах время в мс задается переменной long, надо бы переделать на double и в long переводить уже внутри формулы. Упростит например запись в формулах, когда для множества таймеров одно и то же время PT, так как его можно брать из дополнительного канала.
    сейчас приходится делать так Tp(Val(414),Convert.ToInt64(Val(1)))
    в 1-й канал можно вводить время задержки командой. Так же можно на лету менять время, тоже оказалось полезным.

    • Этот ответ был изменен 4 года, 3 месяца назад от manjey73.
    • Этот ответ был изменен 4 года, 3 месяца назад от manjey73.
Просмотр 15 сообщений - с 1 по 15 (из 29 всего)
  • Вы должны авторизироваться для ответа в этой теме.