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

Пришла задача придумать “что нибудь” для просмотра и контроля за температурами на производстве. Был уже установлен контроллер ПЛК 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.

    Не пинайте сильно. Это первая моя статья.

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *