DEVICE COMMUNICATION LIBRARY


INTRODUCTION

The purpose of this document is to help programmers to develop Matrix Devices. In case you want to support other operating systems than Linux, you may either use our jdevcom API or ask us for detailed information about the Matrix Protocol. This Manual provides you with an overview of the device architecture and hands-on guides (called how-to's) on the most important points in a device file.
If you want to know details on E scripting, see the Scripting Manual. Some additional information on using techniques like select may be found in the forthcoming libvoc documentation.
As development distributions we used Slackware and RedHat systems, although the Matrix is supposed to run on any Linux distribution. Details about this may be found in other Manuals.

OVERVIEW

GENERAL OVERVIEW
The Matrix communicates with devices using the devcom API (device communication application programming interface) on the device side. Since the Matrix is one entity (although there may be many of it as discussed later) and the devices are many, it may be thought of as a Server - Client relation. In contrast to this view is the fact, that the Kernel uses services of the devices. So we have no clear Master - Slave relationship. The Kernel may be thought of as a scriptable name server that receives, interprets and distributes messages from devices. Messages sent from a device to the Kernel are called Events, if they are transferred from the Kernel to a device, they are called Actions.

For definitions of the Kernel, the Devices and Plug-ins please see the Matrix Scripting Manual.
It is best to think of the device as an object, that may send and receive actions in an object oriented view.

A change of the environment is sensed by a sensor. This is detected by the (unix) device driver, which either distributes this to the Matrix device (if this is e.g. listening to a socket, where new data is written by the device driver as seen in the console device) or saves the changes, making it possible for the Matrix device to read them (polling). The Matrix device driver reads the new data and forms a Message that is dispatched to the Kernel through devcom facilities. These form an ASCII message that is sent over sockets and received by the Kernel. The Kernel reacts to the incoming Event and may answer with Actions, sent to this or other devices. These follow the above rules the other way round.

POSSIBILITIES

>>> grafik mehrere matrizen - mehrere aliases - mehrere devices

At start-up, the Kernel contacts all hosts that run devices as scripted. On any of these a so-called Portmapper is running on a well-known port (735), that tells the Kernel on which port the actual devices can be reached. To be able to tell this, each device has to be running and known by the Portmapper (registration is part of the devcom). Then the Kernel registers one or many alias(es) for each device with a initialisation string that may be handled by the device driver. The DMX (light system) device for example may be initialized with a channellist supplied, allowing the Kernel to access one or many (disjunct) channels of the same DMX card with the same scripting command. Every command executed on the device "knows", which alias is used to call it, provided by the devcom lib.

There may be more than one Matrix running in the same network, using the same devices. Although this functionality is in early beta stadium, it will be fully functional in future versions. ´No changes need to be made by the device programmer.

MATRIX COMMUNICATION

DEVICES
A device may be any program that incorporates with the devcom api. After it has called the register_device function, any program can be part of the Matrix network. By using the event dispatching functions and registering actions, it may communicate with the Kernel.

THE PORTMAPPER
The Portmapper provides Location transparency to the Matrix. It is the first program that has to be started on a computer that is assigned to supply devices. It must be started with root privileges, since it runs on a port number smaller than 1024. Every device registers itself (by device name and port) at the Portmapper at startup. If a script is executed by the Kernel, it contacts the Portmapper of the scripted host (see the Scripting Manual for details) at the well-known port 735 and retrieves the port number of the requested device. If a machine only runs the Kernel, it does not need to run any Portmapper.

STARTUP PRECEDENCE
As stated above, the Portmapper has to be started before any Matrix device. Devices may executed in any order with any privileges (even by different users). If a device is executed twice, the first is removed from the Portmappers queue and its process is killed, so it can not be contacted by any Matrix script executed afterwards. A device shutdown (either by dying or by manual shutdown) is automatically detected by the Portmapper and also leads to deleting it from its queue.

ALIAS REGISTRATION
If the Kernel starts a script, it registers one or more aliases for every device. These are sent to the devices before any incoming event may be handled. Along with the alias name goes an initialization string. The device may interpret this string and set its state according to it. Then it sends the names and argument specifications of its Events and Actions. These are compared to those mentioned in the script and if any scripted Action/Event is not supplied by the device, the Kernel fails to start up and returns with an error message that specifies line and error code. The Kernel also quits immediately, if it cannot contact any device, or the Portmapper is not running.

EVENTS AND ACTIONS
Events come from devices and go to the Kernel, where they are interpreted. Actions come from the Kernel and are interpreted (executed) by the device. The initialization string may be parsed by the initialization function and result in different Actions/Events for the same device and different aliases.

 

CONVENTIONS AND RESTRICTIONS

NAME AND CODING CONVENTIONS
Since the Matrix only supports 7-bit ASCII chars, there should not be sent any other characters using the devcom. The device name should be human readable as well as all registered Event/Action names, since these are used for scripting, too. There should be no spaces (but you may use underscores if absolutely necessary) in any names. It is good practice to separate procedures that are directly executed by the Kernel (Actions) from library and/or hardware-communication functions. Our recommended order of the different parts is the following:

1. Author/Copyright/... notice
2. Imports and defines
3. Global declarations (structs/variables)
4. Utility functions
5. Library functions
6. Polling/Timed function(s) (if necessary)
7. Action functions
8. Exit functions
9. Init functions
10. Main function

We advice you to use this order so that readability and clarity of device codes is maintained. All this parts of the device code will be discussed later. If no device name is specified at the appropriate function call ("register_device"), the file name is chosen to be the device name, so keep this one readable, too.
You may write as many Actions and Events as you want, but there may be only one initialization and one exit function!

LIMITATIONS
There are some limitations of the devcom API, that result either from platform independence, system restrictions or its pre-beta state.

  • The most important are: The protocol only transports 7-bit ASCII strings correctly, so be sure not to allow any device to transmit data that consists of characters not in the first 128 characters of the ASCII table. Also, escaped double-quotes are not working correctly yet, so use single quotes, where possible.
  • For performance reasons do not transfer empty strings.
  • If registering a function that gets/returns no data, define it as returning void ("v").
  • Always use the normal exit() function. Never use _exit(), atexit() or on_exit() to manipulate the shutdown behavior.
  • It may happen, that the Kernel needs some time to find a device (this comes from the name resolution, we think). Be patient. We are working on it.
  • Buffer overflows may happen in different parts of the system, so do not excessively send messages. We have no performance estimations, but the Kernel queues incoming events and handles them one by one, so it needs its time. Again: be patient (for at least some microticks).
  • Message length (overall) is limited to about 4k since the message queues do not allow bigger messages on the developers systems (linux). This may be changed in future versions (especially if these run on other platforms).
  • If you use the threaded version, you may not use SIGUSR1 and SIGUSR2 since Linux Threads use this signals to communicate. Signal handling is discussed in depth later. If possible, use the single-threaded version, since it is more stable and easier to program (and thorough tested).
  • Message size is limited to 4k for any message passed between the Kernel and their devices.
  • Field size (the size, data may get) is limited to 1k. Strings are automatically truncated to this size and no error message is returned!
  • Look at our Website to get newest information about Limits/Bugs/Fixes and post us your experiences!

ARCHITECTURE OF THE DEVCOM

GENERAL ARCHITECTURE
Generally, a device may be started as a multi-threaded or a single-threaded program. Only the "run" function differs, but this has much impact on the general possibilities of a device.
There are four kinds of functions that may be implemented:

1. Initialization function : it is automatically triggered when a Kernel connects
2. Actions : they are automatically triggered if an Action is received
3. Events : the device may send an Event to the Kernel
4. Exit function : is triggered if an alias deregisters

All these functions need to be registered in order to be executable by the devcom API. This is made through the use of function pointers, a method that strictly limits the syntax of a function head to conform one specified prototype. Events are not implemented as functions. They are just registered to enable the Kernel to record their names for syntax checking at registration time.

As mentioned in the Name and coding Conventions section, a Matrix device consists of the following parts:

1. Author/Copyright/... notice
2. Imports and defines
3. Global declarations (structs/variables)
4. Utility functions
5. Library functions
6. Polling/Timed function(s) (if necessary)
7. Action functions
8. Event functions
9. Init functions
10. Exit functions
11. Main function

We normally use one file per device, but you may separate Library and/or Utility functions in another file as well as declarations in an additional header file. Library functions are device-specific utility functions (like wrappers for ioctl() calls, ...) while normal utility functions are more general.

Ad 1: All devices have to conform to our license concept.
Ad 2: All devices have to import "devcom.h", "sys.h" and "dev.h". This files may be subject to change in future versions, so do not rely on this information. You may import any other libraries in any order. Keep defines readable.
Ad 3: Global declarations go here. Each alias gets a "state" that is stored in a memory portion and linked to the alias list. You have to allocate the memory (using malloc) in the initialization function. The you have to set a pointer to point on the specific data structure. This state is accessible to all triggered functions since it is delivered at the function call.
Ad 4: Implement any utility functions here (list handling, sorting, ...)
Ad 5: Implement any device dependent utility functions here. This may be calls of ioctl() or (as seen in the console) hardware initialization functions.
Ad 6: If you need to implement polling (details later), you will need a timer and a timed function (handler) that goes here.
Ad 7,8: Actions and Events are described above, below and anywhere else in this manual.
Ad 9,10: Since you need to know the names of the Actions and Events for registering them, you are advised to write the init function after these.
Ad 11: The main() function consists of the following part (in this order): device registration/initialization, init function registration, exit function registration (if needed), arbitrary initialization stuff done by you, run function call (either threaded or not).

The program has two ways to exit: Either it may exit using the normal exit() function call (for example in a shutdown action triggered by the Kernel) or it may receive a signal (mostly SIGSEGV) that leads to an immediate exit (but most signals are handled by the devcom, so the shutdown is safe in most cases).
Details about the different parts of a device file can be found in the sample device section of this manual and in the how-to's.

SINGLE-THREADED VERSION
The term single threaded version is wrong, since it is one single process and not one thread. We will call it this way anyway. The motivations for creating two methods of creating matrix devices were the following: First, we created the threaded version, since we thought of threads of offering optimal buffering facilities. If data arrives on a socket, the thread wakes up and handles the data. The actual action is performed by a separate thread, so that time-consuming tasks may be implemented using model without blocking incoming further actions or even the execution of the Kernel. Also, since threads are a bit nifty to program, we decided to implement a second version of the continuously running code that does not use those.
In this model, a select statement is used for waiting for data arriving from the Kernel. This data is handled as soon as it arrives and buffering is handled by the socket implementation of the operating system (buffer size can be adjusted in a linux header file, if necessary, but this needs recompilation of the kernel to be activated). If events can be triggered by data availability at file descriptors (e.g. console/stdin), this should be done using the same select statement (and the libvoc). See the sample device console and the select-how-to for details.

MULTI-THREADED VERSION
There are certain limitations for working with this version and several limitations of the protocol arise from it. First, signal handling is more complex: Every signal received by the process is distributed among all threads. This implicates no problem, since signal handlers are inherited from the parent process/thread. But you should be careful, when working with signals. Second, do not use SIGUSR1 and SIGUSR2. This signals confuse the threads because they are used in the pthread implementation we used for programming the matrix devcom: LinuxThreads. You may use any other thread library (if it complies to POSIX Threads standards), if you want.
Also, it is even more important than in the single-threaded case, that any device exits using the normal exit() function. Never use _exit(), atexit() or on_exit() to manipulate the shutdown behavior. Otherwise, threads may be left running by the system and only the currently executed thread finishes execution (only a real kill (no term) works from this point on).
Experiments showed performance advantages of the threaded version and it is also more resistible to buffer overflows (although none should happen in both versions).

DATA TYPES
Data is transferred in ASCII format in our protocol. The following Data Types are used by the Matrix and our Linux devices:

Data Type C Type Size Format Character
Integer Values long int 4 Bytes d
Floating Point double 8 Bytes f
Strings char[] 500 Bytes max. s
Void - - v

Table 1 :Data Types

Void is used if a function receives no parameters or returns no parameters. Always submit a void, if no other data type is applicable.
Strings are always handled as 7-Bit ASCII conformant. Do not use any special characters from over 127 ASCII.
The field length (for e.g. action and event names) is limited to 100 Bytes.

 

PROGRAMMING THE DEVCOM


ACTION HOW TO
As we said above, actions are triggered by the Kernel. In order to automate this triggering, we use function pointers to manage the actions, a device provides (you may think of them as services e.g. in the Jini system). The format, a function thus has to be is

long int (*func)(char *, alias_p, char *, void *)

where the first char * is where the parameters get, the alias_p is a pointer to the alias structure (see the header file excerpts below for details on data structures), the second char * is a pointer to an allocated portion of memory where the programmer may write return values and the last argument is a miscellaneous data pointer that is registered with the action in the init part and then delivered when the action is triggered. We use the console's write function as example: The call of this function in the Matrix Scripting Language E is demonstrated by the following (fully functional) script:

use con = console@localhost("");

->con:read(^key) 
{
con:write(key);
}

The syntax for the function that is supposed to write a string to the standard ouput is:

long int con_write(char *myparams, alias_p myalias, char *msg, void *data) {
	char *c;

	c = dev_get_string(1,myparams);
	printf(c);
	fflush(stdout);
	free(c);

	return 0;
}

For the registration of the function, please see the initialization how-to below. When called through the devcom, myparams will contain the scripted parameters, myalias will represent the alias, that called this function, msg may be filled (use printf()) with return values and data contains device-specific data.

EVENT HOW-TO
In the above action how-to you see the E definition of an event (read). This event should be triggered when a key is pressed. In order to do so, we use the extended select functionality of the libvoc. The event is also registered in the init function. Other ways of generating events are Timer functions and the use of other run modes.

The functions that actually send the events are:

int single_evt(alias_p myalias, char *ename, char *params, int ack, select_p sel);
int dev_evt(alias_p myalias, char *ename, char *params, int ack);

The alias defines the target of the event. If you provide a NULL pointer, it will be sent to all available aliases. ename is the name of the event as registered and scripted. params contains the arguments sent with the event and ack defines if the device should wait for an acknowledgement or not. Do always set ack to 0 (zero), since the acknowledgment function is not implemented (in the scripting language).
Set params to NULL if you want to transmit a void event (e.g. -> switch:on()).

PARAMETER HANDLING HOW-TO
Parameters are sent in both directions: To and from the device. So, the devcom provides facilities for encoding and decoding parameters (or arguments). To get a parameter from a parameter string you may use the dev_get_* functions that are available for all data types supported:

double dev_get_float(int nr, char *params);
long int dev_get_int(int nr, char *params);
long int *dev_get_ints(int nr, char *params);
char *dev_get_string(int nr, char *params);

The Integer value nr contains the number of the specified data type in the parameter string, so dev_get_int(1,myparams) searches the first integer in myparams and dev_get_string(1,myparams) the first string (as char *!). Also, dev_get_int(2,myparams) returns the second Integer value. Strings acquired through this function need to be freed when they are no longer needed. In order to encode a parameter string you need to provide a format string. If you want to define a void return value, you may provide a NULL pointer as the parameter string for the event. char *dev_create_parastr(char *fmt, ...);

See Table 1 for the format string characters.

Examples:
dev_create_parastr("fdd",3,3.4,6.5);
dev_create_parastr("s","test");
dev_create_parastr("ds",3,stringp);

INITIALIZATION HOW-TO
The initialization function decides, which actions and events are available for a specific alias on the basis of a initialization string provided. This string is in the myparams string and may be retrieved using the dev_get_string(1,myparams) command.
The state of the alias may be a structure defined by the device programmer. As mentioned above, any action receives the state when called. In our example we set the state using the following commands:
console_p state;
state = (console_p) malloc(sizeof(struct console_s));
state->alias = myalias;
state->sel = (select_p)data;

myalias->state = state;

After this, we register actions and events using the following two functions:

int dev_register_action(alias_p myalias, char *name, DEVFUNCP, char *ret, char *fmt, void *data);
int dev_register_event(alias_p myalias, char *name, DEVFUNCP, char *ret, char *fmt, void *data);

#define DEVFUNCP long int (*func)(char *, alias_p, char *, void *)
 

The alias should be the alias of the init function. The name is the name that is used for accessing the action/event in the E script. Again, the fmt string consists of d, f, s and v. The return string ret is used to define the return data. This may either be d, f, s or v. Only one value might be returned (see the Scripting Manual for details of using return values in scripts). DEVFUNCP is the function to be called if an action is registered. In case of an event registration this should be NULL (it is only used again to maintain compatibility in call semantics). Data may be any pointer to anything that is supplied with any succeeding function call of DEVFUNCP.

POLLING HOW-TO
There are two ways of polling devices. You may either use timers and interrupt handlers or use our run_with_me() function (the name was chosen in a rather poetic moment). This function just runs through a user-defined function (you are advised to call it run_once() for clarity) all about 10ms. This is managed by a timed select statement, so timer interrupt calls (SIGALRM) will break it.
The run_with_me strategy is used in the Midi device (devmidi.c) as the following code snippet shows (please look over the fact, that this function is called midi_run() and not run_once()):

int midi_run(void *data) {
/* Cast the data-object to a select-object */
	select_p sel = (select_p) data;

/* 
 * Listen on all file-descriptors registered in the select-objects
*/
	select_select(sel, NULL);
	return 0;
}

int main(int argc, char *argv[]) {
	select_p sel;

/* Create the select-object used by this device */
	sel = select_create();

/* Register the device midi in the matrix */
	dev_register_device("midi", argc, argv);

/* 
 * Register the init-action - this action gets the select-object for
 * further processing - so more aliases can use different midi-ports
 */
	dev_register_init("midi_init", midi_init, "s", "v", sel);

/* 
 * Register the exit-action - this action gets the select-object for
 * further processing- so the specific exited alias could be removed
 * from the select-object
 */
	dev_register_exit("midi_exit", midi_exit, "s", "v", sel);

/*
 * Start the device and listen on all ports registered in the select-
 * object by calling the function "midi_run" with the parameter "sel"
 */
	dev_run_with_me(midi_run, sel);
	exit(0);
}

This device runs in single-threaded mode and never gets the "focus" again, except when the timed interrupt calls the registered interrupt routine light_update(). The duration of one timing slice is determined through the itimer structure (as a global variable, itimer does not conform "Volker's programming standards").

SHUTDOWN HOW-TO
The shutdown facilities are known to be not mighty enough yet, so we are constantly working on them. By now, the device registers its shutdown function as an interrupt service routine for most interrupts that are supposed to stop the program. You are advised to exit the matrix device program with a call of the normal exit() function. Threads will be killed and much memory is freed automatically. Also, some system resources are freed. If an alias is deregistered, its registered exit function is called. This function should free its state structure and alias specific system resources.

 

COMMENTED SAMPLE DEVICE

CONSOLE DEVICE (FULL SOURCE)
The following code is the code for a console device (keyboard). It may do 5 actions and send one event: Actions:

1. Write a string
2. Write an integer
3. Write a string followed by a new-line
4. Wait for an input string and return this
5. Shut down the console

EVENTS.
1. Key pressed

only one alias/matrix!!

INCLUDES AND DEFINES
First the Includes of this specific device appear and then those of all devices (config.h is necessary for autoconf).

#include 
#include 

#include "config.h"
#include "devcom.h"

#define ON 1
#define OFF 0
GLOBAL DECLARATIONS
The struct console_s represents the state of one console alias. For better handling of this data it is re-linked to its originating alias. It also contains a select data type that is linked to the select variable that is used throughout the device. See the init function for details of how the state is handled.
struct console_s {
 	alias_p alias;
 	select_p sel;
} console_t;

typedef struct console_s *console_p;
LIBRARY FUNCTIONS
This is a utilitarian function that handles some terminal functions of the stdin. This is called at the start of the device file (see the main function) and when shutting the device down.
void set_console_attr(int flag)
{
	static struct termios save;
	static struct termios buf;
	static int ok = 0;

	if (!ok) {
		tcgetattr(STDIN_FILENO, &save);
	
		buf = save;
	
		buf.c_lflag &= ~(ECHO | ICANON | IEXTEN);
		buf.c_cc[VMIN] = 0;
		buf.c_cc[VTIME] = 0;
		ok = 1;
	}
	switch (flag) {
		case ON:
			tcsetattr(STDIN_FILENO, TCSANOW, &buf);
			break;
		case OFF:
		default:
			tcsetattr(STDIN_FILENO, TCSANOW, &save);
			break;
	}	
}
ACTION FUNCTIONS
The following Actions all follow the same rules: First retrieve an argument from the parameter string. Then act as expected by the Matrix. On success, return zero (in this example all functions are always successful). The shutdown function appears here, because it is handled as an action. Always exit from a device using exit(). Never use _exit() - especially when working with threads.
The dev_get_*(n,s) functions return the argument number n of the type * in the string s. Always free strings returned by dev_get_string().
long int con_write(char *myparams, alias_p myalias, char *msg, void *data) {
	char *c;
	
	c = dev_get_string(1,myparams);
	printf(c);
	fflush(stdout);
	free(c);

	return 0;
}

long int con_write_i(char *myparams, alias_p myalias, char *msg, void *data) {
	
	printf(" %ld ",dev_get_int(1,myparams));
	fflush(stdout);
	return 0;
}

long int con_writeln(char *myparams, alias_p myalias, char *msg, void *data) {
	char *c;
	
	c = dev_get_string(1,myparams);
	printf(c);
	printf("\n");
	fflush(stdout);
	free(c);

	return 0;
}

long int con_shutdown(char *myparams, alias_p myalias, char *msg, void *data) {

	set_console_attr(OFF);
	printf("shutdown initiated through matrix\n");
	exit(1);
	return 0;
}
In the con_readln function, the way of sending back data can be observed: simply copy the data into the pointer called msg after creating a correct parameter string from it using dev_create_parastr().
long int con_readln(char *myparams, alias_p myalias, char *msg, void *data) {
 	int len;
 	char ret[FIELD_SIZE], *paras;
	
 	paras = dev_get_string(1,myparams);
 
 	printf("\n");
 	printf(paras);
 	fflush(stdout);

 	free(paras);
 	set_console_attr(OFF);
 	len = read(STDIN_FILENO,ret,FIELD_SIZE-1);
 	set_console_attr(ON);
 	ret[len-1] = '\0';			// kill 
 	paras = dev_create_parastr("s",ret);
 	sprintf(msg, paras);
 	free(paras);
 	return 0;
}
EVENT FUNKTIONS
This event is fully handled by the select function of the libvoc. If data arrives at the file descriptor of the standard input, this function is triggered. The state of the corresponding alias is provided by the libvoc too, so the event sending mechanism may use the alias re-linked there.
int con_event(int fd, void *data, void **rdata) {
 
 	static unsigned char recv[2] = {'\0','\0'};
 	char *params;
	
 	if (read(fd, &recv, 1) <= 0) return -1;
 	if ((recv[0] == '\n')||(recv[0] == '\r')) return 0;
	
 	params = dev_create_parastr("s",recv);
	
 	single_evt(((console_p)data)->alias, "read", params, 0, ((console_p)data)->sel);

 	free(params);
 	return 0;
}
INIT FUNCTION
The Initialization function is used for initializing a specific alias. It is registered in the main() function and triggered when the Matrix connects to the device. It registers all functions for its alias and sets the initial state.
In our demo device the state is set to be a console_p (definition above). We also turn the console on (this could be done in the main function either). Since we use a single threaded version, we add a select for the standard input's file descriptor number (STDIN_FILENO) into our select structure, that was delivered through the data pointer. We also supply the function con_event() to the select_addto_rlist() function call, that will handle incoming data. Then we register the actions and the events. If the last argument of the dev_register_action() function call would be the data pointer, we could use the same select structure to dynamically add and remove other file descriptors. See the libvoc description for details about this. At last, we print a message, that includes the device name in the string device. Always use dev_register_device() to set the device name since it initializes all device-specific data.
long int con_init(char *myparams, alias_p myalias, char *msg, void *data) 
{
 	console_p state;
	
 	set_console_attr(ON);
	
 	state = (console_p) malloc(sizeof(struct console_s));
	
 	state->alias = myalias;
 	state->sel = (select_p)data;
 	myalias->state = state;
	
 	select_addto_rlist((select_p) data, STDIN_FILENO, con_event, state);

 	(void)dev_register_action(myalias, "write", con_write, "v", "s", NULL);
 	(void)dev_register_action(myalias, "write_i", con_write_i, "v", "d", NULL);
 	(void)dev_register_action(myalias, "writeln", con_writeln, "v", "s", NULL);
 	(void)dev_register_action(myalias, "shutdown", con_shutdown, "v", "v", NULL);
 	(void)dev_register_action(myalias, "readln", con_readln, "s", "s", NULL);
 	(void)dev_register_event(myalias, "read", NULL, "v", "s", NULL);
	
 	if (verbose) fprintf(stderr, "console %s ready\n", device);
 	return 0;
}
EXIT FUNCTIONS
The exit function is called when the device exits. Since our device only supports one console, it only needs to delete one item of the read list of the select structure. Then it may print a verbose function (in case the device was started with the -v option set) and resets the console. The user-defined exit function is a part of the devcom exit facilities that kill threads, free memory and so on. If you call exit() in it, take an eternity time to wait for the command prompt - it will only call the same exit function again. The same is applicable for dev_exit().
long int con_exit(char *myparams, alias_p myalias, char *msg, void *data)
{
 	select_delfrom_rlist((select_p) data, STDIN_FILENO);
 	if (verbose) printf("console reset!\n");
 	set_console_attr(OFF);
 	return 0;
}

FURTHER NOTES

NAMES
We do not have any strict naming conventions yet, but device programmers should keep some facts in mind:

  • action/event/device names are used for scripting
  • registered functions should be recognized quickly
  • global variables should be unique (Volker says they are generally bad style)
  • structures and pointers should be recognizable by their names
  • one file = one style · upper case letters are ugly (says Martin)
  • matrix devices are open source - keep them human readable

COMMAND LINE OPTIONS
-v.....verbose output
-d....demon mode (not stable yet)

You can get usage information by starting a device with any command line information other that those mentioned above.

The verbose option sets a global integer variable called verbose. This is used as in the console device's init function:

if (verbose) fprintf(stderr, "console %s ready\n", device);

All verbose messages should be printed on stderr since buffering otherwise may hide some of them.

 

FUTURE ENHANCEMENTS

We are constantly developing and refining the Matrix. The Main focus lies on:

  • better and easier scripting
  • bug fixing (merely memory holes and code cleaning)
  • reworking the shutdown facilities of matrix devices
  • more transparent function registration
  • better event support
  • stability
  • developing more device

DEVCOM OVERVIEW

#define DEVFUNCP long int (*func)(char *, alias_p, char *, void *)

/* registration */
int dev_register_device(char *device_name, int argc, char *argv[]);
int dev_register_action(alias_p myalias, char *name, DEVFUNCP, char *ret, char *fmt, void *data);
int dev_register_event(alias_p myalias, char *name, DEVFUNCP, char *ret, char *fmt, void *data);
int dev_register_init(char *name, DEVFUNCP, char *ret, char *fmt, void *data);
int dev_register_exit(char *name, DEVFUNCP, char *ret, char *fmt, void *data);

/* deregistration */
void dev_deregister_action(alias_p myalias, char *name);
void dev_deregister_event(alias_p myalias, char *name);
void dev_deregister_init(char *name);
void dev_deregister_exit(char *name);

/* event generation */
int single_evt(alias_p myalias, char *ename, char *params, int ack, select_p sel);
int dev_evt(alias_p myalias, char *ename, char *params, int ack);

/* running */
int dev_run();
int dev_run_with_me(int (*dev_run_once)(void *), void *);
int dev_run_single(select_p sel);

/* exit */
long int dev_exit(alias_p al);

/* util */
alias_p dev_get_alias(void *criteria, int (*comp)(void *, void *));
double dev_get_float(int nr, char *params);
long int dev_get_int(int nr, char *params);
long int *dev_get_ints(int nr, char *params);
char *dev_get_string(int nr, char *params);
char *dev_create_parastr(char *fmt, ...);
void alive();
void dev_clean_up();