Как-то раз у нас в цеху встала линия оцинкования: захлопнулись тормоза на натяжных станциях, ролики перестали крутиться и вывалилась куча ошибок, т.к. на ходу тормоз не должен накладываться. При выяснении причины, оказалось, что один из компрессоров выпал в ошибку и перестал качать воздух, давление в системе просело и не смогло удерживать тормоза на станциях натяжения в открытом состоянии. По истории ошибок на самом компрессоре тогда обнаружили, что был перегруз частотного преобразователя, а потому после запуска к компрессору стали проявлять особое внимание дабы такая ситуация больше не повторилась. Но она повторялась еще пару раз, компрессор в конце концов все же отревизировали, но написать хотелось конечно не об этом. Дело в том, что данный компрессор, как и многие современные устройства, был довольно умный – ну по крайней мере у него была возможность показывать через веб интерфейс данные о своем состоянии, как то: давление, температура и прочее. Поскольку компрессор может что-то передавать, то почему бы эти данные не собирать и потом при похожих ситуациях не анализировать, чтобы понимать что происходило в этот момент. Вот и возникла идея подключаться к веб-интерфейсу, парсить данные которые там есть и передавать их на специальный сервер который пишет сигналы с других агрегатов и механизмов. Вот об этом я и хотел рассказать, как мы это сделали

Вообще компрессоров было 3 штуки, и все три разные. На двух есть, веб-интерфейс, причем разные, на одном нет. Начну, пожалуй, с первого компрессора IngersolRand, где относительно просто удалось решить задачу. При открытии страницы компрессора в браузере выяснилось, что некий скрипт ежесекундно посылает POST запрос определенной структуры, а в ответ ему приходят запрашиваемые данные.

По скрину очень хорошо видно какие данные запрашиваются (давление нагрузки, давление разгрузки, давление в сепараторе и прочее) и в ответном сообщении эти данные хорошо и соотносятся.

Для проверки своей теории я решил запросить через VBS скрипт только то, что нужно мне,

Dim objHTTP
strToSend = "package_discharge_pressure&sump_pressure"
Set objHTTP = CreateObject("Microsoft.XMLHTTP")
Call objHTTP.Open("POST", "http://10.0.163.51/getVar.cgi", false)
objHTTP.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
objHTTP.Send strToSend
MsgBox(objHTTP.ResponseText)

и как и ожидалось получил то что хотел:

Поскольку данные оказалось так просто получить, то решено было написать программу на C#, которая бы запрашивала каждую секунду нужные данные от компрессора, а затем пересылала бы его ответ на наш сервер. Самый простой вариант это использовать WebClient, но почему-то он не заработал с этим компрессором, хотя на запросы от VBS и powershell отвечал нормально. Но зато заработал WinHttpRequest. В принятом ответе происходит замена & на ; разделение на массив значений, который потом преобразуется в массив байт, который передается на наш сервер по протоколу UDP. Весь этот код нагуглил в интернете:

Hidden text
using System;
using System.Globalization;
using WinHttp;
using System.Net;
using System.Net.Sockets;
using System.Threading;

