3 #include <ctype.h> /* ANSI C */
8 #include "efaxio.h" /* EFAX */
12 #define MAXRESPB 1024 /* maximum bytes of modem responses saved */
14 static char *prompts[] = { /* modem responses that are prompts */
15 "OOK", "-CONNECT FAX", "CCONNECT", "NNO CARRIER", "EERROR",
16 "NNO DIALTONE", "BBUSY", "NNO ANSWER", "M+FCERROR", "VVCON",
19 int lockpolldelay = 8000 ; /* milliseconds between checks of lock files */
21 /* signals to be caught so can hang up phone */
22 static int catch [] = { CATCHSIGS, 0 } ;
28 int c1=0 ; /* use class 1 */
30 int c20=0 ; /* use class 2.0 */
31 int c2=0 ; /* force class 2 */
32 int cmdpause = T_CMD ; /* delay before each init command */
33 int vfc = 0 ; /* virtual flow control */
34 uchar startchar = DC2 ; /* character to start reception */
36 /* response detector lookup tables */
40 uchar rd_nexts [ 256 ] = { 0 }, rd_allowed [ 256 ] = { 0 } ;
42 /* Initialize two lookup tables used by a state machine to detect
43 modem responses embedded in data. The first shows which
44 characters are allowed in each state. The second shows which
45 characters in each state increment the state. Each table is
46 indexed by character. The detector sequences through 6 states
47 corresponding to sequences of the form: <CR><LF>AX...<CR><LF>
48 where A is an upper-case letter and X is an u.c. letter or
49 space. The state values are 01, 02, 04, 08, 10 and 20 (hex)
50 and are used to mask in a bit from the tables. When the state
51 reaches 0x20 a modem response has been detected. With random
52 data there is a small O(10^-10) chance of spurious
55 static void rd_init ( void )
59 rd_nexts[CR] = rd_allowed[CR] = 0x01 | 0x08 ;
60 rd_nexts[LF] = rd_allowed[LF] = 0x02 | 0x10 ;
61 for ( c='A' ; c<'Z' ; c++ ) {
62 rd_allowed[c] = 0x04 | 0x08 ;
65 rd_allowed[' '] = 0x08 ;
69 /* Get a modem response into buffer s, storing up to n bytes.
70 The response includes characters from the most recent control
71 character until the first LF following some text. Returns s
72 or null if times-out in t deciseconds or on i/o error. Trace
73 messages are buffered to reduce possible timing problems. */
75 static char *tgets( TFILE *f, char *s, int len, int t )
79 for ( i=n=0 ; 1 ; i++ ) {
80 if ( ( c = tgetc ( f, t ) ) == EOF ) break ;
81 if ( i == 0 ) msg ( "M-+ .%03d [", time_ms ( ) ) ;
82 msg ( "M-+ %s", cname ( c ) ) ;
83 if ( n > 0 && c == LF ) break ;
84 if ( ! iscntrl ( c ) && n < len ) s[n++] = c ;
87 if ( n >= len ) msg ( "W- modem response overflow" ) ;
88 s[ n < len ? n : len-1 ] = '\0' ;
90 if ( c == EOF ) msg ( "M- <...%.1f s>]", (float)t/10 ) ;
94 return c == EOF ? 0 : s ;
98 /* Send bytes to the modem, doing bit-reversal and escaping DLEs.
99 Returns 0 or 2 on errors. */
101 int sendbuf ( TFILE *f, uchar *p, int n, int dcecps )
104 uchar *order = f->obitorder ;
105 uchar buf [ MINWRITE+1 ] ;
108 for ( i=0 ; ! err && n > 0 ; n-- ) {
110 if ( c == DLE ) buf[i++] = DLE ;
112 if ( i >= MINWRITE || n == 1 ) {
114 /* ``virtual'' flow control */
116 if ( vfc && dcecps > 0 ) {
117 over = f->bytes - ( proc_ms ( ) - f->mstart ) * dcecps / 1000
119 if ( over > 0 ) msleep ( over * 1000 / dcecps ) ;
122 if ( tput ( f, buf, i ) < 0 )
123 err = msg ( "ES2fax device write error:" ) ;
133 /* Scan responses since giving previous command (by cmd()) for a
134 match to string 's' at start of response. If a matching
135 response is found it finds the start of the data field which
136 is defined as the next non-space character in the current or
137 any subsequent responses. If ip is not null, reads one integer
138 (decimal format) into ip. [problem: Class 2.0 status responses
139 are in hex.] Returns pointer to start of data field of
140 response string or NULL if not found. */
142 static char responses [ MAXRESPB ], *lresponse = responses ;
144 char *sresponse ( char *s, int *ip )
149 lens = strlen ( s ) ;
150 for ( p=responses ; p<lresponse ; p += strlen(p) + 1 ) {
152 if ( ! strncmp ( p, s, lens ) ) {
155 lenr = strlen ( r ) ;
156 if ( strspn ( r, " \r\n" ) == lenr && r+lenr < lresponse ) r += lenr ;
160 msg ( "R read value %d from \"%s\"", *ip, r ) ;
170 /* Search for the string s in responses since last command.
171 Skips lines beginning with "AT" (command echo) and removes
172 trailing spaces. Returns pointer to the string or NULL if not
175 char *strinresp ( char *s )
179 for ( p=responses ; p<lresponse && !r ; p += strlen(p) + 1 )
180 if ( strncmp ( p, "AT", 2 ) )
181 r = strstr ( p, s ) ;
187 /* Appends the value of the first response that includes the
188 string s to buffer buf of length len. Skips characters before
189 and including "=" in the response and doesn't copy trailing
192 int getresp ( char *s, char *buf, int len )
197 if ( ( p = strinresp ( s ) ) ) {
198 if ( ( q = strchr ( p, '=' ) ) ) p = q+1 ;
199 for ( q = p+strlen(p)-1 ; q>=p && isspace(*q) ; q-- ) ;
201 if ( n + strlen(buf) < len )
202 strncat ( buf, p, n ) ;
204 strncat ( buf, p, len - strlen(buf) - 1 ) ;
212 /* Search for a match to the string s in a null-terminated array of
213 possible prefix strings pointed to by p. The first character of each
214 prefix string is skipped. Returns pointer to the table entry or NULL if
217 static char *strtabmatch ( char **p, char *s )
219 while ( *p && strncmp ( *p+1, s, strlen ( *p+1 ) ) ) p++ ;
220 return ( ! *p || **p == '-' ) ? NULL : *p ;
224 /* Send command to modem and check responses. All modem commands
225 go through this function. Collects pending (unexpected)
226 responses and then pauses for inter-command delay (cmdpause)
227 if t is negative. Writes command s to modem if s is not null.
228 Reads responses and terminates when a response is one of the
229 prompts in prompts[] or if times out in t deciseconds.
230 Repeats command if detects a RING response (probable
231 collision). Returns the first character of the matching prefix
232 string (e.g. 'O' for OK, 'C' for CONNECT, etc.) or EOF if no
233 such response was received within timeout t. */
235 int cmd ( TFILE *f, char *s, int t )
237 char buf [ CMDBUFSIZE ], *p = "" ;
238 int resplen=0, pause=0 ;
245 lresponse = responses ;
251 while ( tgets ( f, buf, CMDBUFSIZE, pause ) )
252 msg ( "W- unexpected response \"%s\"", buf ) ;
254 msg ( "C- command \"%s\"", s ) ;
256 if ( strlen(s) >= CMDBUFSIZE-4 ) {
257 msg ( "E modem command \"%s\" too long", s ) ;
259 sprintf ( buf, "AT%s\r", s ) ;
260 tput ( f, buf, strlen(buf) ) ;
266 msg ( "C- waiting %.1f s", ((float) t)/10 ) ;
268 while ( ( p = tgets ( f, buf, CMDBUFSIZE, t ) ) ) {
270 if ( ( resplen += strlen ( buf ) + 1 ) <= MAXRESPB ) {
271 strcpy ( lresponse, buf ) ;
272 lresponse += strlen ( buf ) + 1 ;
275 if ( ( p = strtabmatch ( (char**) prompts, buf ) ) ) {
276 msg ( "C- response \"%s\"", buf ) ;
280 if ( ! strcmp ( buf, "RING" ) ) { msleep ( 100 ) ; goto retry ; }
284 return p ? *p : EOF ;
288 /* Send command to modem and wait for reply after testing (and
289 possibly setting) current error status via err
290 pointer. Returns 0 if err is already set, command response, or
293 int ckcmd ( TFILE *f, int *err, char *s, int t, int r )
296 if ( ( ! err || ! *err ) && ( c = cmd ( f, s, t ) ) != r && r ) {
297 msg ( err ? "E %s %s %s" : "W %s %s %s",
298 c == EOF ? "timed out" : "wrong response",
299 s ? "after command: " : "after waiting",
301 if ( err ) *err = 3 ;
307 /* Resynchronize modem from an unknown state. If no immediate
308 response, try pulsing DTR low (needs &D{2,3,4}), and then
309 cancelling data or fax data modes. In each case, discards any
310 responses for about 2 seconds and then tries command ATQ0V1 to
311 enable text responses. Returns 0 if OK or 4 if no response.
314 static int modemsync ( TFILE *f )
316 int err=0, method=0 ;
318 for ( method=0 ; ! err ; method++ ) {
324 ttymode ( f, VOICECOMMAND ) ;
326 msg ("Isync: dropping DTR") ;
327 ttymode ( f, COMMAND ) ; msleep ( 200 ) ;
328 ttymode ( f, DROPDTR ) ; msleep ( 200 ) ;
329 ttymode ( f, COMMAND ) ;
332 msg ("Isync: sending escapes") ;
333 ttymode ( f, VOICECOMMAND ) ;
334 tput ( f, CAN_STR, 1 ) ;
335 tput ( f, DLE_ETX, 2 ) ;
337 ttymode ( f, COMMAND ) ;
338 tput ( f, CAN_STR, 1 ) ;
339 tput ( f, DLE_ETX, 2 ) ;
341 tput ( f, "+++", 3 ) ;
344 err = msg ("E4sync: modem not responding") ;
347 while ( method && cmd ( f, 0, 20 ) != EOF ) ;
348 if ( cmd ( f, "Q0V1", -20 ) == OK ) break ;
354 /* Set up modem by sending initialization/reset commands.
355 Accepts either OK or CONNECT responses. Optionally changes
356 baud rate if a command begins with a number. Returns 0 if OK,
359 int setup ( TFILE *f, char **cmds, int ignerr )
364 for ( ; ! err && *cmds ; cmds++ ) {
366 if ( *cmds && isdigit( **cmds ) ) {
370 if ( ( c = cmd ( f, *cmds, -TO_RESET ) ) != OK && c != VCONNECT &&
372 err = msg ( "E3modem command (%s) failed", *cmds ? *cmds : "none" ) ;
380 /* Terminate session. Makes sure modem is responding, sends
381 modem reset commands or hang-up command if none, removes lock
382 files. Returns 0 if OK, 3 on error.*/
384 int end_session ( TFILE *f, char **zcmd, char **lkfile, int sync )
389 err = modemsync ( f ) ;
390 if ( f && zcmd && ! err && sync )
391 err = setup ( f, zcmd, 0 ) ;
393 ttymode ( f, ORIGINAL ) ;
395 unlockall ( lkfile ) ;
400 /* Initialize session. Try locking and opening fax device until opened or
401 get error. Then set tty modes, register signal handler, set up
402 modem. Returns 0 if OK, 2 on errors, 3 if initialization failed, 4 if no
405 int begin_session ( TFILE *f, char *fname, int reverse, int hwfc,
406 char **lkfile, ttymodes mode, void (*onsig) (int) )
408 int i, err=0, busy=0, minbusy=0 ;
411 err = lockall ( lkfile, busy >= minbusy ) ;
412 if ( ! err ) err = ttyopen ( f, fname, reverse, hwfc ) ;
414 if ( busy++ >= minbusy ) {
415 msg ( "W %s locked or busy. waiting.", fname ) ;
416 minbusy = minbusy ? minbusy*2 : 1 ;
418 msleep ( lockpolldelay ) ;
420 } while ( err == 1 ) ;
422 if ( ! err ) msg ( "Iopened %s", fname ) ;
424 if ( ! err ) err = ttymode ( f, mode ) ;
428 f->rd_state = RD_BEGIN ;
431 for ( i=0 ; ! err && catch [ i ] ; i++ )
432 if ( signal ( catch [ i ], onsig ) == SIG_ERR )
433 err = msg ( "ES2can't set signal %d handler:", catch [ i ] ) ;
435 if ( !err ) err = modemsync ( f ) ;