www.pudn.com > d4j.zip > R2CASChannel.java


// local.dialogic.R2CASChannel 
// $Id: R2CASChannel.java,v 1.15 2003/11/13 11:51:47 cgm8 Exp $ 
/*  
 * Copyright (c) 1999 Carlos G Mendioroz. 
 * 
 *  This file is part of D4J. 
 * 
 *  D4J is free software; you can redistribute it and/or 
 *  modify it under the terms of the GNU Lesser General Public 
 *  License as published by the Free Software Foundation; either 
 *  version 2 of the License, or (at your option) any later version. 
 *   
 *  D4J is distributed in the hope that it will be useful, 
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 *  Lesser General Public License for more details. 
 *   
 *  You should have received a copy of the GNU Lesser General Public 
 *  License along with this library; if not, write to the 
 *  Free Software Foundation, Inc., 59 Temple Place - Suite 330, 
 *  Boston, MA  02111-1307, USA. 
 * 
 * Report problems and direct all questions to: 
 * 
 *	tron@acm.org 
 */ 
 
package local.dialogic; 
 
public class R2CASChannel extends Channel implements Runnable { 
    // Constantes 
    private static final int INIT = -1; 
    private static final int RESET = 0; 
    private static final int IDLE = 1; 
    private static final int RSEIZED = 2; 
    private static final int ICALL = 3; 
    private static final int RINGS = 4; 
    private static final int IN = 5; 
    private static final int OFFH = 6; 
    private static final int DIAL = 7; 
    private static final int DIALOK = 8; 
    private static final int OUT = 9; 
    private static final int ERROR = 10; 
    // Handy constants 
    private static final int A = Dialogic.DTB_ABIT; 
    private static final int B = Dialogic.DTB_BBIT; 
    private static final int C = Dialogic.DTB_CBIT; 
    private static final int D = Dialogic.DTB_DBIT; 
    private static final int CA = Dialogic.DTC_ABIT; 
    private static final int CB = Dialogic.DTC_BBIT; 
    private static final int CC = Dialogic.DTC_CBIT; 
    private static final int CD = Dialogic.DTC_DBIT; 
    private static final int WINK = Dialogic.DTMM_WINK; 
    // Protocol constants 
    private static final int R2Idle = 200;      // Lapse in IDLE 
    private static final int R2InterDigit = 15000;// timeout 
    private static final int R2OHDelay = 1000;  // Wait for seizure conf 
    private static final int R2SimSeizureDelay = 200;   // Keep signaled 
    private static final int R2ImpulseGuard = 300;// Do not pay att. to fsig  
    private static final int R2ConnDelayOut = 60; // Delay before connection 
    private static final int R2ConnDelayIn = 75; // Delay before connection 
    // reject reasons 
    public static final int CONGESTION = 0; 
    public static final int BUSY = 1; 
    public static final int BADNUMBER = 2; 
    public static final int NOTINSERVICE = 3; 
 
    // Variables 
    Voice voiceDev = null; 
    DTI dtiDev = null; 
    boolean ra = false, rb = false; 
    boolean xa = false, xb = false; 
    private int linestate = -1; 
    private int pulses = 0; 
    private String ani = null; 
    private int r2tone = 0; 
    private int ringsSent = 0; 
    private boolean ringPhase = true; 
     
    private int inDnisLength = 4; 
    private String dnisPref = ""; 
    private boolean freeAccept = false; 
    private boolean A3B6Accept = false; 
 
    public R2CASChannel(String dtiName, String voiceName) { 
        this (dtiName, voiceName, ""); 
    } 
     
    public R2CASChannel(String dtiName, String voiceName, String dnisPreffix) { 
        super(); 
        dtiDev = new DTI(this, dtiName); 
        voiceDev = new Voice(this, voiceName); 
        // link 
        dtiDev.listen(voiceDev); 
        voiceDev.listen(dtiDev); 
        // 
        linestate = INIT; 
        dnisPref = dnisPreffix; 
        ra = dtiDev.getA(); 
        rb = dtiDev.getB(); 
        dtiDev.setsig(xa = true, xb = true); 
 
        voiceDev.r2_creasig(); 
         
        group = new ThreadGroup(dtiName + "/" + voiceName + " group"); 
 
        serviceThread = new Thread(group, this, dtiName + "/" + voiceName + " service"); 
        serviceThread.start(); 
    } 
 
