1 #include <ctype.h> /* ANSI C */
6 #include "efaxio.h" /* EFAX */
10 #define MAXRESPB 1024 /* maximum bytes of modem responses saved */
12 char *prompts[] = { /* modem responses that are prompts */
13 "OOK", "-CONNECT FAX", "CCONNECT", "NNO CARRIER", "EERROR",
14 "NNO DIALTONE", "BBUSY", "NNO ANSWER", "M+FCERROR", "VVCON",
17 int lockpolldelay = 8000 ; /* milliseconds between checks of lock files */
19 /* signals to be caught so can hang up phone */
20 int catch [] = { CATCHSIGS, 0 } ;
25 int c1=0, c20=0 ; /* use class 1/class 2.0 */
26 int c2=0 ; /* force class 2 */
27 int cmdpause = T_CMD ; /* delay before each init command */
28 int vfc = 0 ; /* virtual flow control */
29 uchar startchar = DC2 ; /* character to start reception */
31 /* response detector lookup tables */
32 uchar rd_nexts [ 256 ] = { 0 }, rd_allowed [ 256 ] = { 0 } ;
34 /* Initialize two lookup tables used by a state machine to detect
35 modem responses embedded in data. The first shows which
36 characters are allowed in each state. The second shows which
37 characters in each state increment the state. Each table is
38 indexed by character. The detector sequences through 6 states
39 corresponding to sequences of the form: <CR><LF>AX...<CR><LF>
40 where A is an upper-case letter and X is an u.c. letter or
41 space. The state values are 01, 02, 04, 08, 10 and 20 (hex)
42 and are used to mask in a bit from the tables. When the state
43 reaches 0x20 a modem response has been detected. With random
44 data there is a small O(10^-10) chance of spurious
51 rd_nexts[CR] = rd_allowed[CR] = 0x01 | 0x08 ;
52 rd_nexts[LF] = rd_allowed[LF] = 0x02 | 0x10 ;
53 for ( c='A' ; c<'Z' ; c++ ) {
54 rd_allowed[c] = 0x04 | 0x08 ;
57 rd_allowed[' '] = 0x08 ;
61 /* Get a modem response into buffer s, storing up to n bytes.
62 The response includes characters from the most recent control
63 character until the first LF following some text. Returns s
64 or null if times-out in t deciseconds or on i/o error. Trace
65 messages are buffered to reduce possible timing problems. */
67 char *tgets( TFILE *f, char *s, int len, int t )
71 for ( i=n=0 ; 1 ; i++ ) {
72 if ( ( c = tgetc ( f, t ) ) == EOF ) break ;
73 if ( i == 0 ) msg ( "M-+ .%03d [", time_ms ( ) ) ;
74 msg ( "M-+ %s", cname ( c ) ) ;
75 if ( n > 0 && c == LF ) break ;
76 if ( ! iscntrl ( c ) && n < len ) s[n++] = c ;
79 if ( n >= len ) msg ( "W- modem response overflow" ) ;
80 s[ n < len ? n : len-1 ] = '\0' ;
82 if ( c == EOF ) msg ( "M- <...%.1f s>]", (float)t/10 ) ;
86 return c == EOF ? 0 : s ;
90 /* Send bytes to the modem, doing bit-reversal and escaping DLEs.
91 Returns 0 or 2 on errors. */
93 int sendbuf ( TFILE *f, uchar *p, int n, int dcecps )
96 uchar *order = f->obitorder ;
97 uchar buf [ MINWRITE+1 ] ;
100 for ( i=0 ; ! err && n > 0 ; n-- ) {
102 if ( c == DLE ) buf[i++] = DLE ;
104 if ( i >= MINWRITE || n == 1 ) {
106 /* ``virtual'' flow control */
108 if ( vfc && dcecps > 0 ) {
109 over = f->bytes - ( proc_ms ( ) - f->mstart ) * dcecps / 1000
111 if ( over > 0 ) msleep ( over * 1000 / dcecps ) ;
114 if ( tput ( f, buf, i ) < 0 )
115 err = msg ( "ES2fax device write error:" ) ;
125 /* Scan responses since giving previous command (by cmd()) for a
126 match to string 's' at start of response. If a matching
127 response is found it finds the start of the data field which
128 is defined as the next non-space character in the current or
129 any subsequent responses. If ip is not null, reads one integer
130 (decimal format) into ip. [problem: Class 2.0 status responses
131 are in hex.] Returns pointer to start of data field of
132 response string or NULL if not found. */
134 char responses [ MAXRESPB ], *lresponse = responses ;
136 char *sresponse ( char *s, int *ip )
141 lens = strlen ( s ) ;
142 for ( p=responses ; p<lresponse ; p += strlen(p) + 1 ) {
144 if ( ! strncmp ( p, s, lens ) ) {
147 lenr = strlen ( r ) ;
148 if ( strspn ( r, " \r\n" ) == lenr && r+lenr < lresponse ) r += lenr ;
150 if ( ip && sscanf ( r, "%d", ip ) > 0 )
151 msg ( "R read value %d from \"%s\"", *ip, r ) ;
160 /* Search for the string s in responses since last command.
161 Skips lines beginning with "AT" (command echo) and removes
162 trailing spaces. Returns pointer to the string or NULL if not
165 char *strinresp ( char *s )
169 for ( p=responses ; p<lresponse && !r ; p += strlen(p) + 1 )
170 if ( strncmp ( p, "AT", 2 ) )
171 r = strstr ( p, s ) ;
177 /* Appends the value of the first response that includes the
178 string s to buffer buf of length len. Skips characters before
179 and including "=" in the response and doesn't copy trailing
182 int getresp ( char *s, char *buf, int len )
187 if ( ( p = strinresp ( s ) ) ) {
188 if ( ( q = strchr ( p, '=' ) ) ) p = q+1 ;
189 for ( q = p+strlen(p)-1 ; q>=p && isspace(*q) ; q-- ) ;
191 if ( n + strlen(buf) < len )
192 strncat ( buf, p, n ) ;
194 strncat ( buf, p, len - strlen(buf) - 1 ) ;
202 /* Search for a match to the string s in a null-terminated array of
203 possible prefix strings pointed to by p. The first character of each
204 prefix string is skipped. Returns pointer to the table entry or NULL if
207 char *strtabmatch ( char **p, char *s )
209 while ( *p && strncmp ( *p+1, s, strlen ( *p+1 ) ) ) p++ ;
210 return ( ! *p || **p == '-' ) ? NULL : *p ;
214 /* Send command to modem and check responses. All modem commands
215 go through this function. Collects pending (unexpected)
216 responses and then pauses for inter-command delay (cmdpause)
217 if t is negative. Writes command s to modem if s is not null.
218 Reads responses and terminates when a response is one of the
219 prompts in prompts[] or if times out in t deciseconds.
220 Repeats command if detects a RING response (probable
221 collision). Returns the first character of the matching prefix
222 string (e.g. 'O' for OK, 'C' for CONNECT, etc.) or EOF if no
223 such response was received within timeout t. */
225 int cmd ( TFILE *f, char *s, int t )
227 char buf [ CMDBUFSIZE ], *p = "" ;
228 int resplen=0, pause=0 ;
235 lresponse = responses ;
241 while ( tgets ( f, buf, CMDBUFSIZE, pause ) )
242 msg ( "W- unexpected response \"%s\"", buf ) ;
244 msg ( "C- command \"%s\"", s ) ;
246 if ( strlen(s) >= CMDBUFSIZE-4 ) {
247 msg ( "E modem command \"%s\" too long", s ) ;
249 sprintf ( buf, "AT%s\r", s ) ;
250 tput ( f, buf, strlen(buf) ) ;
256 msg ( "C- waiting %.1f s", ((float) t)/10 ) ;
258 while ( ( p = tgets ( f, buf, CMDBUFSIZE, t ) ) ) {
260 if ( ( resplen += strlen ( buf ) + 1 ) <= MAXRESPB ) {
261 strcpy ( lresponse, buf ) ;
262 lresponse += strlen ( buf ) + 1 ;
265 if ( ( p = strtabmatch ( (char**) prompts, buf ) ) ) {
266 msg ( "C- response \"%s\"", buf ) ;
270 if ( ! strcmp ( buf, "RING" ) ) { msleep ( 100 ) ; goto retry ; }
274 return p ? *p : EOF ;
278 /* Send command to modem and wait for reply after testing (and
279 possibly setting) current error status via err
280 pointer. Returns 0 if err is already set, command response, or
283 int ckcmd ( TFILE *f, int *err, char *s, int t, int r )
286 if ( ( ! err || ! *err ) && ( c = cmd ( f, s, t ) ) != r && r ) {
287 msg ( err ? "E %s %s %s" : "W %s %s %s",
288 c == EOF ? "timed out" : "wrong response",
289 s ? "after command: " : "after waiting",
291 if ( err ) *err = 3 ;
297 /* Resynchronize modem from an unknown state. If no immediate
298 response, try pulsing DTR low (needs &D{2,3,4}), and then
299 cancelling data or fax data modes. In each case, discards any
300 responses for about 2 seconds and then tries command ATQ0V1 to
301 enable text responses. Returns 0 if OK or 4 if no response.
304 int modemsync ( TFILE *f )
306 int err=0, method=0 ;
308 for ( method=0 ; ! err ; method++ ) {
314 ttymode ( f, VOICECOMMAND ) ;
316 msg ("Isync: dropping DTR") ;
317 ttymode ( f, COMMAND ) ; msleep ( 200 ) ;
318 ttymode ( f, DROPDTR ) ; msleep ( 200 ) ;
319 ttymode ( f, COMMAND ) ;
322 msg ("Isync: sending escapes") ;
323 ttymode ( f, VOICECOMMAND ) ;
324 tput ( f, CAN_STR, 1 ) ;
325 tput ( f, DLE_ETX, 2 ) ;
327 ttymode ( f, COMMAND ) ;
328 tput ( f, CAN_STR, 1 ) ;
329 tput ( f, DLE_ETX, 2 ) ;
331 tput ( f, "+++", 3 ) ;
334 err = msg ("E4sync: modem not responding") ;
337 while ( method && cmd ( f, 0, 20 ) != EOF ) ;
338 if ( cmd ( f, "Q0V1", -20 ) == OK ) break ;
344 /* Set up modem by sending initialization/reset commands.
345 Accepts either OK or CONNECT responses. Optionally changes
346 baud rate if a command begins with a number. Returns 0 if OK,
349 int setup ( TFILE *f, char **cmds, int ignerr )
354 for ( ; ! err && *cmds ; cmds++ ) {
356 if ( *cmds && isdigit( **cmds ) ) {
360 if ( ( c = cmd ( f, *cmds, -TO_RESET ) ) != OK && c != VCONNECT &&
362 err = msg ( "E3modem command (%s) failed", *cmds ? *cmds : "none" ) ;
370 /* Terminate session. Makes sure modem is responding, sends
371 modem reset commands or hang-up command if none, removes lock
372 files. Returns 0 if OK, 3 on error.*/
374 int end_session ( TFILE *f, char **zcmd, char **lkfile, int sync )
379 err = modemsync ( f ) ;
380 if ( f && zcmd && ! err && sync )
381 err = setup ( f, zcmd, 0 ) ;
383 ttymode ( f, ORIGINAL ) ;
385 unlockall ( lkfile ) ;
390 /* Initialize session. Try locking and opening fax device until opened or
391 get error. Then set tty modes, register signal handler, set up
392 modem. Returns 0 if OK, 2 on errors, 3 if initialization failed, 4 if no
395 int begin_session ( TFILE *f, char *fname, int reverse, int hwfc,
396 char **lkfile, ttymodes mode, void (*onsig) (int) )
398 int i, err=0, busy=0, minbusy=0 ;
401 err = lockall ( lkfile, busy >= minbusy ) ;
402 if ( ! err ) err = ttyopen ( f, fname, reverse, hwfc ) ;
404 if ( busy++ >= minbusy ) {
405 msg ( "W %s locked or busy. waiting.", fname ) ;
406 minbusy = minbusy ? minbusy*2 : 1 ;
408 msleep ( lockpolldelay ) ;
410 } while ( err == 1 ) ;
412 if ( ! err ) msg ( "Iopened %s", fname ) ;
414 if ( ! err ) err = ttymode ( f, mode ) ;
418 f->rd_state = RD_BEGIN ;
421 for ( i=0 ; ! err && catch [ i ] ; i++ )
422 if ( signal ( catch [ i ], onsig ) == SIG_ERR )
423 err = msg ( "ES2can't set signal %d handler:", catch [ i ] ) ;
425 if ( !err ) err = modemsync ( f ) ;