Мониторинг температур на предприятии

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Пришла задача придумать “что нибудь” для просмотра и контроля за температурами на производстве. Был уже установлен контроллер ПЛК 160 и подключены датчики температур по интерфейсу RS-485 (Википедия).

Контроллер и датчики были установлены до меня.

Была примерная схема подключения:


Использовали CoDeSys (Википедия) для просмотра.

Не было никакой истории по температурам и неизвестно когда была авария.

Начало


Идея пришла такая — создать WEB-сайт в связке с базой MySQL и хранить там информацию по температурам и авариям.

Первоначальные задачи:

  • Просмотр данных с любого компьютера на территории предприятия
  • Просмотр текущих аварий и произошедших
  • Онлайн просмотр текущих значений
  • Изменять максимальные и минимальные значения для регистрации аварий

В дальнейшем выяснилось следующее:

Минимум и максимум недостаточны для контроля аварий.

Были добавлены критический максимум и критический минимум, а также время, во время которого температура может прийти в норму.

  1. Если температура вышла за пределы минимума или максимума, но вернулась в норму за время T, то это незначительная авария (но данная авария регистрировалась как незначительная).

    image
  2. Если температура вышла за пределы критического минимума или критического максимума, то это сразу критическая авария.

    image

Потребовалось разграничить доступ:

  • Администратор — только для меня )))
  • Технологи — для каждого датчика менять 5 параметров

    image
    Пришлось добавить изменения параметров аварии по времени. Это для того чтобы, например, с 00:00 до 09:00 не регистрировались аварии.

    image
  • Инженеры — калибровка

    По правильному необходимо с помощью ноутбука с COM портом цепляться к модулю для калибровки. Решил реализовать так же через WEB, т.е. человек занимающийся калибровкой подходит к датчику со своим термометром и выставляет фактическое значение на сайте.

    image
  • Все остальные — просмотр

Программная часть


Была создана виртуальная машина со связкой с ПЛК 160 по локальной сети.
Установлен CoDeSys.

Настроены IP адреса, чтобы компьютер видел контроллер.

image

Проект находится по пути c:\project\pro\ и называется my_work.pro.

Запуск самого проекта производится через файл run.cmd

"C:\Program Files\3S Software\CoDeSys V2.3\Codesys.exe" "C:\project\pro\my_work.pro" /userlevel 0 /password 157999 /online 

Приложение запускает файл run.cmd

WinExec(Pchar(“c:\run.cmd”), SW_HIDE);

Для получения значений температуры использовал DDE (Википедия)

config.ini

[CoDeSys]
service=CoDeSys
topic=C:\project\pro\my_work.pro
item=C:\Program Files\3S Software\CoDeSys V2.3\
cmd=C:\run.cmd
[db]
host=127.0.0.1
port=3306
user=root
key=keypassword
db=workdb