    public void close() 
    { 
        super.clear(); 
        if (serviceThread != null) { 
            Channel.stopGroup(this); 
            serviceThread.interrupt(); 
            try { 
                serviceThread.join(500); 
            } 
            catch (InterruptedException ie) {}; 
            serviceThread = null; 
        } 
        if (dtiDev != null) { 
            dtiDev.close(); 
            dtiDev = null; 
        } 
        if (voiceDev != null) { 
            voiceDev.close(); 
            voiceDev = null; 
        } 
    } 
 
    public Voice getVoice() { 
        return voiceDev; 
    } 
 
    public Device getNetwork() { 
        return dtiDev; 
    } 
 
    public void run() { 
        int dnisl; 
        StringBuffer dnis = new StringBuffer(dnisPref); 
        EVT evt; 
             
        service: 
        while(true) try { 
            dnis.setLength(dnisPref.length()); 
            dnisl = 0; 
            clear(); 
            beginService(); 
            // Stay in reset... 
            do { 
       	        evt = serviceWaitEvent(R2Idle); 
       	        // Just in case we are resetting from OFFH timeout... 
       	        /* Longer description: We are always xa && !xb here, 
       	         * but for the case that we give up waiting for line, in which 
       	         * case we are !xa && !xb, and should wait forever until rb,  
       	         * then go xa && !xb */ 
       	        if (!xa && !xb && rb) 
           	        dtiDev.setsig(xa = true, xb = false); 
           	 /* evt is null only when timeout, but AOFF events are filtered by service. 
           	  * we stay here until we either see idle for some time or get an event 
           	  */ 
       	    } while (!(xa && !rb) || (evt == null && !(ra && !rb))); 
       	     
       	    if (evt == null) { 
       	        // We are ok for dial out 
       	        linestate = IDLE; 
                setState(FREE);  
                try {  
       	            evt = serviceWaitEvent(); 
       	        } catch (ChannelException ie) { 
                    // Somebody is asking me to leave, outdial ? 
               	    if (linestate != OFFH) 
                        setState(OOS); // This should not happen... 
               	    return; 
               	} 
       	    } 
       	     
       	    // event should be AOFF, and state RSEIZED 
       	    if (linestate != RSEIZED) { 
       	        System.err.println("Ignoring " + evt); 
       	        continue service; 
       	    } 
            voiceDev.r2_fenable(); 
       	    call = new Call(R2CASChannel.this); 
       	    ani = null; 
       	    getDnis: 
       	    while (true) { 
       	        evt = serviceWaitEvent(R2InterDigit); 
       	        if (evt == null) { 
       	            // Timeout in incomming dial 
           	        abortRings("InterDigit timeout"); 
           	        continue service; 
           	    } 
       	        if (evt.type == EVT.TDX_CST  
       	            && evt.cstevt == EVT.DE_TONEON) { 
       	            r2tone = evt.cstdata; 
       	            dnisl++; 
                         
                    switch(r2tone) { 
                    case Voice.SIGI_1: 
       	                dnis.append('1'); 
       	                break; 
                    case Voice.SIGI_2: 
       	                dnis.append('2'); 
       	                break; 
                    case Voice.SIGI_3: 
       	                dnis.append('3'); 
       	                break; 
                    case Voice.SIGI_4: 
       	                dnis.append('4'); 
       	                break; 
                    case Voice.SIGI_5: 
       	                dnis.append('5'); 
       	                break; 
                    case Voice.SIGI_6: 
       	                dnis.append('6'); 
       	                break; 
                    case Voice.SIGI_7: 
       	                dnis.append('7'); 
       	                break; 
                    case Voice.SIGI_8: 
       	                dnis.append('8'); 
       	                break; 
                    case Voice.SIGI_9: 
       	                dnis.append('9'); 
       	                break; 
                    case Voice.SIGI_10: 
       	                dnis.append('0'); 
       	                break; 
       	            case Voice.SIGI_15: 
       	                break getDnis; 
       	            default: 
           	            // Unexpected forward signal 
               	        abortRings("Unexpected forward" + evt); 
               	        continue service; 
                    } 
       	            if (dnisl < inDnisLength) { 
           	            voiceDev.r2_sendb(Voice.SIGA_1, r2tone); 
           	        } else { 
           	            break; 
           	        } 
       	        } else if (evt.type == EVT.TDX_CST && 
       	                    evt.cstevt == EVT.DE_TONEOFF) { 
       	            // Ignore, forward off 
                } else if (evt.type == EVT.TDX_PLAYTONE) { 
       	            // Ignore, backward completed 
                } else { 
                    abortRings("Unexpected " + evt); 
       	            continue service; 
       	        } 
       	    } 
       	    call.dnis = dnis.toString(); 
            linestate = ICALL; 
       	    if (handler != null) { 
       	        endService(); 
       	        handler.handleCall(call); 
       	    } else { 
       	        // Reject with congestion 
       	        voiceDev.r2_sendb(Voice.SIGB_4, r2tone); 
       	        do { 
       	            evt = serviceWaitEvent(); 
       	        } while (evt.type != EVT.TDX_PLAYTONE); 
       	    } 
        }  
        catch (ChannelException ce) {} 
        finally { 
            endService(); 
        } 
    } 
 
    // abort incomming due to protocol error 
    private void abortRings(String error) { 
        EVT evt; 
        System.err.println("Rings: " + error); 
        setState(OOS); 
        voiceDev.stop(); 
        voiceDev.r2_fdisable(); 
        voiceDev.r2_sendbp(Voice.SIGA_4); 
        do { 
       	    evt = serviceWaitEvent(); 
       	} while (!ra && evt.type != EVT.TDX_PLAYTONE); 
        if (call != null) call.drop(); 
    } 
 
    public String toString() { 
        return "R2CASChannel on " + dtiDev + "/" + voiceDev; 
    } 
     
    public void setFree(boolean x) { 
        freeAccept = x; 
    } 
     
    public void setA3B6(boolean x) { 
        A3B6Accept = x; 
    } 
 
    public void setDnisLength(int n) { 
        inDnisLength = n; 
    } 
 
    static TNGEN ringLead = new TNGEN(200, -40, 20) ; 
    static TNGEN ring = new TNGEN(440, -10, 100) ; 
    static TNGEN ringSilence = new TNGEN(200, -40, 200); 
    static TPT stdTPT = new TPT(); 
    static Thread ringer = null; 
    static EVT ringerEvt = null; 
 
    void accept() { 
        if (linestate != ICALL) 
            throw new ChannelException("accept(): wrong state"); 
 
        try { 
        EVT evt = null; 
        boolean acceptOne = false; 
 
        if (freeAccept || A3B6Accept){ 
            voiceDev.r2_sendb(Voice.SIGA_3, r2tone); 
            while (true) { 
                evt = serviceWaitEvent(R2InterDigit); 
                if (evt == null) { 
                    // Timeout in incomming dial 
       	            abortIcall("InterDigit timeout"); 
       	        } 
   	            if (evt.type == EVT.TDX_CST  
   	                && evt.cstevt == EVT.DE_TONEON) { 
   	                r2tone = evt.cstdata; // category 
   	                if (freeAccept) 
                        voiceDev.r2_sendb(Voice.SIGB_7, r2tone); 
                    else 
                        voiceDev.r2_sendb(Voice.SIGB_6, r2tone); 
   	                break; 
                } else if (evt.type == EVT.TDX_CST && 
   	                        evt.cstevt == EVT.DE_TONEOFF) { 
   	                // Ignore, forward off 
                } else if (evt.type == EVT.TDX_PLAYTONE) { 
   	                // Ignore, backward completed 
                } else { 
   	                abortIcall("Unexpected " + evt); 
   	            } 
   	        } 
        } else 
            voiceDev.r2_sendb(Voice.SIGA_6, r2tone); 
        // Accept end: forward cat off & back done 
        while (true) { 
            evt = serviceWaitEvent(); 
            if (evt.type == EVT.TDX_PLAYTONE || 
                (evt.type == EVT.TDX_CST && evt.cstevt == EVT.DE_TONEOFF)) { 
                if (acceptOne) { 
                    try { 
                        Thread.sleep(R2ConnDelayIn); 
                    } catch (InterruptedException ie) { 
                        //nah, catch it elsewhere 
                        Thread.currentThread().interrupt(); 
                    }; 
                    break; 
                } 
                acceptOne = true; 
            } else { 
       	        abortIcall("Unexpected " + evt ); 
       	    } 
        } 
       	voiceDev.r2_fdisable(); 
        ringsSent = 0; 
       	linestate = RINGS; 
       	voiceDev.playtone(ringLead, stdTPT); 
        ringPhase = true; 
        ringerEvt = null; 
		Runnable r = new Runnable() { 
		     public void run() { 
		     		try {  
						ringerEvt = serviceWaitEvent(); 
		     		} catch (ChannelException ce) {} 
					ringer = null; 
					if ((Dialogic.debug & Dialogic.DEBUG_CHANNEL) != 0) 
						System.out.println(R2CASChannel.this.toString() + ": ringer off"); 
			} 
		}; 
        ringer = new Thread(r, this.toString() + " ringer"); 
        ringer.start(); 
        } finally { 
            endService(); 
        } 
    } 
 
