Пришла задача придумать “что нибудь” для просмотра и контроля за температурами на производстве. Был уже установлен контроллер ПЛК 160 и подключены датчики температур по интерфейсу RS-485 .
Контроллер и датчики были установлены до меня.
Была примерная схема подключения:
Использовали CoDeSys для просмотра.
Не было никакой истории по температурам и неизвестно когда была авария.
Начало
Идея пришла такая — создать WEB-сайт в связке с базой MySQL и хранить там информацию по температурам и авариям.
Первоначальные задачи:
- Просмотр данных с любого компьютера на территории предприятия
- Просмотр текущих аварий и произошедших
- Онлайн просмотр текущих значений
- Изменять максимальные и минимальные значения для регистрации аварий
В дальнейшем выяснилось следующее:
Минимум и максимум недостаточны для контроля аварий.
Были добавлены критический максимум и критический минимум, а также время, во время которого температура может прийти в норму.
- Если температура вышла за пределы минимума или максимума, но вернулась в норму за время T, то это незначительная авария (но данная авария регистрировалась как незначительная).
2. Если температура вышла за пределы критического минимума или критического максимума, то это сразу критическая авария.
Потребовалось разграничить доступ:
- Администратор — только для меня )))
- Технологи — для каждого датчика менять 5 параметров
Пришлось добавить изменения параметров аварии по времени. Это для того чтобы, например, с 00:00 до 09:00 не регистрировались аварии.
Инженеры — калибровка
По правильному необходимо с помощью ноутбука с COM портом цепляться к модулю для калибровки. Решил реализовать так же через WEB, т.е. человек занимающийся калибровкой подходит к датчику со своим термометром и выставляет фактическое значение на сайте.
- Все остальные — просмотр
Программная часть
Была создана виртуальная машина со связкой с ПЛК 160 по локальной сети.
Установлен CoDeSys.
Настроены IP адреса, чтобы компьютер видел контроллер.
Проект находится по пути c:projectpro и называется my_work.pro.
Запуск самого проекта производится через файл run.cmd
"C:Program Files3S SoftwareCoDeSys V2.3Codesys.exe" "C:projectpromy_work.pro" /userlevel 0 /password 157999 /online
Приложение запускает файл run.cmd
WinExec(Pchar(“c:run.cmd”), SW_HIDE);
Для получения значений температуры использовал DDE (Википедия)
config.ini
[CoDeSys] service=CoDeSys topic=C:projectpromy_work.pro item=C:Program Files3S SoftwareCoDeSys V2.3 cmd=C:run.cmd [db] host=127.0.0.1 port=3306 user=root key=keypassword db=workdb
Старт программы:
- Загружаем параметры конфигурации 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; ………………….. ?>Остальные страницы примерно такого же типа. Каждая страница обрабатывает свои данные.
- Что сделано:
- Список датчиков. Наименования, Имя датчика для программы, Тип датчика.
Датчики были Сгруппированы по назначению.
- Добавлены “статусы аварий”: В процессе аварии, Авария завершена, Критическая авария.
- Реализовано добавление пользователей и их роли.
- Логирование кто что делал.
- Архив всех аварий.
- Графики.
Костыли
- При запуске программы CoDeSys выходит окошко:
Программно его закрываем.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. Вдруг контроллер отключили.
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. Непонятное зависание.
- Перезапускаем приложение.
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.
В нем правило обнаружения с наименованием “Датчики”.
Добавляем в 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 можно и отправлять на почту и смс и многое другое.
Результат
Получилась система мониторинга температур на предприятии с просмотром текущих и произошедших аварий.
Подробнее об аварии.
На данный момент добавлены датчики открытия/закрытия дверей.
Плюсы:
- Минимальные затраты (относительно).
- Плюс к карме (?).
- Мониторинг работает уже 3 года.
Минусы:
- Много точек отказа: контроллер, сеть, программа CoDeSys, виртуальная машина, MySQL, IIS.
P.S.