Система мониторинга работы электропечей по выращиванию кристаллов через jabber

в 7:53, , рубрики: cnc, qt, Qt Software, sql, xmpp, метки: , , ,

image

На производстве по выращиванию кристаллов сапфира захотелось иметь систему мгновенных оповещений в случае поломок/аварий при выполнении программы ЧПУ. Стойка станка представляет из себя некую программу, которая пишет сообщения (и сообщения об ошибках в том числе) в базу данных. Получилось так, что БД — Paradox, а кодировка данных Win-1251. Путь проб и ошибок в виде графических интерфейсов и отправке сообщений по SMS — тема отдельного разговора. Однако, как это бывает на заводах, все сроки прошли, решение задачи потребовалось «вчера» и «в кротчайшие сроки и в обязательном порядке», а иначе «полетят головы» (классика жанра). Поэтому было принято решение быстренько на коленке переписать все прежние графические костыли в простой костыль в виде консольного приложения, параметры задавать текстовыми файлами, а вместо SMS слать сообщение jabber клиенту. Начальству соврали, что старый вариант взят в опытную эксплуатацию и за неделю собрались все решить. Инструмент для решения — QT.

Архитектура и механизмы взаимодействия

Программа, которая ставится на станки, условно названа «клиент». Программа, которая будет принимать данные от клиентов — «сервер». Обмен данных осуществлен через broadcast UDP. По принятым данным, сервер отправляет информацию по всему своему контакт листу jabber аккаунта.

Клиент

Параметры программы

Параметры решено хранить в json. Для работы использую 4-й QT, в нем еще нет работы с json, поэтому парсить буду с помощью сторонней библиотеки для QT.

Файл с параметрами клиента database.json

{
"LogFileName":"c://manlog.log",
"ConnectionString":"Driver={Microsoft Paradox Driver (*.db )};DriverID=282;FIL=Paradox 4.x;DBQ=c:\;ReadOnly=1",
"CNCName":"CNC_01",
"RefreshPeriod":"2000"
}

Чтение параметров:

    QFile configFile("./config/database.json");
    configFile.open(QIODevice::ReadOnly | QIODevice::Text);
    QTextStream dbFileIn(&configFile);
    QString jsonData = dbFileIn.readAll();
    configFile.close();

    bool ok = false;

    QVariantMap result = QtJson::parse(jsonData, ok).toMap();

    if(ok) {
        if(result.contains("ConnectionString")) {
            connecionString = result.value("ConnectionString").toString();
        }
        if(result.contains("LogFileName")) {
            logFileName = result.value("LogFileName").toString();
        }
        if(result.contains("RefreshPeriod")) {
            refreshPeriod = result.value("RefreshPeriod").toString().toInt();
        }
        if(result.contains("CNCName")) {
            cncName = result.value("CNCName").toString();
        }
    }

Красивый индусокод, в лучших традициях наколенных технологий, который чувствителен к регистру символов в файле параметров.

Подключение к БД PARADOX

Для доступа к БД из QT есть QODBC. Строка соединения для доступа к PARADOX:

"Driver={Microsoft Paradox Driver (*.db )};DriverID=282;FIL=Paradox 4.x;DBQ=c:\;ReadOnly=1"

где DBQ — путь к БД.

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

Программа на стойке пишет лог в таблицу ManLog.

Имя поля Тип Назначение
Data Date Дата занесения записи в лог
Time DateTime Время занесения записи в лог
Mes Varchar Сообщение

В типах данных столбцов могу ошибаться. И QT в них тоже ошибается, поэтому при составлении SQL запроса пришлось хитрить: преобразовывать время в строку, потому что взять поле, как toDateTime() не удалось.

SELECT data, format(ManLog.Time,'hh:mm:ss'), mes from ManLog

На этом проблемы с подключением к БД не закончились. Как показал просмотр файла БД, данные там в кодировке win-1251. У меня уже был опыт работы с кодеками, преобразованием кодировок, настройке кодировок в BDE, ковырянии реестра. Все это может по-разному работать на разных ОС. На стойках зоопарк из ОС (7, XP), да и ковырять их лишний раз не дадут. Поэтому было решено брать данные из поля Mes в двоичном виде:

    QSqlQuery query;
    query.exec("SELECT data, format(ManLog.Time,'hh:mm:ss'), mes from ManLog");
  ...
    QByteArray msg = query.value(2).toByteArray(); //двоичные данные!

Данные считаны из БД, а теперь их надо сверить с кодами ошибок. Но коды ошибок нам нужны тоже в двоичном виде в той кодировке, в которой они в базе. В json коды ошибок прописать можно, но при чтении json файла мы опять сталкиваемся с проблемой кодировки. Поэтому, нет времени объяснять, создаем ini файл и вбиваем туда ошибки windows блокнотом:

Файл со списком ошибок

errorlist.ini — файл со списком ошибок

Превышена предельно допустимая температуры воды
и т.д.

Считываю данные в список бинарных массивов:

    QFile fileIni("./config/errorlist.ini");
    fileIni.open(QFile::ReadOnly);
    QByteArray errorRaw = fileIni.readAll();
    fileIni.close();
    errorRaw = errorRaw.replace('n', "");
    QList<QByteArray> errorAllList = errorRaw.split('r');
    for(int i=0;i<errorAllList.count();i++) {
        if(errorAllList.at(i).count()>0) {
            errorList << errorAllList.at(i);
        }
    }

Дальше мы заводим список уже считанных строк, чтобы не слать их повторно. Запускаем таймер с периодом refreshPeriod и делаем перезапрос данных. Если есть новые данные — отправляем их по UDP.

Отправка данных по сети

Данные решено сериализовать с помощью QDataStream. Отправляю имя станка, дату, время, признак — является ли строка ошибкой, и само сообщение в двоичном виде.

Определяем есть ли ошибка в строке

        QString errText="ok";
        foreach(QByteArray err, errorList) {
            if(msg.contains(err)) {
                errText = "ERROR";
            }
        }

Заворачиваем, отправляем:

        QByteArray datagram;
        QDataStream out( &datagram, QIODevice::ReadWrite );
        out.setVersion(QDataStream::Qt_4_0);
        out << cncName;
        out << query->value(0).toDate(); //date
        out << logTime;//time
        out << errText;
        out << msg;//bytearray

        udpSocket->writeDatagram(datagram, QHostAddress::Broadcast, 45000);

Серверная часть

Состоит из двух частей: прием UDP сообщений и отправка их по xmpp.

Принимаем UDP пакеты

Слушаем порт:

    udpSocket = new QUdpSocket(this);
    connect(udpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));

    udpSocket->bind(45000);

Разбираем пришедшее сообщение и испускаем сигнал, если у записи есть признак «ошибка».

void CncReceiver::onReadyRead()
{
    QByteArray buffer;
...
    buffer.resize(udpSocket->pendingDatagramSize());

    udpSocket->readDatagram(buffer.data(), buffer.size());

    QDataStream in( &buffer, QIODevice::ReadWrite );
    in.setVersion(QDataStream::Qt_4_0);

    in >> cncName;
    in >> date;
    in >> time;
    in >> errText;
    in >> msg;
...
    if(errText!="ok")
        emit needSendToAll(msg, cncName, date, time);

}

Этот сигнал будет ловить класс, умеющий отправлять сообщения наружу. В нашем случае в Jabber. Но надо еще будет сделать и по sms.

Отправляем сообщения в jabber

Для отправки собщений создаем класс наследник от QXmppClient из этой замечательной библиотеки.

Для хранения списка рассылки используем

QList<QString> sendList;

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

А слот, который ловит сигнал needSendToAll, отправляет сообщение всем из sendList.

void JabberClient::sendToAll(QByteArray msg, QString cncName, QDate errDate, QString errTime)
{
    QString messageToUser;

    QTextCodec *codec = QTextCodec::codecForName("Windows-1251");

    messageToUser = codec->toUnicode(msg);

    foreach(QString userName, sendList) {
        this->sendMessage(userName, "CNC_NAME: "+ cncName +  "n" +
                                    errDate.toString("yyyy-MM-dd ")+ errTime + "nMesage:n" + messageToUser);
    }
}

Итоги

Я думаю, что сжатые сроки не оправдывают качество моего кода. Кто работает на производстве меня поймет. Остальных прошу понять и простить. Но надо было быстро. В срок уложились. Есть еще что улучшить и исправить. Проект получился узкого назначения, т.к. sql запрос задан жестко. Да и вообще условие задано жестко. Формирование сообщений — жесткое. Поэтому проект может быть интересен не сколько как продукт, а как пример реализации. Возможно, мой опыт окажется кому-то полезным, потому что я долго мучался с русским в БД и подключением xmpp к проекту.

Исходные коды:
code.google.com/p/cnc-error-monitor/

Автор: UA3MQJ

Источник


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js