    void answer(int rings) { 
        if (linestate == ICALL) 
            accept(); 
        if (linestate != RINGS) 
            throw new ChannelException("answer(): wrong state"); 
         
        // Ring cycle 
        long now = System.currentTimeMillis(); 
        long conn = call.startTime() + rings * 3000; // 3 secs each 
        if (conn > now && ringer != null) 
        	try {  
                ringer.join(conn - now); 
        	} catch (InterruptedException ie) {} 
		if (linestate != RINGS) { 
			linestate = RESET; 
			setState(OOS); 
			throw new ChannelException("answer(): " + ringerEvt); 
		} 
		linestate = IN; 
		if (ringer != null) { 
			Thread r = ringer; // Ringer clears its reference! 
			r.interrupt(); 
			try {  
				r.join(); 
			} catch (InterruptedException ie) {} 
		}  
        voiceDev.stop(); 
        dtiDev.setsig(xa = false, xb = true); 
    } 
 
    void reject(int reason) { 
        if (linestate != ICALL) 
            throw new ChannelException("reject(): wrong state"); 
 
        try { 
        	linestate = IN; // Actually rejecting, but different from ICALL. 
	        setState(OOS); 
	        int bsig = Voice.SIGA_4; 
	        EVT evt; 
	 
	        switch(reason) { 
	        case BUSY: 
	            bsig = Voice.SIGB_3; 
	            break; 
	        case BADNUMBER: 
	            bsig = Voice.SIGB_5; 
	            break; 
	        case NOTINSERVICE: 
	            bsig = Voice.SIGB_8; 
	            break; 
	        case CONGESTION: 
	        default: 
	            bsig = Voice.SIGA_4; 
	        } 
	         
	        if (bsig != Voice.SIGA_4) { 
	            // Switch to B 
	            voiceDev.r2_sendb(Voice.SIGA_3, r2tone); 
	            while (true) { 
	                evt = serviceWaitEvent(R2InterDigit); 
	                if (evt == null) { 
	                    // Timeout in incomming dial 
	       	            abortRings("Reject interDigit timeout"); 
	       	            clear(); 
	       	            return; 
	       	        } 
	   	            if (evt.type == EVT.TDX_CST  
	   	                && evt.cstevt == EVT.DE_TONEON) { 
	   	                r2tone = evt.cstdata; // category 
	   	                break; 
	                } else if (evt.type == EVT.TDX_CST && 
	   	                        evt.cstevt == EVT.DE_TONEOFF) { 
	   	                // Ignore, forward off 
	                } else if (evt.type == EVT.TDX_PLAYTONE) { 
	   	                // Ignore, backward completed 
	                } else { 
	       	            abortRings("Reject unexpected " + evt); 
	       	            clear(); 
	       	            return; 
	   	            } 
	   	        } 
	        }  
	        voiceDev.r2_sendb(bsig, r2tone); 
	         
	        do { 
	            evt = serviceWaitEvent(); 
	        } while (!ra && evt.type != EVT.TDX_PLAYTONE); 
        	clear(); 
        } finally { 
            endService(); 
        } 
    } 
 
