In the previous parts (
Part 1 ,
Part 2 ,
Part 3 ), the creation of two processes was described: the simulator and the control process ... Now it’s
time for
the adjustment .
So, at the moment we have already implemented and run the following processes:
you can start adjusting ...
Adjustment. The prologue.
The time has come for a small explanatory picture to understand what is happening here ...

In fact, of course, everything is a bit more complicated, but the figure is designed to help understand how we have
“information flows” .
So..- All interaction goes through SharedMemory
- The management process gets saved to the SM command, and from the SM receives notification of a change in the level sensor (Level_s)
- The control simulator receives command change notifications from the SM, and in SM it stores the simulated state level
- All interaction goes through sensors.
')
Once all the interaction takes place through the sensors, the adjustment, in general, is “setting the sensors” and “tracking the current status of the sensors”. For these purposes, several utilities are included in
libuniset2-utils :
- uniset2-admin is a multifunctional utility, but in this case it also allows setting ( setValue ) and watching the current status of sensors ( getValue )
- uniset2-smviewer is a utility that allows you to immediately see the status of all sensors registered in SM
- uniset2-smonit - utility, "tracking" (monitoring) for changes in these sensors
All these utilities are actively used in debugging, but they are tools for tracking the “external” life of processes. And there are two additional mechanisms that allow you to observe the "life of processes" from the inside:
- vmonit - monitoring of internal variables of the object
- LogServer - remote reading of process logs
Of course, it would be possible to start the setup by running two processes at once, and watch what is happening there. But it is more correct, if possible, to carry out the adjustment of processes separately.
Let's start with the simulator.Debugging simulator
- Run SM - Enter the src / Services / SharedMemory / directory . Run the start_fg.sh script
- Run the simulator - Enter the src / Algorithms / Imitator / directory . Run the start_fg.sh script
Check that objects are available. Go to
src / Services / Administrator / and run
./existMust see the following
Screen output[pv@pvbook Administrator]$ ./exist ||=======******** UNISET-EXAMPLE/Services ********=========|| !!!!!! ||=======******** UNISET-EXAMPLE/Controllers ********=========|| (22000 )SharedMemory1 <--- exist ok ||=======******** UNISET-EXAMPLE/Objects ********=========|| (20001 )Imitator1 <--- exist ok [pv@pvbook Administrator]$
Now back to the
beginning and remember what the simulator should do.
The simulator should, on arrival of the
cmdLoad_C = 1 command, start simulating tank filling (growth of the
Level_AS sensor), and on the arrival of the
cmdUnload_C = 1 command - simulate tank emptying (reduce the
Level_AS sensor).
So we must
- set the cmdLoad_C = 1 sensor and see the Level_AS increment
- set the cmdUnload_C = 1 sensor and see the decrease in Level_AS
Let's look at all the current status of the sensors. Let's use the
uniset2-smviewer utility .
Enter the
src / Services / SMViewer directory and run
./start_fg.shSee this ...
Screen output [pv@pvbook SMViewer]$ ./start_fg.sh ====================================================== SharedMemory1 ------------------------------------------------------ ( 101) | AI | Level_AS | 0 ( 100) | DI | OnControl_S | 0 ------------------------------------------------------ ====================================================== SharedMemory1 ------------------------------------------------------ ( 103) | DO | CmdUnload_C | 0 ( 102) | DO | CmdLoad_C | 0 ------------------------------------------------------ ====================================================== SharedMemory1 ------------------------------------------------------
As you can see, everything is by zero ... Of course, there will be A LOT of sensors in a real project, and therefore you can (
and should) use
uniset2-smviewer with
grep if you want to somehow filter the output ...
The second utility we need is
uniset2-smonit to see how the level sensor will change. Let's run it. Go to
src / Services / SMonit / and ...
Small subtlety when using smonitSince uniset2-smonit starts and tracks the change of the specified sensors, it must have a “return address”. For a uniset system, such an address is an identifier . By default, uniset2-smonit tries to run as TestProc . Those. it is assumed that in configure.xml in the objects section an object with the name = "TestProc" is declared. But if for some reason you don’t want to have such an object in your project, you can run uniset2-smonit with the --name XXX parameter and specify any name from existing objects in the project (not currently used).
For this utility, you need to specify which sensors we want to monitor, so it has a key
--sid(for simplicity, it is entered immediately in
start_fg.sh ). As a parameter
--sid you can specify an
identifier , and you can specify the
name of the sensor. We will specify the name Level_AS.
./start_fg.sh Level_AS
smonit starts and hangs waiting for changes. At the same time, the current state of the sensor is displayed at the beginning. From the output, you can see the
name ,
the last modification time (including microseconds),
the process
identifier that saved this sensor in SM (in this case, Imitator1), and the current
value (and in the form of float -
fvalue ).
Everything seems to be ready, set the cmdLoad_C = 1 sensor and see how the Level_AS sensor ran.
For exposure just we will use admin th.
[pv@pvbook Administrator]$ ./setValue CmdLoad_C=1
and switching to the console, where smonit is running, look at how the sensor ran (from 0 to 100).
So the increase is working. The reduction is checked in the same way ... first you need to remember to reset the previous command, and we will do it in one fell swoop
[pv@pvbook Administrator]$ ./setValue CmdLoad_C=0,CmdUnload_C=1
smonit ran in the opposite direction (from 100 to 0)
Monitoring object's internal variables (vmonit)
Now I will describe the mechanism that allows you to view the internal variables of the object. In general, everything is very simple. Knowing the identifier or name of the object, you can simply request information from it.
So, everything is up and running. Let's see what the Imitator1 object will show us.
Go to
src / Services / Administrator and run the command
./oinfo Imitator1As you can see from the output, the
oinfo command allows
you to see
- The state of all inputs and outputs of the object with bindings to sensors ( internal in_, out_ variables)
- The current list of working timers, with the remaining work time, etc.
- The values of all variables with which the process is running ( declared in src.xml )
- Internal information on the object (the size of the message queue, which was the maximum, were there overflows)
- As well as user information
About user information I will say a little more ...
Each object (more precisely, the skeleton class) has a special function
virtual std::string getMonitInfo() override;
redefining that, you can display your information in the form of text (string). In this case, the simulator, for example, writes
"The current mode of operation: fill" (or "empty") . You can write something more complicated.
An example of the implementation of the function in the simulator string Imitator::getMonitInfo() { ostringstream s; s << " : " ; if( in_cmdLoad_c ) s << " .." << endl; else if( in_cmdUnload_c ) s << " .." << endl; return std::move(s.str()); }
Adding to the information output of its variables
Of course, when you write your management process, you will most likely have, in addition to the variables declared in the xml file, some other class fields of your own. And of course, you also want to follow (display information) about the current state and your variables.
There is nothing easier . Suppose we add two command counters to the simulator.
Adding to Imitator.h ... private: unsigned int numCmdLoad = { 0 }; unsigned int numCmdUnload = { 0 };
Then, if you want to see them in the
oinfo output, simply in the constructor we will make two
magic calls:
Adding to Imitator.cc Imitator::Imitator( UniSetTypes::ObjectId id, xmlNode* cnode, const string& prefix ): Imitator_SK(id, cnode, prefix) { ... vmonit(numCmdLoad); vmonit(numCmdUnload); }
Those. just
wrapped your variables in
vmonit (xxx) . There certainly was not without
macro magic , but probably it doesn’t
bother much ...
As a result, we will already see our variables on the screen (
started among others ).
Screen output (re-call ./oinfo) IMPORTANT: while only standard simple types
are supported: bool, int, long, etc., for everything else there is a universal function
getMonitInfo ()Remote reading of logs (built-in LogServer)
As you know, no matter how many debugging mechanisms exist, the favorite
cout (
or not the favorite printf ) will still be used. Well, libuniset provides this method as well. In fact, the topic is very extensive, if you disclose all the features and details, then this is a topic for a separate article. Therefore, I will show the application and tell you some details ...
In the generated class skeleton, there is a special object for logs -
log . It essentially has an interface as cout, only this is
shared_ptr , so you need to use it as a pointer. for example
log->info() << "......information.." << endl;
or
log->crit() << "......critical" << endl;
The log has 15 levels, you can enable them "in parallel" (eg, info, warn, crit), it can specify the file where to write logs, you can enable and disable the output of date and time at the beginning of each line, etc. . In general, a lot of standard features. For each object, you can enable or disable logs simply by specifying a command line argument when starting.
Log management through command line arguments --ObjectName-log-add-levels info,warn,crit,level1,... - ( ) --ObjectName-log-del-levels info,warn,crit,level1,... - ( ) --ObjectName-log-set-levels info,warn,crit,level1,... - ( )
But all this would not be so interesting if it were not for the existence of such a mechanism as
LogServer .
Each object has a built-in LogServer, which by default does not start, accordingly it does not consume resources and is not visible at all. But a simple command line argument
--ObjectName-run-logserver
we can activate it. By default, it runs on
localhost , and uses the object identifier as the port. But you can also force the launch
host and
port .
Commands to override host and port --ObjectName-logserver-host xxx --ObjectName-logserver-port zzz
Once the object is running LogServer, we can read its logs, and
remotely . Just connect to the specified host and port. There is a special utility
uniset2-log for reading logs. With the help of it, in addition to reading logs, you can also control the level of log output, write to a file, etc., i.e. exercise
full control over the object logs. This is a very convenient mechanism, since allows you to enable and disable logs
without restarting the program (
and you can’t often stop the process, but you really need to look at what’s going on inside).... let's just show ...So, everything is running, and we added a line to the simulator start_fg.sh
--Imitator1-run-logserver
By the way, in the output
./oinfo , if someone did not notice, information is displayed on whether the LogServer is running. But let me show you again (
go to the src / Services / Administator / directory and run the command ./oinfo Imitator1 ).
Displaying information about the object (note the LogServer) So logserver is running on
localhost and port
20001 . But by default
(unless, of course, the developer forcibly turns them on) , the logs are disabled. Accordingly, we will not just connect, but also immediately turn on
all (any) logs to immediately start
seeing them. Let's connect
(add the -v key to see the debug information about who we are connecting to) uniset2-log -i localhost -p 20001 -v -a any
I added a log (level 3) to the timer function, for output to demonstrate the operation.
Additive to Imitator.cc (mylog3) void Imitator::timerInfo( const UniSetTypes::TimerMessage* tm ) { if( tm->id == tmStep ) { if( in_cmdLoad_c )
Then we connect (as indicated above) and in another console (
go to src / Services / Administrator ) set the command
./setValue CmdLoad_C=1,CmdUnload_C=0
... and after some time, the opposite
./setValue CmdLoad_C=0,CmdUnload_C=1
But what we see in the logs (
remotely read )
Taking the opportunity to read logs remotely, it is important not to forget that if you
turn them
on , it would be nice to
turn off when you log
off , because in a real system logs should not work,
input / output is expensive. And yet, I will point out a little opportunities that are not disclosed earlier
- LogAgregator - an object that allows you to aggregate the logs from multiple objects and manage them "centrally"
- Support in LogAgregatore regular expressions (C ++ 11), allowing you to choose which logs ( from which objects ) you want to read more flexibly
- The ability to specify several commands in uniset2-log at once to turn on some logs, turn off others and, for example, read third ones. All this is one team.
In a real application, the
LogServer of course runs one on all objects (within a single executable file), which are lined up in the hierarchy using the
LogAgregator and can be flexibly managed.
Small total
Talking about some mechanisms each time, I try to strike a balance between a
bunch of details of internal functioning and simplicity of
external application. Those. "
Here are ready-made commands, take and use - they work out of the box ." Therefore, I did not tell a lot, and maybe something remains unclear ... I will try to answer your questions.
In general, if we do not assume that normal testing is much more than any
“tests” (boundary cases, max, min task, simultaneous setting of commands, etc.), then we finished with setting up the simulator. The main thing was to demonstrate the
setup tools included in libuniset2:
- utilities for working with sensors and monitoring their condition
- mechanism for remote viewing of the state of internal variables of the object
- mechanism for remote reading and logging control (for each object)
Approximately in the same way, we can adjust the work of our management process, but better I will show the adjustment of the control algorithm using the example of using a more
advanced method of setting up - writing functional tests using
uniset2-testsuite . About it in the
following part ...Well, in the end, as usual, reference: