modem_script()

Updated: April 19, 2023

Run a script on a device

Synopsis:

#include <sys/modem.h>

int modem_script( int fd,
                  struct modem_script* table,
                  speed_t* baud,
                  void (*io)( 
                        char* progress,
                        char* in,
                        char* out ),
                  int (*cancel)(void) );

Arguments:

fd
The file descriptor for the device that you want to read from; see modem_open().
table
An array of modem_script structures that comprise a script of commands that you want to run on the device; see below.
baud
A pointer to a speed_t where the function can store the baud rate (if your script says to do so).
io
A function that's called to process each string that's emitted or received.
cancel
NULL, or a callback function that's called whenever the newquiet time period (specified in the script) expires while waiting for input.

Library:

libc

Use the -l c option to qcc to link against this library. This library is usually included automatically.

Description:

The modem_script() function runs the script table on the device associated with the file descriptor fd. The script implements a simple state machine that emits strings and waits for responses.

Each string that's emitted or received is passed to the function io() as follows:

Call Description
(*io)(str, 0, 0) Emitted progress string
(*io)(0, str, 0) Received string
(*io)(0, 0, str) Emitted response string

This lets an application set up a callback that can display the script's interaction in a status window.

If you provide a cancel function, it's called once each newquiet 1/10 of a second while waiting for input. If this function returns a nonzero value, the read returns immediately with -1 and errno is set to ETIMEDOUT. You can use the cancel function as a callback in a graphical dialer that needs to support a cancel button to stop a script.

The table is an array of modem_script structures that contain the following members:

char curstate
The current state. Execution always begins at state 1, which must be the first array element of table. Multiple elements may have the same current state, in which case any received input is matched against each pattern member for that state.
int curflags
The flags to use on a pattern match of a response:
  • MODEM_NOECHO — don't echo the response through the io() callback.
  • MODEM_BAUD — extract any number in the response and assign it to baud.
char newstate
When a pattern match occurs with pattern, this is the next state. A state transition causes response to be output and newflags, newtimeout, and newquiet to be saved and associated with the new state. Changing to a new state of 0 causes modem_script() to return with the value in retvalue.
int newflags
Saved on a state transition and passed to modem_read() when waiting for a response in the new state. For information about these flags, see modem_read().
int newtimeout
Saved on a state transition and passed to modem_read() when waiting for a response in the new state. This timeout is described in modem_read().
int newquiet
Saved on a state transition and passed to modem_read() when waiting for a response in the new state. This quiet timeout is described in modem_read().
short retvalue
The return value when the script terminates with a pattern match, and the new state is 0.
char* pattern
A pattern to match against received characters. The pattern is matched using fnmatch(). Only patterns in the current state or the wildcard state of 0 are matched. On a match, the current state changes to newstate.
char* response
On a pattern match, this response is output to the device. If the curflags don't have MODEM_NOECHO set, the response is given to the callback function passed as the io parameter.
char* progress
On a pattern match, this progress string is passed to the callback function passed as the io parameter.

Here's an example that demonstrates the operation of the script:

/*
 curstate curflags newstate newflags newtimeout newquiet retvalue pattern response
*/

struct modem_script table[] ={
  {1, 0,            1, 0,              2,  5, 0, NULL,              "ATZ\\r\\P0a"},
  {1, 0,            2, 0,              30, 5, 0, "*ok*",            "ATDT5910934"},
  {2, MODEM_BAUD,   3, MODEM_LASTLINE, 10, 5, 0, "*connect*",       NULL},
  {3, 0,            4, 0,              8,  5, 0, "*login:*",        "guest"},
  {4, MODEM_NOECHO, 5, 0,              15, 5, 0, "*password:*",     "xxxx"},
  {5, 0,            0, 0,              0,  0, 0, "*$ *",            NULL},
  {0, 0,            0, 0,              0,  0, 1, "*no carrier*",    NULL},
  {0, 0,            0, 0,              0,  0, 2, "*no answer*",     NULL},
  {0, 0,            0, 0,              0,  0, 3, "*no dialtone*",   NULL},
  {0, 0,            0, 0,              0,  0, 4, "*busy*",          NULL},
  { NULL }
};

When this script is passed to modem_script(), the current state is set to 1, and the output is ATZ (the response in the first array element).

While in any state, modem_script() waits for input, matching it against the current state or the wildcard state of 0.

State 1

Input Action
*ok* Go to state 2 and emit ATDT1-591-0934. The flags to be used in the new state are set to 0, the quiet time in the new state is set to 5/10 of a second, and the timeout time in the new state is set to 30 seconds.
*no carrier* Go to state 0 (the termination newstate), return with the contents of retvalue (1).
*no answer* Go to state 0 (the termination newstate), return with the contents of retvalue (2).
*no dialtone* Go to state 0 (the termination newstate), return with the contents of retvalue (3).
*busy* Go to state 0 (the termination newstate), return with the contents of retvalue (4).

State 2

Input Action
*connect* Go to state 3 and don't emit anything to the device. The flags to be used in the new state are set to MODEM_LASTLINE, the quiet time in the new state is set to 5/10 of a second, and the timeout time in the new state is set to 10 seconds. Since the current flags are MODEM_BAUD, the baud rate is extracted from the connect message.
*no carrier* Same as previous table
*no answer* Same as previous table
*no dialtone* Same as previous table
*busy* Same as previous table

State 3

Input Action
*login* Go to state 4 and emit guest. The flags to be used in the new state are set to 0, the quiet time in the new state is set to 5/10 of a second, and the timeout time in the new state is set to 8 seconds.
*no carrier* Same as previous table
*no answer* Same as previous table
*no dialtone* Same as previous table
*busy* Same as previous table

State 4

Input Action
*password* Go to state 5 and emit xxxx. The flags to be used in the new state are set to 0, the quiet time in the new state is set to 5/10 of a second, and the timeout time in the new state is set to 15 seconds. Since the current flags are MODEM_NOECHO, the password response xxxx isn't sent to the io() callback.
*no carrier* Same as previous table
*no answer* Same as previous table
*no dialtone* Same as previous table
*busy* Same as previous table

State 5

Input Action
*$ * Go to state 0 (the termination newstate), return with the contents of retvalue (0).
*no carrier* Same as previous table
*no answer* Same as previous table
*no dialtone* Same as previous table
*busy* Same as previous table

If you set the flag MODEM_BAUD for a state, then any number embedded in a matching response is extracted and assigned as a number to the baud parameter.

If you don't set the flag MODEM_NOECHO for a state, then all emitted strings are also given to the passed io function as (*io)(0, 0, response).

Returns:

The retvalue member of a script entry that terminates the script. This will always be a positive number. If modem_script fails, it returns -1 and sets errno.

Errors:

EAGAIN
The O_NONBLOCK flag is set for the file descriptor, and the process would be delayed in the write operation.
EBADF
The file descriptor, filedes, isn't a valid file descriptor open for writing.
EINTR
The write operation was interrupted by a signal, and either no data was transferred, or the resource manager responsible for that file doesn't report partial transfers.
EIO
A physical I/O error occurred. The precise meaning depends on the device.
EPIPE
An attempt was made to write to a pipe (or FIFO) that isn't open for reading by any process. A SIGPIPE signal is also sent to the process.

Classification:

QNX Neutrino

Safety:  
Cancellation point Yes
Interrupt handler No
Signal handler Read the Caveats
Thread Read the Caveats

Caveats:

Depending on what you do in your cancel function, it might or might not be safe to call modem_script() from a signal handler or a multithreaded program.