    void dial(Call call, String number) { 
        if (linestate != IDLE) 
            throw new ChannelException("dial(): wrong state"); 
        try { 
        linestate = OFFH; 
        if (serviceThread != null) { 
            Channel.stopGroup(this); 
            serviceThread.interrupt(); 
            /* Service thread will normally die,  
             * but simultaneous seizure may prevent that from happening... */ 
            try { 
                serviceThread.join(500); 
            } 
            catch(InterruptedException ie) { 
                Thread.currentThread().interrupt(); 
            } 
            // Give up if it did not quit... 
            if (serviceThread.isAlive()) { 
                System.err.println("Seizure failure: service not quitting"); 
                throw new ChannelException("dial(): seizure failure"); 
            } 
            serviceThread = null; 
        } 
        this.call = call; 
        setState(OUTGOING); 
        EVT evt = null; 
        // Here we go... 
        dtiDev.setsig(xa = false, xb = false); // line seizure 
        evt = serviceWaitEvent(R2OHDelay); 
 
       	// event should be BON, and state DIAL 
       	if (linestate != DIAL) { 
       	    if (evt != null && evt.type == EVT.DTEV_SIG && !ra) { 
       	        // Simultaneous seizure... 
       	        System.err.println("Simultaneous seizure"); 
       	        setState(OOS); 
                try { 
                    Thread.sleep(R2SimSeizureDelay); 
                } catch (InterruptedException ie) { 
                    //nah, catch it elsewhere 
                    Thread.currentThread().interrupt(); 
                }; 
       	    } else if (evt != null) { 
       	        System.err.println("Seizure failure: " + evt); 
       	        setState(OOS); 
       	    } else { 
       	        System.err.println("Seizure failure: timeout"); 
       	        setState(OOS); 
       	    } 
      	    throw new ChannelException("dial(): seizure failure"); 
       	} 
       	// Dial... 
        voiceDev.r2_benable(); 
       	int dnisp = 0; 
       	int r2tone = 0; 
       	boolean phaseB = false; 
       	int anip = 0; 
 
       	sendDigit(number, 0, false); 
       	sendDnis: 
       	while (true) { 
       	    evt = serviceWaitEvent(R2InterDigit); 
       	    if (evt == null) { 
       	        // Timeout  
           	    throw new ChannelException("dial(): dial timeout"); 
           	} 
       	    if (evt.type == EVT.TDX_CST  
       	        && evt.cstevt == EVT.DE_TONEOFF) { 
       	        r2tone = evt.cstdata; 
       	         
       	        // Reset ANI pointer 
                if (r2tone != Voice.SIGA_5) 
                    anip = 0; 
                if (!phaseB) { 
                    switch(r2tone) { 
                    case Voice.SIGA_1: // next 
       	                dnisp++; 
       	                break; 
                    case Voice.SIGA_2: // previous 
       	                dnisp--; 
       	                if (dnisp < 0) dnisp = 0; 
       	                break; 
                    case Voice.SIGA_7: // prev^2 
       	                dnisp-= 2; 
       	                if (dnisp < 0) dnisp = 0; 
       	                break; 
                    case Voice.SIGA_8: // prev^3 
       	                dnisp-= 3; 
       	                if (dnisp < 0) dnisp = 0; 
       	                break; 
                    case Voice.SIGA_9: // last (again) 
       	                break; 
                    case Voice.SIGA_10: // start over 
       	                dnisp = 0; 
       	                break; 
                           	             
                    case Voice.SIGA_3: // Categ/go B 
                        voiceDev.r2_sendf(Voice.SIGII_1); // Abo std 
                        phaseB = true; 
                        continue sendDnis; 
                    case Voice.SIGA_4: // Congestion 
       	                throw new BusyException("dial(): Congestion"); 
                    case Voice.SIGA_5: // Categ/ANI 
                        if (anip == 0)  
                            voiceDev.r2_sendf(Voice.SIGII_1); // Abo std 
                        else 
                            voiceDev.r2_sendf(Voice.SIGII_12); // no ANI 
                        anip++; 
       	                continue sendDnis; 
                    case Voice.SIGA_6: // Accept 
                        linestate = DIALOK; 
       	                break sendDnis; 
       	            default: 
           	            // Unexpected backward signal 
               	        throw new ChannelException("dial(): Unexpected backward" + evt); 
                    } // backward tone off case 
                    sendDigit(number, dnisp, false); 
                } else { 
                    // phase B 
                    switch(r2tone) { 
                    case Voice.SIGA_3: // Congestion 
       	                throw new BusyException("dial(): Busy"); 
                    case Voice.SIGA_4: // Congestion 
       	                throw new BusyException("dial(): B Congestion"); 
                    case Voice.SIGA_5: // Categ/ANI 
                        if (anip == 0)  
                            voiceDev.r2_sendf(Voice.SIGII_1); // Abo std 
                        else 
                            voiceDev.r2_sendf(Voice.SIGII_12); // no ANI 
                        anip++; 
       	                continue sendDnis; 
                    case Voice.SIGA_6: // Accept 
                        linestate = DIALOK; 
       	                break sendDnis; 
                    case Voice.SIGA_7: // Free Accept 
                        linestate = DIALOK; 
                        pulses = -1; // will compensate with pulses++ at connect 
       	                break sendDnis; 
       	            default: 
           	            // Unexpected backward signal 
               	        throw new ChannelException("dial(): Unexpected backward B " + evt); 
                    } 
                } 
       	    } else if (evt.type == EVT.TDX_CST && 
       	                evt.cstevt == EVT.DE_TONEON) { 
       	        // Ignore, backward on 
            } else if (evt.type == EVT.TDX_PLAYTONE) { 
       	        // Ignore, forward completed 
            } else { 
                throw new ChannelException("dial(): Unexpected " + evt); 
       	    } 
       	} // SendDnis 
       	// We are in DIALOK state, let user wait for connect (OUT state) 
        voiceDev.r2_bdisable(); 
        while (serviceWaitEvent(R2ConnDelayOut) != null) {} 
        } finally { 
            endService(); 
        } 
    } 
     
