📜 ⬆️ ⬇️

Experience in developing management software for the questroom

The last year and a half are so popular. questrooms, quests "leave the room" realized in real life. When I went to the first quests, most of the tasks were limited to finding keys and codes for mechanical locks, magnetic locks were something cool and rare, not to mention wireless interfaces, but the farther away, the more technically difficult this entertainment becomes. At the end of winter, a friend of mine working in one of these offices asked for help in writing a control program for one of their quests, because the programmer had merged with them and the timeline began to burn. The task was interesting, the money was not bad, and it’s not a sin to help a good person, so I agreed, although it was a shame to understand that I would not go to this quest, since I would know his entire scenario. The terms were initially set to be very tight, so for development I chose the familiar C ++ \ Qt5.5 environment.
It quickly became clear that I had done a lot of stupidity in taking on work with the not-so-well-written, but I suspect that everyone made this mistake anyway, so there’s no point in writing about it, it’s not a pitfall, it’s the most common rake.

From a hardware point of view, the controlled part of the quest is as follows:


In the initial formulation, the task looked pretty simple: read the MODBUS data, write the MODBUS data, at the right moments play the sound in the desired column and video on the second screen. As practice has shown, all this is really done is not very difficult. But to understand how to do this is not very difficult - not so banal.

MODBUS
')
MODBUS is in fact a standard communication protocol for industrial electronics, it is implemented either on top of TCP or on top of a com port. Perhaps, if devices connected via TCP were used in the quest, everything would be a little easier, or maybe not, I don't know. I got the option with a com port. The advantages of this protocol are standard. Cons - it works through a serial port at a speed of 9600 baud, which ultimately limits us to about 20 requests per second for each of the ports.
All MODBUS devices have several sets of registers. In my case, three were involved: flag registers (store a boolean value, can be written), storage registers (store an integer, can be written), and input registers (store an integer, cannot be written). There were three relays in the quest (eight input registers that received sensor status, eight control relay channel flags) and a stack of dimmers (I actually use five storage registers in them, four control the brightness on their channels, the fifth controls the overall brightness).

It is very important to competent planning by electronic engineers. Firstly, it is very desirable that the devices to which the active recording goes and the devices from which the active reading goes are hung on different ports. In my case, this was done, dimmers hung on one port, on the second - relay workers. Secondly, if synchronous recordings will often go to some devices, it is necessary that they hang on neighboring channels, and trouble happened here, because often the light bulbs that burn only together hang on different dimmers and have to send several requests, spending valuable channel bandwidth.

I didn’t have the slightest desire to form MODBUS packages manually, so I ended up using the libmodbus library, it is quite convenient and stable. The qModMaster project in which the basic functions of reading and writing are implemented helped me to deal with it, and for debugging I used HHD Free Serial Virtual Ports (its free functionality is more than enough) and DiagSlave Modbus Slave Simulator. Of course, Modbus Slave from the Modbus tools set is much more convenient, but after a month of free use it turns into a pumpkin, and somehow I didn’t really want to buy it for one project.

What is important to understand when working with MODBUS:
1. Take out work with each port in a separate thread. Most likely, you will want to use the entire bandwidth of the port, even if you have only one port, then without additional flows it will be guaranteed impossible to work with the visual interface.

2. Think about the queue system and priorities. I ended up doing this: the class handling the port has two sets of queues, a write queue, and a read queue. In the record, the first stage is allocated for one-time tasks that must be performed (closing / opening the relay, for example), the remaining queues are specific devices and are used in a situation when you need to write a long sequence of values, but not critical, so that they are all written (for example, the flickering of dimmer-controlled light bulbs is implemented in this way), each read queue contains read requests from one specific device. The reading priority is also set, I just sewed it with a constant into the code, because I did not invent the adaptive method, but I still don’t need a user adjustment. Read priority means that if there are jobs in both the read queue and the write queue, then for every X write operations, one read operation is guaranteed.

A revolving system is used inside the read / write queues. The task is taken from the first stage, then from the second, etc., then the circle repeats. In the case of recording queues, this led me to another nuance, if I always take the first task, then the time to complete the queue will increase in proportion to the number of queues. In such a situation, it is impossible to fire up the lamp for ten seconds; with the same queue length, it can flare up for ten seconds, a minute, and two, depending on the load on the port. Fortunately, this task is solved very simply - when completing a task from a queue, before taking the top task, I drop tasks according to the number of queues (of course, except in the case of a dedicated first stage, where all tasks are performed).

3. Do not take unnecessary actions. If you don’t need a sensor, don’t read from it, if you wait for the activation signal from the sensor and you don’t need another one, erase all tasks related to this sensor from the queue. It sounds quite banal, but at the first tests on the hardware I was faced with a situation where, due to the accumulated queues, the sensor I was starting to interrogate about five minutes after it was turned on. However, at that time I still didn’t have revolver lines for reading.

4. If you control the brightness of the light, then know that the dimmer of course has 256 gradations of brightness, but the change from 0 to 1, from 1 to 10 and from 64 to 255 is perceived almost equally when you need to make a pulsating light or a smooth glow (which is still the truth will be stepped) this must be considered.

