* Fix formatting, grammar, and syntax Additionally, a sentence that referenced something that isn't used anywhere was removed. * Promote headers, Format stuff Many headers were far too deep. In some cases, the highest header was h4. To correct this, the offending headers were promoted once or twice as appropriate. Minor formatting changes were made, too. * Fixed an incorrect conversion constant. * Update library dependency explanation The old explanation was confusing and seemingly self contradicting in places. This new explanation aims to fix that. Co-authored-by: Matthew Elmer <m.elmer@mailbox.org>
22 KiB
Home → Documentation Home → Simulation Capabilities → Variable Server |
---|
When running a Trick simulation, unless specifically turned off, a server called the "variable server" is always up and listening in a separate thread of execution. The variable server is privy to simulation parameters and their values since it resides in an asynchronous simulation thread. Threads share the same address space as their siblings and parent. Clients connect to the variable server in order to set/get values of Trick processed variables. You may already be familiar with the Trick applications that use the variable server: the simulation control panel, Trick View (TV) , Event/Malfunction Trick View (MTV) , and the stripchart.
The variable server is a convenient way for external applications to interact with the simulation. Any application that needs to set or get simulation parameters may do so through the variable server. The external application need not be on the same machine since the connection to the variable server is via a Trick communication TCP/IP socket.
User accessible routines
These commands are for enabling/disabling the variable server, and for getting its status. The variable server is enabled by default.
int var_server_set_enabled(int on_off);
int var_server_get_enabled();
Disabling the variable server will disable all Trick runtime GUIs: simulation control panel, TV, MTV, and stripchart.
These commands are for toggling information messages from the variable server (i.e., commands received from ALL clients). The messages go to the terminal, the simulation control panel, and the "send_hs" file in the RUN directory. The variable server information message capability is off by default.
int set_var_server_info_msg_off();
int set_var_server_info_msg_on();
These commands are also for toggling information messages from the variable server (i.e., commands received from ALL clients). The messages only go to a dedicated "varserver_log" file in the RUN directory. The variable server log capability is off by default.
int set_var_server_log_off();
int set_var_server_log_on();
Getting and Setting the Variable Server Port Information
To set the variable server port to a fixed number in the input file use var_server_set_port()
trick.var_server_set_port( unsigned int port )
To get the variable server host and port information in the input file use var_server_get_hostname() and var_server_get_port().
trick.var_server_get_hostname()
trick.var_server_get_port()
Additional TCP or UDP sockets can be opened as well. Additional TCP sockets operate the same way as the original variable server socket. A UDP socket will only host 1 variable server session, and the responses will be sent to the latest address that sends commands to it.
Note that this is not necessary to allow multiple variable server clients - any number of clients can connect to the original variable server port.
trick.var_server_create_udp_socket( const char * source_address, unsigned short port )
trick.var_server_create_tcp_socket( const char * source_address, unsigned short port )
Commands
The variable server accepts commands in the form of strings. The variable server parses these commands using the Python input processor. So in theory, any Python valid syntax is acceptable to the variable server. This section lists the commands that are specific for the variable server. Commands are sent over a Trick communication TCP/IP socket to the variable server. Multiple commands (newline separated) can be sent in the string over the socket. The variable server will send back information to the requesting client.
If the command contains a syntax error, Python will print an error message to the screen, but nothing will be returned to the client.
Adding a Variable
trick.var_add( string var_name )
or
trick.var_add( string var_name , string units )
Adding a variable will tell the variable server to send the variable's value back to the client at a specified frequency. An optional units parameter may be attached to the variable as the desired return units. Multiple variables may be added to the list to be sent back to the client. The format of the returned values are described below, Ascii Format or binary format.
Simulation time as a decimal number in "seconds" is available through a special var_add command. This time marks the simulation time at the start of the variable server's task to copy variables.
trick.var_add("time")
Time Homogeneous or Synchronous Data
Copying Data Out of Simulation.
trick.var_set_copy_mode(int mode)
There are 3 options to when the variable server will copy data out from the simulation. Each option has unique capabilites.
Asynchronous Copy (mode = trick.VS_COPY_ASYNC or 0)
This is the default. Values are copied out of the sim asynchronously. Copies are done approximately at the var_cycle() rate during run and freeze mode. A separate thread is used to copy the data. The data is not guaranteed to be time homogenous. This mode does not affect the main thread real-time performance.
End of Main Thread Execution Copy (mode = trick.VS_COPY_SCHEDULED or 1)
This mode copies data at the end of execution frame. Copies are done exactly at the var_cycle() rate after the main thread has finished all of it's jobs scheduled to run at that time step both in run and freeze mode. All variables solely calculated in the main thread are guaranteed to be time homogenous. Variables calculated in child threads are not guaranteed to be time homogenous. Copying data may very slightly affect the main thread real-time performance.
Top of Frame Copy (mode = trick.VS_COPY_TOP_OF_FRAME or 2)
This mode copies data at the top of frame. Copies are done at a multiple and offset of the Executive software frame. During freeze mode copies are made at a multiple and offset of the freeze frame. With careful planning, all variables from all threads can be guaranteed to be time homogenous. Copying data may very slightly affect the main thread real-time performance.
To set the frame multiplier and frame offset between copies use the following commands. The frame refers to the software frame in the Executive. In freeze mode a different multiplier and offset are used.
trick.var_set_frame_multiple(int mult)
trick.var_set_frame_offset(int offset)
trick.var_set_freeze_frame_multiple(int mult)
trick.var_set_freeze_frame_offset(int offset)
Writing Data Out of Simulation.
trick.var_set_write_mode(int mode)
There are 2 options when the variable server writes the data.
Asynchronous Write ( mode = trick.VS_WRITE_ASYNC or 0 )
This is the default. Values are written onto the socket asynchronously. Writes are done approximately at the var_cycle() rate during run and freeze mode. A separate thread is used to copy write data. This mode does not affect the main thread real-time performance.
Write When Copied ( mode = trick.VS_WRITE_WHEN_COPIED or 1 )
Values are written onto the socket as soon as they are copied from the simulation. The write rate depends on the copy. Writes are done in the main thread of execution. This can greatly affect real-tim performance if a large amount of data is requested.
Old Style var_sync() Command
trick.var_sync(bool mode)
var_sync() was previously used to control the copies and writes from the simulation. The number of options has outgrown what a single var_sync command can configure. It may still be used to configure a subset of the copy/write combinations.
trick.var_sync(0) # asynchronous copy and asynchronous write.
trick.var_sync(1) # end of main thread copy and asynchronous write.
trick.var_sync(2) # end of main thread copy and write when copied.
Sending the Return Values Immediately
trick.var_send()
The var_send command forces the variable server to return the list of values to the client immediately.
Sending variables only once and immediately
trick.var_send_once( string var_name)
The var_send_once command forces the variable server to return the value of the given variable to the client immediately.
trick.var_send_once( string var_list, int num_vars)
var_send_once can also accept a comma separated list of variables. The number of variables in this list must match num_vars, or it will not be processed.
Changing the Units
trick.var_units( string var_name , string units )
The returned values can be converted to other units of measurments. The var_units command tells the variable server what units to use. If the units are changed, then the units are included in the returned string to the client.
Removing a Variable
trick.var_remove( string var_name )
Removing a variable removes the variable from the list returned to the client.
Clearing the List of Variables
trick.var_clear()
To clear the whole list of variables sent to the client.
Exiting the Variable Server
trick.var_exit()
Disconnects the current client from the variable server.
Checking for existence of a variable
trick.var_exists( string var_name )
To test if a variable name exists. A special response is sent to the client when this command is processed.
In var_binary mode, the (4 byte) message indicator of the response will be 1, followed by a (1 byte) value of 0 or 1 to indicate the existence of the variable.
In var_ascii mode: the message indicator of the response will be "1" followed by a tab, then an ASCII "0" or "1" to indicate the existence of the variable.
Changing the Return Value Cycle Rate
trick.var_cycle( double cycle_rate )
Changes the rate of the return messages to the client. This rate is estimated and may not perfectly match the requested rate.
Pause the Variable Server
trick.var_pause()
Pauses the return values sent to the client. Even when paused, the variable server will accept new commands.
Unpause the Variable Server
trick.var_unpause()
Resumes sending the return values to the client.
Setting Ascii Return Format
trick.var_ascii()
Sets the return message format to ASCII. See below for the format of the message.
Setting Binary Return Format
trick.var_binary()
Sets the return message format to Binary. See below for the format of the message.
trick.var_binary_nonames()
This variation of the binary format reduces the amount of data that is sent to the client. See below for the exact format.
Sending stdout and stderr to client
trick.var_set_send_stdio(bool on_off)
If var_set_send_stdio is called with a true value, then all python stdout and stderr output will be redirected to the client instead of printing to the simulation stdout/stderr location. Note: output from C/C++ code called from python will direct it's output to the simulation stdout/stderr location. See the return message format for Stdio.
This is useful to get output from the simulation such as the return values of a function.
# Example in a variable server client to get the Trick version used to compile a sim
# The C prototype is "const char *exec_get_current_version(void) ;"
trick.var_set_send_stdio(True)
sys.stdout.write(trick.exec_get_current_version())
# The returned text will look like this. See the return message format below
4 1 10
10.7.dev-1
# If a "print" is used instead of sys.stdout.write, a second message is sent containing
# a single newline.
print "trick.exec_get_current_version()"
4 1 10
10.7.dev-14 1 1
<- a single newline is the second message
Debugging Variable Server Messages
trick.var_debug(int level)
The level may range from 0-3. The larger the number the more debugging information is printed to the screen (for the current client only).
Logging Messages to file.
These commands are for toggling information messages from the variable server (for this client only). The messages only go to a dedicated "varserver_log" file in the RUN directory. The variable server log capability is off by default. (See the global variable server commands @link Trick::VariableServer::set_var_server_log_on() set_var_server_log_on() @endlink and @link Trick::VariableServer::set_var_server_log_off() set_var_server_log_off() @endlink for toggling the logging capability for ALL clients.)
trick.var_server_log_on()
trick.var_server_log_off()
Setting Variable Server Client Tag
trick.var_set_client_tag(string name)
This sets an identifying name tag to be associated with the current client that will be printed with each information message displayed. Information messages are displayed as a result of @link Trick::VariableServer::set_var_server_info_msg_on() set_var_server_info_msg_on() @endlink, @c var_server_log_on() or @c var_debug(). For instance, Trick sets a name tag for each of its variable server clients (simulation control panel is "SimControl", TV is "TRICK_TV", etc.).
Byteswapping
trick.var_byteswap(bool on_off)
Returned Values
By default the values retrieved are sent asynchronously to the client. That is, the values retrieved by the variable server are pulled directly from memory asynchronously and do not guarantee synchronization from the same simulation execution frames unless the var_sync command is used. Values will be returned to the client in the same order that they were issued in the var_add command(s). Typically the client receives the data from the variable server in a buffer via the tc_read command (see TrickComm for more information).
Ascii Format
The default format, or if var_ascii is commanded specifically, causes the variable server to return a buffer containing a tab delimited character string in the following format:
0\t<variable1 value>[\t<variable2 value>. . .\t<variableN value>]
where N is the number of variables registered via the var_add command(s). The "\t" represents a tab character, and the "\n" is the newline character that always ends the string. Note that if a value being returned is itself a character string data type, any tab (or other unprintable character) that occurs within the character string value will appear as an escaped character, i.e. preceded by a backslash.
The 1st value returned in the list will always be a message indicator. The possible values of the message indicator listen in the table below.
Name | Value | Meaning |
---|---|---|
VS_IP_ERROR | -1 | Protocol Error |
VS_VAR_LIST | 0 | A list of variable values. |
VS_VAR_EXISTS | 1 | Response to var_exists( variable_name ) |
VS_SIE_RESOURCE | 2 | Response to send_sie_resource |
VS_LIST_SIZE | 3 | Response to var_send_list_size or send_event_data |
VS_STDIO | 4 | Values Redirected from stdio if var_set_send_stdio is enabled |
VS_SEND_ONCE | 5 | Response to var_send_once |
If the variable units are also specified along with the variable name in a var_add or var_units command, then that variable will also have its units specification returned following its associated value separated by a single blank. For example, if the 2nd of N variables was specified with {} in either a var_add or var_units command, the returned string would be in the following format:
0\t<variable1 value>\t<variable2 value> {<variable2 units>}. . .\t<variableN value>
Note that the maximum message size that the variable server sends to the client is 8192 bytes. If the amount of data requested is larger than that, the ASCII message will be split into multiple messages. The client is responsible for concatenating the multiple messages back together. (Hint: look for the "\n" delimter)
If a syntax error occurs when processing the variable server client command, Python will print an error message to the screen, but nothing will be returned to the client.
If a var_add command was issued for a non-existent variable, there will be a one time Trick error message printed to the screen, but the resulting data sent to the client is still ok. The value returned for the non-existent variable is the string "BAD_REF".
Binary Format
By specifying the var_binary or var_binary_nonames command, the variable server will return values in a binary message formatted as follows:
<message_indicator><message_size><N>
<variable1_namelength><variable1_name><variable1_type><variable1_size><variable1_value>
<variable2_namelength><variable2_name><variable2_type><variable2_size><variable2_value>
. . .
<variableN_namelength><variableN_name><variableN_type><variableN_size><variableN_value>
Where the first 12 bytes are the message header:
- message_indicator is the same possible values as in var_ascii shown above : a 4 byte integer
- message_size is the total size of the message in bytes (NOT including message_indicator) : a 4 byte integer
- N is the number of variables registered via the var_add command(s) : a 4 byte integer . and the remaining bytes of the message contain the variable data:
- variable_namelength is the string length of the variable name : a 4 byte integer (NOT present for var_binary_nonames)
- variable_name is the ASCII variable name string : @e variable_namelength bytes of string (NOT present for var_binary_nonames)
- variable_type is Trick data type of the variable : a 4 byte integer (see Trick::MemoryManager::TRICK_TYPE)
- variable_size is number of bytes the variable occupies in memory : a 4 byte integer
- variable_value is the variable's current value : @e variable_size bytes of @e variable_type
When the client has requested a very large amount of data, it is possible that it may require more than one message to be returned. The maximum message size is 8192 bytes, so if the data returned by the variable server requires more space than that (once formatted into the above message format), then the variable server sends more than one message. This is indicated by the @e N field. For example, if the client has requested 15 variables, and @e N = 15, then everything is contained in that one message. However if @e N < 15, then the client should continue reading messages until all @e N received add up to 15.
If a syntax error occurs when processing the variable server client command, Python will print an error message to the screen, but nothing will be returned to the client.
If a var_add command was issued for a non-existent variable, there will be a one time Trick error message printed to the screen, but the resulting data sent to the client is still ok. The message returned for the non-existent variable will have a type of 24 and it's value will be the string "BAD_REF".
Stdio Format
These messages are sent to the client if stdout and stderr are redirected. See "Sending stdout and stderr to client" for more details.
4 <stream> <size>
<text>
- message_id Stdio messages are message_id = 4.
- stream is the stream the message was written to. 1 = stdout, 2 = stderr
- size is the number of bytes in the section. The newline between the and is not counted in the size.
- text is the message
Only output from python is redirected, i.e. "print" or calls to "sys.stdout.write()". C/C++ code called from python will still direct their stdout/stderr to the simulation output location. The "print" statement will send 2 messages, the text in the print, and an additional newline. Calls to sys.stdout.write() only generate 1 message.
Error messages printed by python to stderr may be sent in multiple messages.
Variable Server Broadcast Channel
To connect to the variable server for any simulation, a client needs to know the hostname and port. As of 10.5, the port number is determined by the OS. For external applications the best way to find a varible server port is to listen to the variable server broadcast channel. Every simulation variable server will broadcast the host and port number to the broadcast channel. The channel is address 224.3.14.15 port 9265. All simulations on your network sends it's information to this address and port so there may be multiple messages with variable server information available here. Here is some C code that reads all messages on the variable server channel.
Note that the multicast protocol is disabled by default in MacOS.
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int mcast_socket ;
char buf1[1024] ;
ssize_t num_bytes ;
int value = 1;
struct sockaddr_in sockin ;
struct ip_mreq mreq;
if ((mcast_socket = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("init socket");
}
if (setsockopt(mcast_socket, SOL_SOCKET, SO_REUSEADDR, (char *) &value, (socklen_t) sizeof(value)) < 0) {
perror("setsockopt: reuseaddr");
}
#ifdef SO_REUSEPORT
if (setsockopt(mcast_socket, SOL_SOCKET, SO_REUSEPORT, (char *) &value, sizeof(value)) < 0) {
perror("setsockopt: reuseport");
}
#endif
// Use setsockopt() to request that the kernel join a multicast group
mreq.imr_multiaddr.s_addr = inet_addr("224.3.14.15");
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(mcast_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, (socklen_t) sizeof(mreq)) < 0) {
perror("setsockopt: ip_add_membership");
}
// Set up destination address
sockin.sin_family = AF_INET;
sockin.sin_addr.s_addr = htonl(INADDR_ANY);
sockin.sin_port = htons(9265);
if ( bind(mcast_socket, (struct sockaddr *) &sockin, (socklen_t) sizeof(sockin)) < 0 ) {
perror("bind");
}
do {
num_bytes = recvfrom(mcast_socket, buf1, 1024, 0 , NULL, NULL) ;
if ( num_bytes > 0 ) {
buf1[num_bytes] = '\0' ;
printf("%s\n" , buf1) ;
}
} while ( num_bytes > 0 ) ;
return 0 ;
}
The information sent by each variable server is a tab delimited list of strings
- Hostname
- Port
- User
- Process ID (PID)
- Simulation directory
- S_main command line name
- Input file
- Trick version of simulation
- User defined tag
- Port (duplicate field for backwards compatibility)