    private void sendDigit(String number, int index, boolean sendI15) { 
       	if (index >= number.length()) { 
       	    // XXX protocol dependent ?  
       	    if (sendI15) 
       	        voiceDev.r2_sendf(Voice.SIGI_15); 
       	    // Stay silent and wait for pulsed answer...         
       	} else { 
       	    switch(number.charAt(index)) { 
       	    case '0': 
       	        voiceDev.r2_sendf(Voice.SIGI_10); 
       	        break; 
       	    case '1': 
       	        voiceDev.r2_sendf(Voice.SIGI_1); 
       	        break; 
       	    case '2': 
       	        voiceDev.r2_sendf(Voice.SIGI_2); 
       	        break; 
       	    case '3': 
       	        voiceDev.r2_sendf(Voice.SIGI_3); 
       	        break; 
       	    case '4': 
       	        voiceDev.r2_sendf(Voice.SIGI_4); 
       	        break; 
       	    case '5': 
       	        voiceDev.r2_sendf(Voice.SIGI_5); 
       	        break; 
       	    case '6': 
       	        voiceDev.r2_sendf(Voice.SIGI_6); 
       	        break; 
       	    case '7': 
       	        voiceDev.r2_sendf(Voice.SIGI_7); 
       	        break; 
       	    case '8': 
       	        voiceDev.r2_sendf(Voice.SIGI_8); 
       	        break; 
       	    case '9': 
       	        voiceDev.r2_sendf(Voice.SIGI_9); 
       	        break; 
       	    default: 
       	        throw new ChannelException("dial(): invalid number ("  
       	            + number + ")"); 
       	    } 
       	} 
    } 
 
    String ani() { 
    	if (ani != null) 
    		return ani; 
        if (linestate != ICALL) { 
        	ani = ""; 
            return ani; 
        } 
         
        EVT evt; 
        StringBuffer anibuf = new StringBuffer(); 
        int anil = 0; 
 
        try { 
        beginService(); 
        voiceDev.r2_sendb(Voice.SIGA_5, r2tone); 
        getANI: 
        while (true) { 
            evt = serviceWaitEvent(R2InterDigit); 
            if (evt == null) { 
                // Timeout in incomming dial 
                abortIcall("Ani timeout"); 
   	        } 
            if (evt.type == EVT.TDX_CST  
                && evt.cstevt == EVT.DE_TONEON) { 
                r2tone = evt.cstdata; 
                anil++; 
                 
                switch(r2tone) { 
                case Voice.SIGI_1: 
                    anibuf.append('1'); 
                    break; 
                case Voice.SIGI_2: 
                    anibuf.append('2'); 
                    break; 
                case Voice.SIGI_3: 
                    anibuf.append('3'); 
                    break; 
                case Voice.SIGI_4: 
                    anibuf.append('4'); 
                    break; 
                case Voice.SIGI_5: 
                    anibuf.append('5'); 
                    break; 
                case Voice.SIGI_6: 
                    anibuf.append('6'); 
                    break; 
                case Voice.SIGI_7: 
                    anibuf.append('7'); 
                    break; 
                case Voice.SIGI_8: 
                    anibuf.append('8'); 
                    break; 
                case Voice.SIGI_9: 
                    anibuf.append('9'); 
                    break; 
                case Voice.SIGI_10: 
                    anibuf.append('0'); 
                    break; 
                case Voice.SIGI_12:  // ANI rq denied 
                case Voice.SIGI_15:  // ANI rq finished 
                    break getANI; 
                default: 
   	                // Unexpected forward signal 
       	            abortIcall("Ani unexpected " + evt); 
                } 
                voiceDev.r2_sendb(Voice.SIGA_5, r2tone); 
            } else if (evt.type == EVT.TDX_CST && 
                        evt.cstevt == EVT.DE_TONEOFF) { 
                // Ignore, forward off 
            } else if (evt.type == EVT.TDX_PLAYTONE) { 
                // Ignore, backward completed 
            } else { 
                abortIcall("Ani unexpected " + evt); 
            } 
        } 
        ani = anibuf.toString();// .substring(1);  // includes category 
        } finally { 
            endService(); 
        } 
        return ani; 
    } 
 