public class getweb
{
    private static IPAddress remoteIPAddress = IPAddress.Parse("127.0.0.1");
    private static int remotePort = 5061;
    [STAThread]
    public static void Main()
    {
        string rl="1";
        do
        {
            if (rl == "1")
            {

                WinHttpRequest req = new WinHttpRequest();

                try
                {
                    req.Open("POST", "http://10.0.163.51/getVar.cgi", true);
                    req.SetRequestHeader("Content-Type", "application/x-www-form-urlencoded");
                    req.Send("online_pressure&offline_pressure&package_discharge_pressure&sump_pressure&airend_discharge_temperature&injected_coolant_temperature&aftercooler_discharge_pressure&separator_pressure_drop&coolant_filter_pressure_drop&inlet_vacuum_pressure&remote_pressure&aftercooler_discharge_temperature&interstage_pressure&machine_state_number&comm_control&status_flags");
                    req.WaitForResponse();
                }
                catch (Exception ex)
                {
                    Console.WriteLine("Error : " + ex.Message.Trim());
                    System.IO.File.WriteAllText("C:\\logs\\err_ingers_" + DateTime.Now.ToString("HHmmss") + ".txt", "INGERS WEB: " + ex.ToString() + "\n " + ex.Message);
                }
                string answer = req.ResponseText;
                answer = answer.Replace("&", ";");
                
                float[] strarr = new float[35];
                for (int i = 0; i< 35; i++)
                {
                    int position = answer.IndexOf(";");
                    string str = answer.Substring(0,position);
                    strarr [i] = float.Parse(str, CultureInfo.InvariantCulture.NumberFormat);
                    answer = answer.Substring(position+1);
                                    
                }
                var byteArray = new byte[strarr.Length * 4];
                Buffer.BlockCopy(strarr, 0, byteArray, 0, byteArray.Length);
                Send(byteArray);
                Thread.Sleep(500);
            }
        }
        while (rl=="1");          

    }
    private static void Send(byte[] datagram)
    {
        // Создаем UdpClient
        UdpClient sender = new UdpClient();

        // Создаем endPoint по информации об удаленном хосте
        IPEndPoint endPoint = new IPEndPoint(remoteIPAddress, remotePort);

        try
        {
            sender.Send(datagram, datagram.Length, endPoint);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Возникло исключение: " + ex.ToString() + "\n  " + ex.Message);
            System.IO.File.WriteAllText("C:\\logs\\err_ingers_udp_" + DateTime.Now.ToString("HHmmss") + ".txt", "INGERS udp: " + ex.ToString() + "\n " + ex.Message);
        }
        finally
        {
            // Закрыть соединение
            sender.Close();
        }
    }
}

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

Аналогичным образом устроен веб-интерфейс второго компрессора Atlas Copco: так же каждую секунду запрос, и каждую секунду ответ, но проблема только в том, что запрос и ответ – это массив шестнадцатиричных значений, который вообще непонятно как расшифровать. Например запрос выглядит как QUESTION=30020130020230020330020430020530030130030230030330030430030a30070130070330070430070530070630070730070830070930070a30070b30070c30070d30210130210530210a300501300504300505300506300507300508300509300e02300e03300e04300e18300e2a31130131130331130431130531130731130831130931130a31130b31130c31130d31130e31130f31131031131131131231131331131431131531131631131731131831131931131a31131b31131c31131d31131e31131f31132031132131132231132331132431132531132631132731132831132931132a31132b31132c31140131140231140331140431140531140631140731140831140931140a31140b31140c31140d31140e31140f311410311411311412300901300906300911300907300912300909300108

А ответ приходит такой: 1B1B00807FFF00D002D40080000C008000C900800001008000010080000000800001008000000080149A264B00000C3B000003180AA72A13020C2D7F023B535A01B432FF03F74860000000C80000A0630000230D07381393060006100000002C00B6022000010080000100800001008000010080000100800001008000010080230100002302000023030000231700002329000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000XX00000000000000000000000000000064XXXXXXXXXXXXXX00000003006489B700B2A4A4014043B700B2A4A7014043B70000001C

Я сейчас не особо помню как понял как нужно расшифровывать запрос и ответ, видимо обратил внимание на повторяющиеся цифры в запросе, и углядел некоторую структуру. но вообще получилось довольно просто расшифровать строку, сделав скрин в определенный момент, и сохранив ответ от компрессора. Сопоставил в Экселе отображаемые данные на мониторе, с теми что были в 16 ричном формате и получилась такая довольно простая табличка, больше методом подгона:

Опять же на сервере все это дело требует расшифровки и преобразований в нужные единицы измерения.

Третий компрессор был без веб-интерфейса, хотя и имел какой-то Ethernet порт и в теории мог бы что-то по нему передавать, но протокол передачи был неизвестен, а потому решили накинуть на каждую фазу мотора компрессора трансформаторы тока, просто чтобы знать ток, и передавать его через контроллер на наш сервер. Однако, немного подумав, поняли, что для такого простого дела юзать целый контроллер не совсем продуктивно, и нужно придумать что-то попроще. Трансформаторы тока подключались через отечественные измерители, у которых на борту имелся Modbus и тогда было решено заюзать преобразователь из модбаса в TCP/IP. К тому же софт для сбора данных, как бы имел возможность принимать данные по протоколу Модбас. По прошествии времени выяснилось, что данный софт на сервере при использовании функции сбора данных по модбасу работал плохо и программа часто зависала, и потому решено было идти по старой схеме: опять городить костыли с UDP передачей.

Преобразователь MOXA я настроил в режиме TCP сервера, т.е. он ожидает подключение клиента на 4002 порту и на запросы клиента выдает ответы. Сами запросы посылаются в порт в формате Modbus RTU – то есть запрашивается номер устройства, функциональный код регистра, адрес регистра, количество запрашиваемых регистров, и контрольная сумма

В самой программе для каждого измерителя тока уже предусмотрен массив байт для запроса, например для первого устройства:

private static byte[] device1 = {0x01,0x03,0x00,0x14,0x00,0x02,0x84,0x0F}; 

01 – адрес измерителя, 03 – запрос Analog Output Holding Registers, 0014 – адрес регистра, 0002 – два регистра запрашиваются (из документации к прибору), 840F – контрольная сумма CRC. Далее создается TCP клиент, и сразу же отправляется запрос для первого устройства.

TcpClient client = new TcpClient("10.0.163.52", 4002);
NetworkStream stream = client.GetStream();
if (deviceNumb==1) {
	stream.Write(device1, 0, device1.Length);
}

После получения данных от первого устройства, эти данные записываются в общий массив и отправляется запрос для второго. Аналогично ответ второго записывается в общий массив и запрашиваются данные с третьего. Третьи данные также попадают в общий массив, и сразу же посылается запрос на первое устройство по циклу. Общий массив данных со всех трех устройств через UDP передается на сервер сбора данных, где эти данные расшифровываются для каждого отдельного устройства. Полный код есть на гитхабе.

Таким образом получилось сделать 3 программы, которые крутятся на еще одном сервере, и пересылают данные на другой. Вот потому и нестандартное программирование, кто-то скажет даже всратое, и наверное будет прав. И еще кое-что. Для удобства, чтобы не было видно запущенных окон, все три проги были преобразованы в сервисы Windows при помощи утилиты nssm.exe – даже если сервак перезапустят, службы запустятся в автомате. И да, такая фигня крутится уже больше года и за все время сервисы приходилось перезапускать не больше 3 раз, что как мне кажется небольшая победа, учитывая, костыль на костыле, и что программировалось все при помощи гугла. И да чаще останавливается именно сервис мохи, поскольку подключение происходит при первом запуске программы и дальше возможные разрывы соединения не учитываются, поэтому помогает перезапуск.