Sound
Play sound in a certain channel, they said. It will be easy, they said.
Perhaps nothing in this task has not knocked me down as much as the sound. I expected that it might be a problem to choose a sound card, but I didn’t expect that playing a sound in a strictly defined channel so that it did not flow into other channels would be so unbanal.
Qt means disappeared immediately, there is nothing even close to this in this library. I found it useful to investigate sound libraries, it turned out that normal people need to create three-dimensional scenes, the task of moving sound sources, but not playing in one particular column.
The first realized variant passes among me, like a very dirty hack. I took single-channel wav files, parsed them and wrote a new wav-file, in which the number of channels is indicated as 8, samples are recorded in the required, and the rest are zeros. The disadvantages of this approach are quite obvious, the analysis of sound in any other format becomes more complicated, there is no single standard for which channel within the wav file will go to which column (that is, if you change the sound card, you may have to reconfigure everything), you need to redo the file every time when you want to play it in another channel. This option somehow worked, but frankly did not suit me and I continued to search. The search was rewarded, I found the BASS library from un4seen developments. In this beautiful library, when playing a sound, simply set the desired channel and get the desired effect. Here, of course, there are also pitfalls.
First, the sound should be played in a separate stream, the library does not.
Secondly, it is necessary to carefully monitor and disable all virtual 3D scenes in the sound card drivers (this also includes all sorts of tweakers for sound, like DFX), the presence of any such setting smears the sound on the channels (and it’s good if only on the neighboring, and not generally all).
Thirdly, BASS has a small undocumented feature. When using only the base library, you can thus play in one channel, but you cannot mix the sound in several channels; trying to apply the flags of several channels leads to the ignoring of this setting by the library. Fortunately, this problem is solved with the help of the BASSmix library from the same developer. The final playback on several channels (but within one zvukovuhi) looks like

bool res = BASS_SetDevice(device_);//device_ -    if(!res) { int code = BASS_ErrorGetCode(); if(code == BASS_ERROR_INIT) BASS_Init(device_, 44100, BASS_DEVICE_SPEAKERS, 0, 0); else { //  } } mixer_ = BASS_Mixer_StreamCreate(44100, 8, BASS_MIXER_END); if(mixer_ == 0) { //  } for(uint j = 0; j < channel_.size(); j++) { HSTREAM chan = BASS_StreamCreateFile(false, path_.toLocal8Bit().data(),0,0,BASS_STREAM_DECODE|BASS_SAMPLE_MONO);//     - res = BASS_Mixer_StreamAddChannel(mixer_, chan, channel_[j]); //channel_ -     channels_.push_back(chan); if(!res) { //  } } res = BASS_ChannelPlay(mixer_, true); if(!res) { //  } 


Video

Video playback also appeared to be associated with some problems related to customer requirements. Since the TV is mounted in a quest and should not cause the sensation of the screen, it was important that no windows flashed on it, and the video playback immediately began. Unfortunately, I haven’t managed to solve this problem completely yet.
The first option was implemented by means of Qt. In general, everything was fine with him, I took QDesktopWidget, created a child with him QVideoWidget, painted in black, and then began to display video on him using QMediaPlayer. Everything was fine, no artifacts, no flickering, but alas, in Qt5.5, video playback sometimes hangs tight. Irregular, unpredictable and without a declaration of war. And it just hangs, scoring zvukovuhu stuttering piece of voice acting, which disappears only after a reboot. I don’t know if this bug was decided in Qt 5.6, but judging by the forums, another bug was introduced there, when the video starts to wipe out all the processor resources that I’ll reach, which I don’t really like.

I did not manage to find another library in a quick way (I don’t have the slightest desire to disassemble the sequence of frames and synchronize the sound with them), so I decided to use VLC Player, which provides very powerful tools for launching from the command line. The final set of flags is as follows:

--qt-minimal-view --no-qt-fs-controller --qt-start-minimized --qt-fullscreen-screennumber = n --fullscreen --play-and-exit --no-osd --no -qt-bgcone filename

With this set of keys, the player starts minimized (no window flashes on the screen), then expands the full-screen video playback on the desired display, does not show any controls and file name, and when it finishes playing, exits the program.

It was with this last point that a problem arose that has not yet been resolved. Exit at the end of playback is necessary, because some of the events in the quest occur upon completion of video playback, and you can track the end of playback in a third-party program only after the completion of QProcess, but when the program is completed for half a second, the last frame with a bunch of artifacts is shown, which spoils the whole the picture. So far I have offered to customers to remount the video, adding blackness at the end of a second or the other, since the artifacts should not be visible on it, but let's see what happens.

Another feature of using this method is that when getting a list of QDesktopWidget or QScreen, Qt sorts them in some sort of order not related to screen numbering in the system, and VLC in the key - qt-fullscreen-screennumber = n just wants the system number (though it numbers from 0, not from 1), so in the end I got the QScreen list and cut the screen number from the device name (well, then I also subtracted the unit to get the number for VLC), it looks like so

  QList<QScreen*> screens = QGuiApplication::screens(); for(int j = 0; j < screens.size(); j++) { QString name = screens[j]->name()+" [" +QString::number(screens[j]->size().width())+"x" +QString::number(screens[j]->size().height())+"]"; int num = screens[j]->name().section("DISPLAY",-1).toInt(); ui->videoDeviceList->addItem(name, screens[j]->name().section("DISPLAY",-1)); } 


I suspect that this method will not work under * nix systems, because there the screens will not be called DISPLAY1, DISPLAY2, etc., but since the program was developed purely under Windows and will not run on other systems, I don’t see any reason .

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


All Articles