    // abort ICALL (ani/accept) due to protocol error 
    private void abortIcall(String error) { 
        EVT evt; 
        System.err.println("Icall: " + error); 
        setState(OOS); 
        voiceDev.stop(); 
        voiceDev.r2_fdisable(); 
        voiceDev.r2_sendbp(Voice.SIGA_4); 
        ani = ""; // prevent late request of ANI 
        do { 
       	    evt = serviceWaitEvent(); 
       	} while (!ra && evt.type != EVT.TDX_PLAYTONE); 
        if (call != null) call.drop(); 
        clear(); 
        throw new HangUpException(); 
    } 
 
    int pulses() { 
        return pulses; 
    } 
 
    synchronized void clear() { 
        if (linestate == RESET) 
            return; 
        if (ani == null) ani = ""; // Prevent late ANI request 
        super.clear(); 
        int fromState = linestate; 
        linestate = RESET;  // No need for flush to throw an event 
         
        voiceDev.stop(); 
        voiceDev.r2_fdisable(); 
        voiceDev.r2_bdisable(); 
        voiceDev.clear(); 
        dtiDev.listen(voiceDev); 
        voiceDev.listen(dtiDev); 
        flush(); 
        pulses = 0; 
        ani = ""; 
        switch(fromState) { 
        case IDLE: 
        case RESET: 
            break; 
		case RINGS: 
			if (ringer != null) ringer.interrupt(); 
        case RSEIZED: 
        case ICALL: 
        // dropping in advance ? 
            if (ra) 
                // They dropped 
                dtiDev.setsig(xa = true, xb = false); 
            // else  
                // We drop. But lets wait... 
            break; 
        case IN: 
            // dropping... 
            if (ra) 
                // They dropped 
                dtiDev.setsig(xa = true, xb = false); 
            else 
                // We drop. 
                dtiDev.setsig(xa = true, xb); 
            break; 
        case OFFH: 
            // Wait for seizure conf 
            break; 
        case DIAL: 
        case DIALOK: 
        case OUT: 
        case INIT: 
            dtiDev.setsig(xa = true, xb = false); 
            break; 
        } 
        if (serviceThread == null) { 
            serviceThread = new Thread(group, this, dtiDev.name + "/" + voiceDev.name + " service"); 
            serviceThread.start(); 
        } 
    } 
 
    /** 
     * service() 
     * Our line state service fn 
     * Run in channel thread (like a interrupt time handler :-) 
     * Can "eat" the event if it should not get into "user space" 
     * or even change it by returning null or other event 
     */ 
     