Комментарии (13)


  1. dlinyj
    14.07.2022 19:08
    +3

    Похвально. Для специально для таких целей есть Zabbix.


    1. Boolkin Автор
      14.07.2022 20:52
      +1

      Спасибо! Не знал про такое, теперь изучаю, но для меня похоже сложновато)

      Но зато как я понимаю изучив один инструмент потом можно будет подстраивать его под любую свою задачу, что гораздо лучше чем то что делал я)


      1. dlinyj
        14.07.2022 22:41

        Да, по началу он сложен, но там легко можно писать свои плагины, и он полностью решает все задачи.


  1. kisaa
    15.07.2022 05:44
    +2

    Возможно, компрессоры (особенно те, что с веб-мордой) и в SNMP умеют. Это на порядок проще, особенно через Zabbix.


    1. shadrap
      15.07.2022 09:37
      +1

      абсолютно.... SNmp must have на таких вещах..

      И вообще хотелось бы чуть больше понимать процесс... Я так понимаю вы проволоку делаете?

      Перегруз преобразователя от чего случился ? Ведь оборудование было изначально настроено на рутинные операции...


      1. Boolkin Автор
        15.07.2022 10:59

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

        У нас агрегат непрерывного горячего цинкования полосы, на выходе оцинкованный рулон металла, но это не имеет значения, потому что компрессор не завязан на него. Компрессор просто качает в магистраль. После техобслуживания, перегрузы прекратились, и если я правильно понял помогла просто продувка самого ЧП. Но это не точно, техобслуживанием занимался представитель компании производителя компрессорной установки.


  1. predbannikov_yurij
    15.07.2022 09:45

    Интересно а что за контроллеры используют компрессоры с вебинтерфейсом.


    1. Dijio
      15.07.2022 10:38

      За IngersolRand не скажу, а у Atlas Copco на боруту регулятор и контроллер Elektronikon, как утверждается их собственного производства.


  1. BubaVV
    15.07.2022 11:35

    • Мама, хочу Факторио

    • У нас есть Факторио дома

      Факторио дома:


  1. Sergeant101
    15.07.2022 17:14
    +1

    По русски это называется бомж автоматизация: упало давление воздуха, заблокировались пневматические тормоза, но ошибки вылезли не по давлению воздуха, а по технической части... С умных компрессоров не читаются ошибки в ПЛК и не выводятся предупреждения на SCADA - это чтобы увеличить время простоя за счёт увеличения продолжительности диагностики неисправности! Параметры работы линии пишутся по WEB интерфейсу бог знает куда, чтобы осложнить наблюдения за состоянием оборудования обслуживающему персоналу.

    Автоматизация, безжалостная и беспощадная!


    1. Boolkin Автор
      15.07.2022 18:19

      Я не согласен. Во-первых, компрессоры качают в общую магистраль, без привязки к конкретной линии и ПЛК, к тому же технолог должен следить за линией, а не за работой компрессоров. Ситуация нестандартная и при таком раскладе не требует особо быстрого реагирования, собственно потому и 3 компрессора, потому что один из них всегда в резерве. Во-вторых, изначально не было вообще ничего, и добавив таким божеметодом хоть что-то, считаю что все же удалось облегчить работу обслуживающему персоналу, то бишь и мне самому. Предположим, подойдет энергетик ко мне и скажет что один компрессор стал хуже качать, я хотя бы смогу по истории посмотреть сколько он качал неделю назад, а сколько стал сейчас, выросли ли токи нагрузки, ну или еще что-нибудь.

      Но вообще согласен, что остановка компрессора должна хоть как-то оповещать операторов, потому как об этом узнали постфактум, что конечно же усложнило поиск неисправности. Но опять же, в проекте не было заложено такого, потому имеем что имеем


      1. Sergeant101
        15.07.2022 20:01

        Ну во первых контроль давления воздуха должен быть на линии.

        Ситуация нестандартная, и не требует быстрого реагирования... Ну штошь: предприятие встало из за просадки давления в воздушной сети из-за отказа компрессора, но ситуация не срочная - можем постоять пару деньков...

        Предположим подойдёт ко мне энергетик... Простите, а вы в каком отделе работаете что энергетик может к вам так запросто завалиться со своими проблемами?

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


        1. Siemargl
          16.07.2022 08:13

          Просадка давления в основной магистрали, это аврал с сиренами в большинстве пр-в (с переключением на резерв). Так что у автора нубоавтоматизация и проктостоматология, согласен.

          Кстати, это типичный пример привлечения айтишника вместо профильного автоматчика.