Старт программы:

  1. Загружаем параметры конфигурации CoDeSys из “config.ini”

    Загружаем параметры конфигурации MySQL из “config.ini”

    По Таймеру ( Определились что достаточно будет считывать данные раз в минуту ):

    • Получаем количество датчиков с MySQL
    • Для каждого датчика создаем компонент DDE.DDEConv:

      DDE.DDEConv[…]:= TDdeClientConv.Create(Self)
      DDE.DDEConv[…].ServiceApplication:=”patchcodesys”
      DDE.DDEConv[…].SetLink(“name”,”patchdde”)
      

      Создаем компонент DDE.DDEItem и связываем с компонентом DDE.DDEConv:

      DDE.DDEItem[…]:=TDdeClientItem.Create(Self)
      DDE.DDEItem[…].DdeConv:=DDE.DDEConv[…]
      

      Передаем имя датчика с MySQL:

      DDE.DDEItem[…].DdeItem:=MySQL.GetSensorName(…) 
      

      Как результат – получаем значение температуры:

      DDE.DDEItem[…].Text
      

      Сохраняем текущее значение температуры и их параметры для каждого датчика.

      MySQL.InsertTemp(MySQL.GetSensorName(...),”Температура”,INSQL(UMin[...]),INSQL(UMax[...]),INSQL(CRMin[...]),INSQL(CRMax[...]))
      

    • Получаем из MySQL на текущую дату и время:

      Минимум

      UMin[I…]:=OUTSQL(MySQL.GetMin(MySQL.GetSensorName(…)))

      Максимум

      UMax[…]:=OUTSQL(MySQL.GetMax(MySQL.GetSensorName(...)))

      Критический минимум

      CRMin[…]:=OUTSQL(MySQL.GetCriticalMin(MySQL.GetSensorName(…)))

      Критический максимум

      CRMax[…]:=OUTSQL(MySQL.GetCriticalMax(MySQL.GetSensorName(…)))

      Время

      CRTime[…]:=MySQL.GetCriticalTime(MySQL.GetSensorName(…))

      Примечание: «Защита от дурака» – если минимум больше максимума или наоборот – то меняем эти значения местами.

      if (UMin[…]>=UMax[…]) then
      begin
      UM[…]:=UMin[…];
      UMin[…]:=UMax[…];
      UMax[…]:=UM[…];
      end;
      

    • Авария:

      Если не было аварии создаем запись

      MySQL.InsertCrash(FormatDateTime('yyyy-mm-dd hh:nn:ss', dt),FormatDateTime('yyyy-mm-dd hh:nn:ss', dt),MySQL.GetSensorName(...),…)

      Если была авария обновляем

      MySQL.UpdateCrash(MySQL.GetCrashID(MySQL.GetSensorName(...)),FormatDateTime('yyyy-mm-dd hh:nn:ss', dt),…)

      Авария закончилась устанавливаем Флаг завершения

    WEB-сайт


    Написал страницы на PHP.

    Главная страница (кусок кода, сильно не пинайте):

    <?php
    require 'config.php';
    session_start();
    $page = isset( $_GET['page'] ) ? $_GET['page'] : "";
    
    switch ( $page ) {   
        case 'login':
            login();
            break;
        case 'logout':
            logout();
            break;
        case 'list':
            listpage();
            break;                             
    …………………..
      ?>

    Остальные страницы примерно такого же типа. Каждая страница обрабатывает свои данные.

    Что сделано:

    • Список датчиков. Наименования, Имя датчика для программы, Тип датчика.

      image
    • Датчики были Сгруппированы по назначению.

      image
    • Добавлены “статусы аварий”: В процессе аварии, Авария завершена, Критическая авария.
    • Реализовано добавление пользователей и их роли.
    • Логирование кто что делал.
    • Архив всех аварий.
    • Графики.

    Костыли


    1. При запуске программы CoDeSys выходит окошко:

      image
      Программно его закрываем.

      W_WND_Button_Run: HWND:
      W_WND_RUN: HWND;
      C_Button_Message='Button';
      C_CoDeSys_Message='CoDeSys';
      
      W_WND_RUN := FindWindow(nil,C_CoDeSys_Message);
       if W_WND_RUN<>0 then
      begin
      W_WND_Button_Run:=FindWindowEx(W_WND_RUN, 0,C_Button_Message, nil);
       if W_WND_Button_Run<>0 then
      begin
      SendMessage(W_WND_Button_Run, WM_LBUTTONDOWN, 10, 10);
      SendMessage(W_WND_Button_Run, WM_LBUTTONUP, 10, 10);
      SendMessage(W_WND_Button_Run, WM_LBUTTONDOWN, 10, 10);
      SendMessage(W_WND_Button_Run, WM_LBUTTONUP, 10, 10);
      end;
      end;
      

    2. Вдруг контроллер отключили.

      image

      W_WND_Error:=FindWindow(nil,'Ошибка');
      if W_WND_Error<>0 then
      begin
       W_WND_Button_Error:=FindWindowEx(W_WND_Error,0,'Button', nil);
      if W_WND_Button_Error<>0 then
      begin
      SendMessage(W_WND_Button_Error, WM_LBUTTONDOWN, 10, 10);
      SendMessage(W_WND_Button_Error, WM_LBUTTONUP, 10, 10);
      SendMessage(W_WND_Button_Error, WM_LBUTTONDOWN, 10, 10);
      SendMessage(W_WND_Button_Error, WM_LBUTTONUP, 10, 10);
      PostMessage(FindWindow(PChar(C_CoDeSys),nil), WM_QUIT, 0, 0);
      end;
      end;
      

    3. Непонятное зависание.

      image

      Перезапускаем приложение.

      C_CLOSE_DEBUG='CoDeSys for Automation Alliance (debug)';
      W_WND_CLOSE:=FindWindow(nil,C_CLOSE_DEBUG);
      if W_WND_CLOSE<>0 then
      begin
      KillProcess('Codesys.exe');
      KillProcess('WerFault.exe');
      PostMessage(FindWindow(PChar(C_Close_DEBUG),nil), WM_QUIT, 0, 0);
      PostMessage(FindWindow(PChar(C_CoDeSys),nil), WM_QUIT, 0, 0);
      MySQL.InsertLog('Error debug.. Kill process - codesys.exe and WerFault.exe');
      MySQL.InsertLog('Restart programm');
      RestartThisApp;
      end;
      
      //Убиваем процесс
      
      function KillProcess(ExeName: string): LongBool;
      var
        B: BOOL;
        ProcList: THandle;
        PE: TProcessEntry32;
      begin
        Result := False;
        ProcList := CreateToolHelp32Snapshot(TH32CS_SNAPPROCESS, 0);
        PE.dwSize := SizeOf(PE);
        B := Process32First(ProcList, PE);
        while B do begin
          if (UpperCase(PE.szExeFile) = UpperCase(ExtractFileName(ExeName))) then
            Result := TerminateProcess(OpenProcess($0001, False, PE.th32ProcessID), 0);
          B:= Process32Next(ProcList, PE);
        end;
        CloseHandle(ProcList);
      end;
      
      //Рестарт приложения
      
      procedure TForm1.RestartThisApp;
      begin
        ShellExecute(Handle, nil, PChar(Application.ExeName), nil, nil, SW_SHOWNORMAL);
        Application.Terminate; // or, if this is the main form, simply Close;
      end;
      

    ZABBIX


    Создал узел сети с адресом 127.0.0.1.

    В нем правило обнаружения с наименованием “Датчики”.

    image

    image

    Прототипы элементов данных.

    image

    Прототипы триггеров.

    image

    Добавляем в zabbix_agentd.conf

    UserParameter=sensors[*],/usr/lib/zabbix/alertscripts/sensors.sh
    UserParameter=crash[*],/usr/lib/zabbix/alertscripts/crash.sh $1
    

    Сами скрипты:

    sensors.sh

    #!/bin/sh
    unset id
    unset res
    id=(`echo "select id FROM sensor WHERE type='1'" | mysql -uroot -pПароль -D workdb -h 0.0.0.0 --default-character-set=utf8 2>/dev/null`)
    echo '{ "data": ['
    for (( count=1; count<${#id[@]}; count++ ))
    do 
    res=(`echo "select name FROM sensor WHERE (type='1' and id='${id[$count]}') " | mysql -uroot -pПароль -D workdb -h 0.0.0.0 --default-character-set=utf8 2>/dev/null `)
    r={${res[@]}
    l=${#r}
    res1=(`echo "select param FROM sensor WHERE (type='1' and id='${id[$count]}') " | mysql -uroot -pПароль -D workdb -h 0.0.0.0 --default-character-set=utf8 2>/dev/null `)
    r1={${res1[@]}
    l1=${#r1}
    res2=(`echo "select ddename FROM sensor WHERE (type='1' and id='${id[$count]}') " | mysql -uroot -pПароль -D workdb -h 0.0.0.0 --default-character-set=utf8 2>/dev/null `)
    r2={${res2[@]}
    l2=${#r2}
    res3=(`echo "select min FROM temp_${r2:17:l2} ORDER BY id DESC LIMIT 1 " | mysql -uroot -pпарольs -D workdb -h 0.0.0.0 --default-character-set=utf8 2>/dev/null`)
    r3={${res3[@]}
    l3=${#r3}
    res4=(`echo "select max FROM temp_${r2:17:l2}  ORDER BY id DESC LIMIT 1 " | mysql -uroot -pПароль -D workdb -h 0.0.0.0 --default-character-set=utf8 2>/dev/null`)
    r4={${res4[@]}
    l4=${#r4}
    res5=(`echo "select cmin FROM temp_${r2:17:l2}  ORDER BY id DESC LIMIT 1 " | mysql -uroot -pПароль -D workdb -h 0.0.0.0 --default-character-set=utf8 2>/dev/null`)
    r5={${res5[@]}
    l5=${#r5}2>/dev/null 
    res6=(`echo "select cmax FROM temp_${r2:17:l2} ORDER BY id DESC LIMIT 1 " | mysql -uroot -pПароль -D workdb -h 0.0.0.0 --default-character-set=utf8 2>/dev/null`)
    r6={${res6[@]}
    l6=${#r6}
    res7=(`echo "select param FROM temp_${r2:17:l2} ORDER BY id DESC LIMIT 1 " | mysql -uroot -pПароль -D workdb -h 0.0.0.0 --default-character-set=utf8 2>/dev/null`)
    r7={${res7[@]}
    l7=${#r7}
    
    s=$s'{ "{#SID}": "'${id[$count]}'", "{#SNAME}": "'${r:5:l}'", "{#SDDENAME}": "'${r2:17:l2}'" , "{#SPARAM}": "'${r7:7:l7}'", "{#SMIN}": "'${r3:5:l3}'", "{#SMAX}": "'${r4:5:l4}'", "{#SCMIN}": "'${r5:6:l5}'", "{#SCMAX}": "'${r6:6:l6}'" },'
    done
    a=${#s}
    b=${s: 0: $a-1}
    c=${#b}
    d=$b
    echo $d']}'
    

    crash.sh

    #!/bin/sh
    a=$1
    unset res
    res=(`echo "select flag, id_status FROM crash WHERE  id_sensor='$a' ORDER BY id DESC LIMIT 1 " | mysql -uroot -pПароль -D workdb -h 0.0.0.0  --default-character-set=utf8 2>/dev/null `)
    for (( count=2; count<${#res[@]}; count++ ))
    do 
    s=$s' '${res[$count]}
    done
    b=${s:0:2}
    c=${s:3:4}
    if [ $b = 0 -a $c = 1 ] 
    then
    echo 0
    else
    echo 1
    fi
    

    А дальше через zabbix можно и отправлять на почту и смс и многое другое.

    Результат


    Получилась система мониторинга температур на предприятии с просмотром текущих и произошедших аварий.

    image

    Подробнее об аварии.

    image

    На данный момент добавлены датчики открытия/закрытия дверей.

    Плюсы:

    • Минимальные затраты (относительно).
    • Плюс к карме (?).
    • Мониторинг работает уже 3 года.

    Минусы:

    • Много точек отказа: контроллер, сеть, программа CoDeSys, виртуальная машина, MySQL, IIS.

    P.S.

    Не пинайте сильно. Это первая моя статья.
Источник: https://habr.com/ru/post/470822/


Интересные статьи

Интересные статьи

Мониторинг очень важен для современных приложений, современные приложения по своей природе сильно распределены и имеют разные зависимости, такие как база данных, службы, ...
Предыстория Когда-то у меня возникла необходимость проверять наличие неотправленных сообщений в «1С-Битрикс: Управление сайтом» (далее Битрикс) и получать уведомления об этом. Пробле...
VUE.JS - это javascript фрэймворк, с версии 18.5 его добавили в ядро битрикса, поэтому можно его использовать из коробки.
SRE (Site Reliability Engineering) — подход к обеспечению доступности веб-проектов. Считается фреймворком для DevOps и говорит как добиться успеха в применение DevOps-практик. В этой статье п...
На сегодняшний день существуют готовые (проприетарные) решения для мониторинга IP(TS)-потоков, например VB и iQ, они обладают достаточно богатым набором функций и обычно подобные решения имеются ...