    protected EVT service(EVT evt) { 
        if (dtiDev.source(evt)) { 
            switch(evt.type) { 
            case EVT.DTEV_SIG: 
                ra = (evt.data & A)!= 0; 
                rb = (evt.data & B)!= 0; 
                if (linestate == INIT) { 
                    evt = null; 
                    break; 
                } 
                if ((evt.data & CA) != 0) { 
                    if (ra) { 
                        if (setA()) evt = null; 
                    } else { 
                        if (clrA()) evt = null; 
                    } 
                } else if ((evt.data & CB) != 0) { 
                    if (rb) { 
                        if (setB()) evt = null; 
                    } else { 
                        if (clrB()) evt = null; 
                    } 
                } else if ((evt.data & WINK) != 0) { 
                    pulses++; 
                    return null; 
                } 
                break; 
            case EVT.DTEV_E1ERRC: 
                switch(linestate) { 
                case IN: 
                case OUT: 
                    if (call != null) call.drop(); 
                    clear(); 
                    throw new HangUpException(); 
                } 
                break; 
            } 
        } else if (voiceDev.source(evt)) { 
            if (evt.type == EVT.TDX_PLAYTONE) { 
                if (linestate == RINGS) { 
                    if (ringPhase) { 
   	                    voiceDev.playtone(ring, stdTPT); 
   	                    ringPhase = false; 
   	                    ringsSent++; 
   	                } else { 
   	                    voiceDev.playtone(ringSilence, stdTPT); 
   	                    ringPhase = true; 
   	                } 
   	                return null; 
                } 
            } 
        } 
        return evt; 
    } 
     
    /** 
     * setA() 
     * logic to apply to A bit going high.  
     * Returns true if event was serviced, i.e., should not be passed along 
     */ 
    private boolean setA() { 
        switch(linestate) { 
        case RESET: 
            // They drop after us in incoming ? 
            dtiDev.setsig(xa = true, xb = false); 
            break; 
        case IDLE:  // should not happen... 
        case RSEIZED: // premature abort, handled elsewhere 
            setState(OOS); 
            break; 
        case RINGS: // abort 
			if (ringer != null) ringer.interrupt(); 
			// PASSTHROUGH 
        case ICALL: // late abort 
			linestate = IN; // prevent ring cycle loop (if RINGS) 
						    // prevent late ani request (if ICALL) 
			// PASSTHROUGH 
        case IN: 
            // Incoming hang up 
            if (call != null) call.drop(); 
            clear(); 
            throw new HangUpException(); 
        case OUT: 
            // Remote "forced" clearing... 
            if (call != null) call.drop(); 
            clear(); 
            throw new HangUpException(); 
        } 
        return true; 
    } 
     
    /** 
     * clrA() 
     * logic to apply to A bit going low.  
     * Returns true if event was serviced, i.e., should not be passed along 
     */ 
    private boolean clrA() { 
        switch(linestate) { 
        case RESET: 
            if (rb) 
                break; 
            // Fall through if we are in guard time... 
        case IDLE: 
            // answer ? 
            dtiDev.setsig(xa = true, xb = true); 
            linestate = RSEIZED; 
            setState(INCOMING); 
            return false; 
        case OFFH:  
            // Simultaneous seizure... 
            linestate = ERROR; 
        case DIAL: 
            // XXX Answer before R2 finished ??? 
            return false; 
        case DIALOK: 
            // Answer 
            linestate = OUT; 
            pulses++; 
            if (call != null) call.connect(); 
            return false; 
        } 
        return true; 
    } 
     
    /** 
     * setB() 
     * logic to apply to B bit going high.  
     * Returns true if event was serviced, i.e., should not be passed along 
     */ 
    private boolean setB() { 
        switch(linestate) { 
        case IDLE: 
            // Line blocked ? 
            linestate = RESET; 
            setState(OOS); 
            return false; // Let serviceChannel know we are not idle anymore 
        case RESET: 
            // late seizure conf ? 
            setState(OOS);  // just in case 
            dtiDev.setsig(xa = true, xb = false); 
            break; 
        case OFFH: 
            // seizure conf 
            linestate = DIAL; 
            return false; 
        } 
        return true; 
    } 
     
    /** 
     * clrB() 
     * logic to apply to B bit going low.  
     * Returns true if event was serviced, i.e., should not be passed along 
     */ 
    private boolean clrB() { 
        switch (linestate) { 
        case RESET: 
            // WAIT for serviceChannel to set it idle... 9/9/01 
            // if (ra) linestate = IDLE; 
            // setState(FREE); 
            return true; 
        case OFFH: 
        case DIAL: 
        case DIALOK: 
            setState(OOS); 
            if (call != null) call.drop(); 
            clear(); 
            throw new ChannelException("Abort"); 
        } 
        return false; 
    } 
	//{{DECLARE_CONTROLS 
	//}} 
}