Surveillance de la température dans l'entreprise


La tâche est venue de trouver «quelque chose» pour visualiser et contrôler les températures à l'usine. Un contrôleur PLC 160 a déjà été installé et les capteurs de température sont connectés via l'interface RS-485 ( Wikipedia ).

Le contrôleur et les capteurs ont été installés avant moi.

Il y avait un exemple de schéma de connexion:


CoDeSys utilisé ( Wikipedia ) pour afficher.

Il n'y avait pas d'antécédents de températures et on ne sait pas quand l'accident s'est produit.

Commencer


L'idée est venue de cette façon: créer un site WEB en collaboration avec la base de données MySQL et y stocker des informations sur les températures et les accidents.

Tâches initiales:

  • Afficher les données de n'importe quel ordinateur de l'entreprise
  • Afficher les plantages et incidents actuels
  • Visualisation en ligne des valeurs actuelles
  • Modifier les valeurs maximales et minimales pour l'enregistrement d'alarme

Plus tard, il s'est avéré ce qui suit:

Le minimum et le maximum ne suffisent pas à maîtriser les accidents.

Un maximum critique et un minimum critique ont été ajoutés, ainsi que le temps pendant lequel la température pourrait revenir à la normale.

  1. Si la température a dépassé le minimum ou le maximum, mais est revenue à la normale pendant le temps T , il s'agit d'un accident mineur (mais cet accident a été enregistré comme insignifiant).

    image
  2. Si la température dépasse le minimum critique ou le maximum critique, il s'agit immédiatement d'un accident critique.

    image

Il fallait différencier l'accès:

  • Administrateur - juste pour moi)))
  • Technologues - modifiez 5 paramètres pour chaque capteur

    image
    J'ai dû ajouter des changements dans les paramètres de l'accident dans le temps. Ainsi, par exemple, de 00h00 à 09h00, les accidents ne sont pas enregistrés.

    image
  • Ingénieurs - Calibration

    Correctement, vous devez utiliser un ordinateur portable avec un port COM pour vous accrocher au module pour l'étalonnage. J'ai décidé de mettre en œuvre le même via WEB, c'est-à-dire la personne impliquée dans l'étalonnage vient au capteur avec son thermomètre et affiche la valeur réelle sur le site.

    image
  • Tout le monde - voir

Partie logiciel


Une machine virtuelle a été créée avec un tas de PLC 160 sur le réseau local.
CoDeSys installé.

Les adresses IP sont configurées de sorte que l'ordinateur voit le contrôleur.

image

Le projet se trouve sur le chemin c: \ project \ pro \ et s'appelle my_work.pro .

Le projet lui-même est lancé via le fichier run.cmd

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

L'application lance le fichier run.cmd

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

J'ai utilisé DDE pour obtenir des valeurs de température ( Wikipedia )

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 

Début du programme:

  1. Téléchargez les paramètres de configuration de CoDeSys depuis «config.ini»

    Chargement des paramètres de configuration MySQL depuis «config.ini»

    Par Timer (Il a été décidé qu'il suffirait de lire les données une fois par minute):

    • Obtenez le nombre de capteurs avec MySQL
    • Pour chaque capteur, créez un composant DDE.DDEConv :

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

      Nous créons le composant DDE.DDEItem et le lions au composant DDE.DDEConv :

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

      On passe le nom du capteur avec MySQL :

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

      En conséquence, nous obtenons la valeur de température:

       DDE.DDEItem[…].Text 

      Nous enregistrons la valeur de température actuelle et leurs paramètres pour chaque capteur.

       MySQL.InsertTemp(MySQL.GetSensorName(...),””,INSQL(UMin[...]),INSQL(UMax[...]),INSQL(CRMin[...]),INSQL(CRMax[...])) 

    • Nous obtenons de MySQL à la date et l'heure actuelles:

      Minimum

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

      Maximum

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

      Minimum critique

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

      Maximum critique

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

      Le temps

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

      Remarque: «Protection contre le fou » - si le minimum est supérieur au maximum ou vice versa - alors nous modifions ces valeurs par endroits.

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

    • L'accident:

      S'il n'y a pas eu d'accident, créez un enregistrement

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

      En cas d'accident, nous mettons à jour

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

      L'accident a pris fin. Définissez l'indicateur d'achèvement.

    Site web


    A écrit des pages en PHP .

    Page principale (un morceau de code, ne donne pas beaucoup de coups):

     <?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; ………………….. ?> 

    Les pages restantes sont à peu près du même type. Chaque page traite ses données.

    Ce qui a été fait:

    • Liste des capteurs. Noms, Nom du capteur pour le programme, Type de capteur.

      image
    • Les capteurs ont été regroupés par objectif.

      image
    • Ajout de «statuts d'accident»: En cours d'accident, Accident terminé, Accident critique.
    • Implémentation de l'ajout d'utilisateurs et de leurs rôles.
    • Enregistrer qui a fait quoi.
    • Archive de tous les accidents.
    • Graphiques.

    Béquilles


    1. Lorsque le programme CoDeSys démarre, une fenêtre apparaît:

      image
      Nous le fermons par programme.

       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. Soudain, le contrôleur a été éteint.

      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. Accroche incompréhensible.

      image

      Nous redémarrons l'application.

       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


    Créé un hôte avec l'adresse 127.0.0.1 .

    Il a une règle de détection nommée «Capteurs».

    image

    image

    Prototypes d'éléments de données.

    image

    Déclencheurs de prototype.

    image

    Ajouter à zabbix_agentd.conf

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

    Les scripts eux-mêmes:

    capteurs.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 -ps -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 

    Et puis, via zabbix, vous pouvez envoyer des e-mails et des SMS et bien plus encore.

    Résultat


    Le résultat a été un système de surveillance de la température dans l'entreprise avec un examen des accidents actuels et passés.

    image

    En savoir plus sur l'accident.

    image

    Actuellement, des capteurs d' ouverture / fermeture de portes sont ajoutés.

    Avantages:

    • Coûts minimaux ( relatifs ).
    • Plus au karma (?).
    • Le suivi dure depuis 3 ans.

    Inconvénients:

    • Nombreux points de défaillance: contrôleur, réseau, programme CoDeSys , machine virtuelle, MySQL , IIS .

    PS

    Ne donne pas beaucoup de coups. Ceci est mon premier article.

Source: https://habr.com/ru/post/fr470822/


All Articles