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


// local.dialogic.AnalogChannel 
// $Id: AnalogChannel.java,v 1.14 2003/09/09 11:42:02 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; 
 
/** 
 * Representation for an analog channel. 
 */ 
public class AnalogChannel extends Channel implements Runnable { 
    // Constantes 
    private static final int RESET = 0; 
    private static final int IDLE = 1; 
    private static final int ICALL = 2; 
    private static final int RINGS = 3; 
    private static final int IN = 4; 
    private static final int OFFH = 5; 
    private static final int DIAL = 6; 
    private static final int OUT = 7; 
 
    // Variables 
    Voice voiceDev = null; 
    AGLine agLine = null; 
    private int linestate = RESET; 
    private String mydev; 
    private String dnis = ""; 
    private int dialtone1 = 0; 
    private int dialtone2 = 0; 
    private DXCAP cap; 
    private boolean blindDial = false; // D41ESC problematic GTD... 
    private boolean perfectCall = false; 
    private boolean perfectCallInit = false; // PFA initialized 
    private int minRings = 1; 
    private boolean callerIdEnabled = false; 
     
    private static int dtf11 = 0, dtf12 = 0, dtd1 = 0; 
    private static int dtf21 = 0, dtf22 = 0, dtd2 = 0; 
     
    /** 
     *  Clear the dialtone definitions used by AnalogChannel. 
     *  

* This method should be used before creating the channels. */ public static void clearDialtones() { dtf11 = 0; dtf12 = 0; dtd1 = 0; dtf21 = 0; dtf22 = 0; dtd2 = 0; } /** * Add a single-frequency dialtone definition to the channel. * @param freq The dialtone frequency, in Hertz (must be positive nonzero). * @param dev Allowable deviation in the frequency, in Hertz. */ public static void addStDialtone(int freq, int dev) { if (dtf21 != 0) throw new RuntimeException("Too many dialtones!"); if (freq <= 0) throw new RuntimeException("Bad parameter value"); if (dtf11 == 0) { dtf11 = freq; dtd1 = dev; } else { dtf21 = freq; dtd2 = dev; } } /** * Add a dual-frequency dialtone definition to the channel. * @param freq1 The first dialtone frequency, in Hertz (must be positive nonzero). * @param freq2 The second dialtone frequency, in Hertz (must be positive nonzero). * @param dev Allowable deviation in the frequency, in Hertz. */ public static void addDtDialtone(int freq1, int freq2, int dev) { if (dtf21 != 0) throw new RuntimeException("Too many dialtones!"); if (freq1 <= 0 || freq2 <= 0) throw new RuntimeException("Bad parameter value"); if (dtf11 == 0) { dtf11 = freq1; dtf12 = freq2; dtd1 = dev; } else { dtf21 = freq1; dtf22 = freq2; dtd2 = dev; } } /** * Create an analog channel, specifing a DNIS group. * @param voiceName Device name for the channel. * @param dnis The dialed number or line number associated with the channel. */ public AnalogChannel(String voiceName, String dnis) { super(); mydev = voiceName; voiceDev = new Voice(this, mydev); this.dnis = dnis; try { agLine = new AGLine(this, mydev); if ((Dialogic.debug & Dialogic.DEBUG_CHANNEL) != 0) { System.out.println(this.toString() + " SC capable"); } } catch (RuntimeException rte) { // Non SC boards don't support switching! agLine = null; if ((Dialogic.debug & Dialogic.DEBUG_CHANNEL) != 0) { System.out.println(this.toString() + " non SC capable"); } } /** Dialtones: * Used also for hung up detection. * If not programmed via addStDialtone(), a default of 420/5 is used. */ if (dtf11 != 0) { if (dtf12 != 0) dialtone1 = voiceDev.buildDtGTD(dtf11, dtf12, dtd1); else dialtone1 = voiceDev.buildStGTD(dtf11, dtd1); } else dialtone1 = voiceDev.buildStGTD(420, 5); voiceDev.setGTD(dialtone1, true, false); if (dtf21 != 0) { if (dtf22 != 0) dialtone2 = voiceDev.buildDtGTD(dtf21, dtf22, dtd2); else dialtone2 = voiceDev.buildStGTD(dtf21, dtd2); voiceDev.setGTD(dialtone2, true, false); } group = new ThreadGroup(mydev + " group"); // Enable CallerId by default setCallerIdEnabled(true); // Init default DXCAP, this is very site dependant! // (and is only kept here for some author's comfort :-) cap = new DXCAP(); cap.intflg = 2; /* OPTDIS: No SIT tones down here */ cap.cnosig = 4000; /* 40 secs maximum to something */ cap.nbrbeg = 1; /* Start after 2nd ring. 772 gives a free one 1st */ cap.nbrdna = 5; /* 5 rings before NoAnswer */ cap.lo1tola = 22; /* tolerances to 15 +- 2 lows in busy */ cap.lo1tolb = 19; /* 14 to 17 and 16 to 13 */ cap.lo2tola = 22; cap.lo2tolb = 19; cap.hi1tola = 13; /* Standard values */ cap.hi1tolb = 13; cap.lo1bmax = 40; /* Fast busy all over the place (35/15) */ cap.lo2bmax = 40; cap.hi1bmax = 50; cap.logltch = 12; /* Lo glitch, short because of short busy's */ cap.higltch = 20; cap.lo1rmax = 40; cap.lo2rmin = 150; /* Inter ring, 768 has as low as 116/170 */ cap.hisiz = 90; /* default, hi > hisiz ==> use alowmax */ cap.alowmax = 450; /* Ring lo, 772 has 105/390 */ cap.blowmax = 350; /* Ring lo, 747 has 82/180 */ serviceThread = new Thread(group, this, mydev + " service"); serviceThread.start(); } /** * Create an analog channel. * @param voiceName Device name for the channel. */ public AnalogChannel(String voiceName) { this(voiceName, ""); } /** * Close the channel. */ public void close() { super.clear(); if (serviceThread != null) { Channel.stopGroup(this); serviceThread.interrupt(); try { serviceThread.join(500); } catch (InterruptedException ie) {}; serviceThread = null; } if (voiceDev != null) { voiceDev.close(); voiceDev = null; } } /** * Set the channel call analysis parameters. * @param cap Call analysis parameters object. */ public void setCAP(DXCAP cap) { if (cap == null) throw new RuntimeException("Bad parameter value"); this.cap = cap; } /** * Set the blind dial state for the channel. *

* When set, outbound calls do not wait for a dialtone before dialing begins. * @param state True to set blind dial on, false otherwise. */ public void setBlindDial(boolean state) { blindDial = state; } /** * Set the Perfect Call analysis state for the channel. *

* When set, the channel processes the audio signal on the line to determine * call progress and dialing results (connected, busy, intercept, etc.). * @param state Set to true to turn Perfect Call on, false otherwise. */ public void setPerfectCall(boolean state) { perfectCall = state; } /** * Enable or disable Caller-ID processing. *

* This method controls the state of Caller-ID processing on the voice hardware. * The hardware, Dialogic drivers, and telco connection must all support Caller-ID * for this function to have any effect. * @param state Set to true to turn Caller-ID processing on, false otherwise. * @since 0.5 */ public void setCallerIdEnabled(boolean state) { callerIdEnabled = state; // Set the minimum rings to answer for most caller-ID technologies minRings = state ? 2 : 1; if (linestate == IDLE) { // Kick serviceThread to reconfigure it... serviceThread.interrupt(); } } /** * Get Caller-ID enabling state. * @return True if Caller-ID processing is enabled. * @since 0.5 */ public boolean getCallerIdEnabled() { return callerIdEnabled; } /** * Starts the thread to handle processing for this channel. */ public void run() { while(true) { clear(); linestate = IDLE; setState(FREE); try { voiceDev.setCallerIdEnabled(callerIdEnabled); voiceDev.waitRings(minRings); } catch (ChannelException ie) { // Somebody is asking me to leave, outdial/reconfig if (linestate != OFFH) { setState(OOS); // In the mean time continue; } return; } call = new Call(AnalogChannel.this); call.dnis = dnis; linestate = ICALL; setState(INCOMING); if (handler != null) handler.handleCall(call); } } /** * Returns the class name and the name of the asociated voice resource. */ public String toString() { return "AnalogChannel on " + voiceDev; } /** * Get the associated Voice resource. * @return The Voice object attached to this channel. */ public Voice getVoice() { return voiceDev; } /** * Get the associated Network device * only available on SC capable boards, used for routing. * @return The Device object attached to this channel. */ public Device getNetwork() { return agLine; } /** * Accept a call. */ void accept() { if (linestate != ICALL) throw new ChannelException("accept(): wrong state"); // As there is no way of signalling this, just change the linestate. linestate = RINGS; } /** * Answer a call. */ void answer(int rings) { if (linestate == ICALL) accept(); if (linestate != RINGS) throw new ChannelException("answer(): wrong state"); EVT evt = null; long now = System.currentTimeMillis(); long conn = call.startTime() + (rings - minRings) * 3000; // 3 secs each, some already gone try { beginService(); if (conn > now) evt = serviceWaitEvent(conn - now); if (evt == null) { voiceDev.setHook(true); linestate = IN; } else { linestate = RESET; setState(OOS); throw new ChannelException("accept():" + evt); } } finally { endService(); } } /** * Reject a call. */ void reject(int reason) { if (linestate != ICALL) throw new ChannelException("reject(): wrong state"); linestate = RESET; setState(OOS); // As there is no way of signalling this, just leaves it ringing. // Wait for RingOff event upto 30 secs try { beginService(); serviceWaitEvent(30000); } finally { endService(); } } /** * Places an outbound call. * @see #setBlindDial * @see #setPerfectCall * @param call The call object to be used for this connection. * @param number The number to dial */ void dial(Call call, String number) { if (linestate != IDLE) throw new ChannelException("dial(): wrong state"); linestate = OFFH; if (serviceThread != null) { Channel.stopGroup(this); serviceThread.interrupt(); try { serviceThread.join(); } catch(InterruptedException ie) { Thread.currentThread().interrupt(); } serviceThread = null; } this.call = call; setState(OUTGOING); if ((Dialogic.debug & Dialogic.DEBUG_CHANNEL) != 0) { System.out.println(this.toString() + " calling " + number); } try { beginService(); if (perfectCall && !perfectCallInit) { // Init PerfectCall engine voiceDev.initPerfectCall(); perfectCallInit = true; } EVT evt = null; voiceDev.setHook(true); if (!perfectCall) { // Wait dialtone evt = serviceWaitEvent(3000); // 3 seconds ok ? // D41ESC quirk... if (evt == null || (evt.type != EVT.TDX_CST || evt.cstevt != EVT.DE_TONEON)) { if ((Dialogic.debug & Dialogic.DEBUG_CHANNEL) != 0) { System.out.println(this.toString() + " No dialtone!"); } if (!blindDial) { if (call != null) call.drop(); throw new ChannelException("dial(): No Dialtone"); } } } } finally { endService(); } linestate = DIAL; voiceDev.setGTD(dialtone1, false, false); if (dialtone2 != 0) voiceDev.setGTD(dialtone2, false, false); linestate=OUT; voiceDev.dial(number, cap, perfectCall); } /** * Get the phone number of the calling party, when available. * * @returns the directory number (DN) of the caller. Returns a * zero-length string if the data is not available or not supported * by the underlying hardware. */ String ani() { // Analog ANI is Caller-ID return voiceDev.getAni(); } /** * Retrieves extended Caller-ID information, such as the time of the call. *

* Does not accept Dialogic.CLIDINFO_FRAMETYPE as an argument; * see {@link #callerIdFrameType} to get the frame type. * * @param infoType the type of data to be retrieved. * @return The value of the requested data element. If the element is not present, * a zero-length string is returned. * @since 0.5 */ public String callerIdExtended(int infoType) { return voiceDev.getCallerIdExtended(infoType); } /** * Gets the type of the Caller-ID data frame. The frame type defines * what additional information is available, if any. * @return An integer that identifies the frame type. Frame types are * defined in {@link local.dialogic.Dialogic}. * @since 0.5 */ public int callerIdFrameType() { return voiceDev.getCallerIdFrameType(); } /** * Retrievs accounting info for the current call. * @return zero. */ int pulses() { // As there is no signalling for this, just return 0. return 0; } /** * Clear the channel. *

* Stops any current voice operation, sets the line on hook, flushes * any DTMF in the input buffer, and resets all other channel states. */ synchronized void clear() { if (linestate == RESET) return; /** * Beware of clear loop in ICALL linestate: * when we clear in ICALL, line is actually ringing, after we do onhook, * driver sends us again a RING and we are likely to clear again, and again. */ if (linestate == ICALL) reject(0); super.clear(); linestate = RESET; voiceDev.stop(); voiceDev.clear(); voiceDev.setHook(false); try { Thread.sleep(200); } catch(InterruptedException ie) { Thread.currentThread().interrupt(); } flush(); if (agLine != null) { agLine.listen(voiceDev); voiceDev.listen(agLine); } voiceDev.setGTD(dialtone1, true, false); if (dialtone2 != 0) voiceDev.setGTD(dialtone2, true, false); if (serviceThread == null) { serviceThread = new Thread(group, this, mydev + " service"); serviceThread.start(); } } /** * Process events to track changes in line state. */ protected EVT service(EVT evt) { switch(evt.type) { case EVT.TDX_CST: if (linestate != RESET && linestate != IDLE && linestate != OFFH && (evt.cstevt == EVT.DE_LCOF || (evt.cstevt == EVT.DE_TONEON && (evt.cstdata == dialtone1 || evt.cstdata == dialtone2)))) { clear(); if (call != null) call.drop(); throw new HangUpException(); } break; case EVT.TDX_CALLP: evt.data = voiceDev.callpStatus(); if (evt.data == Dialogic.CR_CNCT) { voiceDev.setGTD(dialtone1, true, false); if (dialtone2 != 0) voiceDev.setGTD(dialtone2, true, false); if (call != null) call.connect(); break; } else if (evt.data == Dialogic.CR_BUSY) throw new BusyException("dial(): Busy"); else throw new ChannelException("dial(): error in call progress"); } return evt; } }