Release of TCA Version 8.5 Summary ======= New functionality added: * Non-blocking query with callback procedure (tcaQueryNotify) * Event handling for generic file descriptors (tcaAddEventHandler, tcaRemoveEventHandler) * Cleanly halting central under VxWorks (killCentral) * Explicit support for enumerated types in format strings * Polling periods and delay commands in milliseconds * Initialize the TCA data structures, without connecting to central Version 8.5 is now 99.94% "pure" -- free of memory leaks, to the best of our knowledge. TCA now has support for CLISP. Xforms interface for Runconsole. Changes in TCA 8.5.30 * Compiles under glibc versions of linux, and specifically under Redhat 5.2 Changes in TCA 8.5.31 * New "bridge" program. Program is now a full-fledged two-way bridge to pass messages back and forth between two central servers. See details below. * Handles tcaWaitForGoal correctly. * More robust to changing "contexts" (knowing which central you are connected to). * Fixed a bug related to unlocking resources Changes in TCA 8.5.31 * Upgraded to run under RedHat 6.x (thanks to Oleg Rodionov @ Drexel). * Removed lots of compiler warnings. DETAILS ======= New Functionality ----------------- TCA_RETURN_TYPE tcaQueryNotify (const char *msgName, void *query, REPLY_HANDLER_FN replyHandler, void *clientData) typedef void (*REPLY_HANDLER_FN)(void *replyData, void *clientData) Previously, the only way to do a non-blocking query was to use the tcaQuerySend/tcaQueryReply pair of functions, but this had the disadvantage that tcaQueryReply was still blocking. tcaQueryNotify provides an alternate model for handling query messages -- the call is completely non-blocking, and the reply is handled using a user-specified callback procedure. The first two arguments are the same as with tcaQuery: the message name and query data. The third argument is a callback procedure that, when the module receives the reply to the query, is invoked with a pointer to the reply data and a pointer to arbitrary, user-specified client data (which is specified as the fourth argument to tcaQueryNotify). Note that the message sent using this function is a regular query message. In particular, the module that handles the query sees no difference between messages sent using the blocking and non-blocking forms of query messages. Note also that if a reply is never received, or if the response is NullReply, then the handler will not be invoked. Simple example: void q1Hnd (void *reply, void *clientData) { printf("Received data: %s\n", (char *)reply); /* Note that the reply must be freed, since it is malloc'd up by TCA */ free(reply); *(int *)clientData = TRUE; } void main (void) { int doneFlag = FALSE; ... tcaQueryNotify("q1_msg", &queryData, q1Hnd, &doneFlag); while (!doneFlag) { /* Do some processing here */ tcaHandleMessage(0); /* Listen for reply (or other messages) */ } ... } ________________________________________________________________ TCA_RETURN_TYPE tcaAddEventHandler (int fd, TCA_FD_HND_FN handler, void *clientData) typedef void (*TCA_FD_HND_FN)(int fd, void *clientData) Add a handler that will be invoked every time there is new input available on the file descriptor fd. The handler will be invoked with the file descriptor and a pointer to arbitrary, user-specified client data (which is specified as the third argument to tcaAddEventHandler). Note that it is the responsibility of the handler to actually read the input from the file descriptor. For example, one could set up a handler to read and parse tty input (using the file descriptor fileno(stdin)) or a handler for X events which, in turn, merely calls the standard X event handler. While this function is not as flexible as those provided by the devUtils package, it has the advantages that it is easy to add to existing TCA code, and it can handle events even when TCA is blocking on a query (which the current devUtils package cannot handle). There can be only one event handler per file descriptor. Multiple calls to tcaAddEventHandler will replace the old handler/clientData pair with that of the most recent call. To remove a handler, use tcaRemoveEventHandler, described below. ________________________________________________________________ TCA_RETURN_TYPE tcaRemoveEventHandler (int fd) Remove any event handler associated with the given file descriptor. It is up to the user code to free any client data associated with the event handler. ________________________________________________________________ void tcaModuleInitialize (void) Initialize the TCA data structures, without connecting to the central server. Useful if a user program uses some of the TCA functions (such as tcaMalloc or the list functions), but one does not want an actual socket connection. ________________________________________________________________ void killCentral (void) [VXWORKS VERSION ONLY] This function, which is meant to be invoked from the VxWorks shell, cleanly shuts down the central server, closing all sockets and file descriptors. This enables the central server to be restarted, without having to reboot the real-time board. Essentially, the task id of central is saved at startup, and killCentral sends a SIGTERM to that task. You can do the same thing by using "i" to print a list of active tasks, then doing "kill 0x,15" (15 is the value of SIGTERM). ________________________________________________________________ Enumerated Types in Format Strings In previous versions of TCA, enumerated types had to be represented as integers ("int") in format strings. Now, they can be represented explicitly. This has advantages in data logging, in interfacing with Lisp and, most importantly, in portability, as not all compilers treat enum's as int's. There are two forms for specifying an enumerated type. The basic format is "{enum : }", which indicates that the format is an enumerated type whose last element has the value "maxVal". For example, the format string for "typedef enum {A, B, C, D} ENUM_TYPE" would be "{enum : 3}" (since 3 is the implicit value of D). Similarly, the format for "typedef enum {E=1, F=2, G=4, H=8} ENUM1_TYPE" would be "{enum : 8}". Note that this cannot be used for enumerated types that have negative values -- for those types, you still need to represent them using "int'. The alternate form for specifying an enumerated type includes the actual values themselves: "{enum , , , ..., }". For example, the format for ENUM_TYPE given above would be "{enum A, B, C, D}". There are two advantages of this form of specification: (1) the logs produced by the central server will contain the symbolic values of the enumeration, rather than just the integer values; (2) The LISP version will automatically convert the symbolic value to the associated integer (for C) and vice versa. The symbolic value is the upper-case version of , interned into the :KEYWORD package. For instance, a LISP module could send a message containing the atom :B, and a C-language module would receive the enumerated value "B" (which would have the integer value 1, given the example above). Note that you cannot use the alternate form if the type declaration explicitly sets the enumerated values (e.g., "{enum E, F, G, H}" will not correctly represent ENUM_TYPE1, given above). Of course, as with all of the other format specifiers, enumerated formats can be embedded in more complex format specifications: "{int, {enum A, B, C, D}, [double:3], {enum : 10}}" Another caveat: The colon (:) is a reserved symbol in the TCA format specification language. You cannot use a colon in any of the enumerated values (same for braces, brackets, commas, and periods). ________________________________________________________________ Polling periods and delay commands in milliseconds The period of polling monitors can now be specified in milliseconds. Previously, the shortest period was one second. Now, the "options" to polling monitors (see function tcaCreateMonitorOptions) includes the field "period_msecs", which by default is zero. The period of a polling monitor is: "1000*options->period + options->period_msecs" milliseconds. A new predefined command message, "tca_msecDelayCommand", has been added. This command is similar to "tca_delayCommand" -- when you send this message, t waits for so many milliseconds before terminating. By sequencing this ith other goal and command messages, one can delay their start: int delay = 500; tcaExecuteCommand("bar", barData); tcaExecuteCommand("tca_msecDelayCommand", &delay); tcaExecuteCommand("foo", fooData); This has the effect of starting "foo" half a second (500 msecs) after "bar" is achieved. To reduce the chance for typing errors (or, at least, to have the compiler catch them), tca.h now defines the macros TCA_DELAY_COMMAND and TCA_MSEC_DELAY_COMMAND as the names of these delay messages. ________________________________________________________________ Purify'd Code The software product "purify" has been used to help track down and eliminate all memory leaks and accesses to freed or uninitialized memory (as far as we can tell). In particular, regregistration of messages now does not lose memory. This also corrects a long-standing bug which could cause the central server to crash if a monitor message was reregistered while the monitor was still active. We will continue to monitor the memory usage of central and the TCA library, and clean up any remaining leaks that may still be lurking around. ________________________________________________________________ Support for CLISP TCA now supports CLISP, in addition to the ever-popular Allegro Common LISP. coming soon: Support for Harlequin's Lispworks. Clisp is a lisp system implemented in C and publicly available under the GNU General Public License. It runs on many platforms under many operating systems. The newest versions will always be available via anonymous ftp from ma2s2.mathematik.uni-karlsruhe.de [129.13.115.2], directory /pub/lisp/clisp/. The TCA port was tested on a sparc running sunOS and the 1996-03-14 release of clisp. Instructions: ============= Building CLISP/TCA ------------------ I will assume that clisp is installed as /usr/local/clisp and that tca is installed in /usr/local/tca. The tca library for clisp is different from the allegro library, so you can not currently build both at the same time and keep the libraries in the same directory. Lines that begin with ">" below indicate commands to type into your favourite shell. First, build the clisp version of the tca/lisp library: > cd /usr/local/tca/src > gmake -k -w PUBLIC_LIBS=libtca_clisp.a CFLAGS_LISP="-DLISP -DCLISP" install_libs Then compile the clisp code. > cd /usr/local/tca/lisp > /usr/local/clisp/base/lisp.run -M /usr/local/clisp/base/lispinit.mem -i clispMacros.lisp -c tcaForeignCalls.lisp > /usr/local/clisp/base/lisp.run -M /usr/local/clisp/base/lispinit.mem -i clispMacros.lisp -c primFmttrs.lisp > gmake -k -w PUBLIC_LIBS=libtca_lisp.a CFLAGS_LISP="-DLISP -DCLISP" install_libs Now, create a clisp binary including the tca functions. > cd /usr/local/clisp > clisp-link create-module-set tca > cp /usr/local/tca/lib/libtca_* tca > chmod ugo+w tca/* ; ranlib tca/libtca_*.a > \rm -r -f base+tca tca/*.o Then Edit /usr/local/clisp/tca/link.sh and change the first two lines to be: -------------- Start of link.sh ------------------------ file_list='libtca_clisp.a libtca_lisp.a' mod_list='tcaForeignCalls primFmttrs' make CC="${CC}" CFLAGS="${CFLAGS}" INCLUDES="$absolute_linkkitdir" $file_list NEW_FILES="$file_list" NEW_LIBS="$file_list libtca_clisp.a libtca_lisp.a" NEW_MODULES="$mod_list" TO_LOAD='loadTca.lisp' ---------------- End of link.sh ------------------------ Create a /usr/local/clisp/tca/loadTca.lisp and put the following line in it: -------------- Start of loadTca.lisp ------------------------ (load "/usr/local/tca/lisp/tca.lisp") ---------------- End of loadTca.lisp ------------------------ > cd tca > rm /usr/local/tca/lisp/*.fas > cd .. > ./clisp-link add-module-set tca base base+tca Starting CLISP/TCA ------------------ To start the tca/clisp use the following command: /usr/local/clisp/base+tca/lisp.run -M /usr/local/clisp/base+tca/lispinit.mem -i /usr/local/tca/lisp/tca.lisp Testing ------- To test it, I run the a1 and b1 modules. These are found in the tca/lisp directory. 1) Start central on . 2) Start a clisp/tca then: (load "/usr/local/tca/lisp/sample.lisp") (load "/usr/local/tca/lisp/b1.lisp") (b1 "") 3) Start a second clisp/tca then: (load "/usr/local/tca/lisp/sample.lisp") (load "/usr/local/tca/lisp/a1.lisp") (a1 "") ________________________________________________________________ Xforms interface for Runconsole. A spiffy new interface has been added to runConsole. It requires the xForms library available for most machines from "http://bragg.phys.uwm.edu/xforms". ________________________________________________________________ Bridge program (tca/tools/bridge) The "bridge" program is used to pass messages back and forth between two centrals. It works by connecting to both centrals. If a module registers a message with central A, then the bridge registers the same message with central B. If a module registers a message handler with central A, the bridge registers a message handler with central B that will forward a message send to central B on to central A, and return the response (if any) to central B. Thus, modules connected to one central can send and receive messages transparently to/from modules connected to another central (at least, that is the goal!) This allows for two subgroups of TCA processes to communicate. The overhead is that the messages must be sent twice (going through the bridge), but the advantage is large if the subgroups don't communicate very often and are separated by great distance, or slow communication links (such as with two robots communicating via wireless ethernet). bridge gets the hostname of one central server from the environment variable CENTRALHOST; the other central server hostname is passed as an command line argument to the bridge program. bridge correctly handles most types of TCA messages (commands, goals, informs, queries, broadcasts). It has not been tested on monitor or multi-query messages. It also works correctly for tcaWaitFor... type calls. You can test the bridge out by: a) in the tca/test directory, do "make a1_bridge" and "make b1_bridge" (these are small changes to the standard a1 and b1 test programs to deal with some of the known bugs described below) b) start a central on some machine A c) start a central on some machine B d) start b1_bridge on either machine A or B e) start either "bridge A" on machine B or "bridge B" on machine A. f) start a1_bridge on the machine that b1_bridge is not on. You should see a flurry of messages passing between a1_bridge and b1_bridge. Known bugs: 1) Sometimes crashes if bridge is started up before other modules get a chance to register their messages; 2) Does not propagate named formatters between centrals. What this means is that if one module uses named formatters, then they must be explicitly registered by some module in the other central. 3) Cannot handle tcaModuleRequires (does not propagate "provides" information between centrals). Please inform us if you discover any previously unknown bugs!