
The possibility of instant notification of certain events is required quite often. System administrators should as soon as possible learn about failures in the services and servers, technical personnel at work - about failures and deviations in the process, rapid response services - about incidents. The most obvious way to alert is SMS notification. For notification via SMS, there are special Internet services that send messages to a given group. You can save money and send it yourself using a GSM modem. But this method has several drawbacks: you need to be able to work with a serial port, and through it with a modem, process the commands sequentially; send messages in Russian is not so easy; the speed of sending to a large number of recipients may not be fast enough; difficult to control delivery; there is no guarantee that the cellular operator will not block the SIM card if it considers mailing for spam. In general, mailing services provide at least some guarantees, but they cost some money.
If you use the Internet to send a message, you can save a lot on the cost of sending. Email message is easy to form and send. But email is not an operational channel. It is not known how soon the addressee will read the message, because not all email clients announce the receipt of a new message. The email client on a mobile device is far from being set up by everyone and not always.
')
Quite another thing - instant messaging (ICQ, XMPP). The XMPP protocol is preferred because it is open. And due to the fact that this is a full-fledged network protocol, the following options are obtained out of the box:
- the contact list may be a mailing list (this list is easy to edit)
- data is encrypted
- delivery control
- you can see the status of recipients (online) to understand who can receive the message
- messages can be received both on a personal computer and on a mobile device and this does not require the development of a special program
- Alert can be expanded with interactivity: add chat / conference, processing additional requests
If desired, the list can be continued.
As an example of the implementation of this approach, a program has been developed that notifies of errors in the process equipment. The equipment rack is a kind of program that writes messages (and error messages as well) to the database. The database has the format - Paradox, and the data encoding is Win-1251. It was decided to abandon the graphical interface in favor of the console application, the parameters set with text files. The solution tool is QT.
Implemented functionality: collecting errors from a variety of technological installations, sending messages via jabber, general chat via jabber.
Architecture and interaction mechanisms
The program, which is placed on the final equipment, is conventionally called the "client". The program that will receive data from clients is a “server”. Data exchange is carried out through broadcast UDP. According to received data, the server sends information on all of its contact jabber account list.
Customer
Program parameters
It is decided to store the parameters in json. The 4th QT is used for work, it does not yet have work with json, so parsing is done using a
third-party QT library (qt-json) .
File with client
database.json parameters
{ "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" }
Reading parameters:
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) { foreach(QVariant key, result.keys()) { if(key.toString().toLower()=="connectionstring") connecionString = result.value(key.toString()).toString(); if(key.toString().toLower()=="logfilename") logFileName = result.value(key.toString()).toString(); if(key.toString().toLower()=="refreshperiod") refreshPeriod = result.value(key.toString()).toString().toInt(); if(key.toString().toLower()=="cncname") cncName = result.value(key.toString()).toString(); } }
Connection to the PARADOX DB
To access the database from QT there is a
QODBC . Connection string for accessing PARADOX:
"Driver={Microsoft Paradox Driver (*.db )};DriverID=282;FIL=Paradox 4.x;DBQ=c:\\;ReadOnly=1"
where DBQ is the path to the database.
Important point: in the connection string after "* .db" before ")" there is a space!
It was decided to store the connection string in a file next to the program, since setting up the data source through system sources is long and inconvenient. And generally it is good, when all settings in one place.
The program on the rack writes a log to the table ManLog.
Field name | Type of | Purpose |
---|
Data | Date | Date of entry in the log |
Time | Datetime | Logging time |
Mes | Vararch | Message |
In data types of columns I can be mistaken. And QT is also wrong in them, so when compiling the SQL query, I had to be cunning: it was not possible to convert time to a string, because it was not possible to take the field as toDateTime ().
SELECT data, format(ManLog.Time,'hh:mm:ss'), mes from ManLog
The problems with connecting to the database did not end there. As shown by viewing the database file, the data is encoded there in win-1251. I already had experience with codecs, converting encodings, setting encodings in BDE, registry settings. All this can work differently on different operating systems. What OS will be on the rack is unknown, and you should not once again interfere with the operation of the OS of industrial equipment. Therefore, it was decided to take the data from the Mes field in binary form:
QSqlQuery query; query.exec("SELECT data, format(ManLog.Time,'hh:mm:ss'), mes from ManLog"); ... QByteArray msg = query.value(2).toByteArray(); // !
The data is read from the database, and now they need to be checked with error codes. But we also need error codes in binary form in the encoding in which they are in the database. It is possible to register error codes in json, but when reading a json file, we again encounter the problem of encoding. Therefore,
there is no time to explain , create an ini file and drive windows errors there with a notepad:
Error list file
errorlist.ini - file with a list of errors
- ..
I read the data in the list of binary arrays:
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); } }
Next we start a list of the lines already read in order not to send them again over the network. We start the timer with the
refreshPeriod period and do a
re -
request of the data. If there is new data, we send it over the network via UDP.
Sending data over the network
Data decided to serialize using
QDataStream . The machine name, date, time, indication - whether the string is an error, and the message itself in binary form are sent.
Determine if there is an error in the string
QString errText="ok"; foreach(QByteArray err, errorList) { if(msg.contains(err)) { errText = "ERROR"; } }
Serialize, ship:
QByteArray datagram; QDataStream out( &datagram, QIODevice::ReadWrite ); out.setVersion(QDataStream::Qt_4_0); out << cncName; out << query->value(0).toDate();
Server part
It consists of two parts: receiving UDP messages and sending them via xmpp.
Accept UDP packets
Listen to the port:
udpSocket = new QUdpSocket(this); connect(udpSocket, SIGNAL(readyRead()), this, SLOT(onReadyRead())); udpSocket->bind(45000);
We parse the incoming message and emit a signal if the recording has a “error” sign.
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); }
This signal will receive a class that can send messages out. In our case in Jabber. By analogy, you can send by mail or SMS.
Send messages to jabber
To send messages, create a class derived from QXmppClient from
this wonderful library (qxmpp) .
To store the mailing list use
QList<QString> sendList;
Fill it with data from the roster jabber account (there are account names).
And the slot that catches the signal
needSendToAll , sends a message to all of
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); } }
Jabber chat implementation
All messages received by the account are sent to the entire list of contacts with the addition of the sender's name. Thus, it turns out a general chat. May be convenient for discussions.
Adding is very simple. To do this, in the JabberClient constructor, add processing of one more signal - the message receiving signal:
connect(this, SIGNAL(messageReceived(QXmppMessage)), SLOT(onMessageReceived(QXmppMessage)));
And we process it in the message receiving slot:
void JabberClient::onMessageReceived(QXmppMessage msg) { if(msg.body().length()>0) { qDebug() << nowStr() << "onMessageReceived" << msg.from() << msg.body(); foreach(QString userName, sendList) { this->sendMessage(userName, msg.from() + ">\n" + msg.body()); } } }
Check the length of the message due to the fact that in the process of typing a message a call is triggered, but the length of the message is 0.
Results
The project is an example of how you can connect from QT to databases with a given message encoding, without being tied and not setting the system locale. It also shows how to use the qxmpp library in QT to send messages to jabber.
Source code examples:
code.google.com/p/cnc-error-monitorPs. The article will be supplemented and updated.
UPD: fixed parsing of parameters from json, implemented chat via jabber
UPD2: strange remark: if you compile the client part on QT version 4, then the data from byteArray from the database is read as it should be - in the encoding of the database. And if to collect on QT5, then the data there turn out corrupted (and no parameters help).