Call State Change Events not firing on initial call

Jul 8, 2013 at 8:00 PM
Hi Mark,
Two things:
The CallStateChanged Event doesn't appear to be firing for initial call progress states (e.g. Dialing, Progressing) until the first time Connected is achieved. After this point, I can Drop the call and see the Idle and Disconnected states. All events fire on subsequent MakeCall attempts.
Am I missing an initialization step?
Are there time constraints between the Opening the Line and Making the call?

Second Item:
After disconnecting a call, If I try to make another call in less than 40 seconds, I get bizarre behavior with my stream buffers. If I wait 40 seconds or more to dial another call, everything works fine every time. I'm calling Drop() to disconnect.
What's going on in this 40-seconds and is there any way I can shorten the period?
Can I status the system to determine when it is ready to accept another call attempt?

Thanks,
Paul
Jul 11, 2013 at 2:05 PM
I have the same problem here.
First time I receive a call I just get an 'IDLE' state after the call.
After that everything works OK.

Also when I start the program and remove the hook for a second the IDLE state event will fire and after that also everything works normal.

Any solution?
Coordinator
Jul 17, 2013 at 3:52 PM
Not sure about the first issue; it's been a while since I've tried this library - you might try a low-level monitor tool such as the Tapi Browser to see if the call state notifications are being reported. It could be a initialization bug where the inbound call state aren't being tracked back to the new call until we get a call handle. Can you post some of your code?

For the second issue, try disposing the call to see if that helps.

mark
Jul 18, 2013 at 5:19 PM
Thanks, Mark.

I'll look into the Tapi Browser. For now, I've downloaded the source and included the DLL from the source into my project so I can trace through some of your code.

Posted below is my code. Note that after MakeCall I'm explicitly looking for Call States, all of which occur as expected. After I get to the Connected State and drop out of this module, only then does the CallStateChanged Event fire with the Connected State. I also set some breakpoints in your TapiCall module and the only time that event fires is on the Connected state.

With regard to the 40 second delay between calls, I tried to dispose of the call after Drop but that didn't seem to do anything.

Thanks for taking a look when you get a chance.

Paul
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.IO.Ports;
using JulMar.Atapi;


public class Class1
{
    class CcsModemConnection : CcsCommConnection
    {
        public static TapiManager _TapiManager;
        public static TapiLine _ModemLine;

        public static FileStream _TapiStream;

        public static TapiCall _TapiCall;

        public static BinaryReader _ModemReader;
        public static BinaryWriter _ModemWriter;

        public static bool _ModemConnectedFlag = false;

        public CcsModemConnection()
        {
            CurrentConnectionType = ConnectionTypeEnum.Modem;
        }

        public override bool Connect()
        {
            _ModemConnectedFlag = false;

            try
            {
                if (_TapiCall != null)
                    _TapiCall.Dispose();

                App.AppWindow.DisplayCommNotice("Attempting connection to panel via Modem");

                string phoneNumber = App.ServerSettings.ModemPhoneNumber;

                try
                {
                    if (_TapiManager == null)
                    {
                        _TapiManager = new TapiManager("EnumTapiLines");

                        if (!_TapiManager.Initialize())
                        {
                            App.AppWindow.DisplayCommNotice("TAPI failed to find any lines or phones to manage.");
                            return false;
                        }

                        List<TapiLine> modemLines = new List<TapiLine>();

                        modemLines = FindTapiDataModems(_TapiManager);

                        if (modemLines.Count() == 0)
                        {
                            App.AppWindow.DisplayCommNotice("No TAPI lines were found.");
                            return false;
                        }
                        else
                        {
                            _ModemLine = modemLines[0];     // Default to first modem for now.  

                            App.ServerSettings.ModemPermanentId = _ModemLine.PermanentId;
                        }

                        App.AppWindow.DisplayCommNotice(string.Format("Found: {0}, opening.", _ModemLine.Name));

                        try
                        {
                            _ModemLine.Open(MediaModes.DataModem);
                        }
                        catch (TapiException ex)
                        {
                            throw new CommConnectException("Error Opening Line. Disconnect and try again later.", ex);
                        }

                        _ModemLine.CallStateChanged += new EventHandler<CallStateEventArgs>(modemLine_CallStateChanged);
                    }

                    // At this point we have an Open Modem Line.

                    App.AppWindow.DisplayCommNotice(string.Format("Making call to {0}", phoneNumber));

                    MakeCallParams param = new MakeCallParams();
                    param.MinRate = 2400;
                    param.MaxRate = 57600;
                    param.MediaMode = MediaModes.DataModem;

                    App.AppWindow.DisplayCommNotice("Waiting for connection...");

                    _TapiCall = _ModemLine.MakeCall(phoneNumber, null, param);

                    int maxConnectTime = 120;   // One minute

                    while ((_TapiCall.CallState == CallState.Dialing) || (_TapiCall.CallState == CallState.Proceeding))
                    {
                        System.Threading.Thread.Sleep(500);
                        maxConnectTime--;
                    }

                    if (_TapiCall.CallState == CallState.Connected)
                    {
                        SetupModemCommBuffer(_TapiCall);
                        _ModemConnectedFlag = true;
                    }
                    else
                    {
                        _ModemConnectedFlag = false;
                    }
                }
                catch (TapiException ex)
                {
                    throw new CommConnectException("Error Connecting to Modem. Disconnect and try again later.", ex);
                }
            }
            catch (Exception ex)
            {
                throw new CommConnectException("Error connecting to panel via modem", ex);
            }

            return _ModemConnectedFlag;
        }

        void modemLine_CallStateChanged(object sender, CallStateEventArgs e)
        {
            App.AppWindow.DisplayCommNotice(string.Format("{0} is now {1}", e.Call, e.CallState));

            switch (e.CallState)
            {
                case CallState.Dialing:
                case CallState.Dialtone:
                case CallState.Proceeding:
                    break;

                case CallState.Connected:

                    _ModemConnectedFlag = true;
                    break;

                case CallState.Busy:
                case CallState.Disconnected:

                    _ModemConnectedFlag = false;
                    break;

                case CallState.Idle:

                    _ModemConnectedFlag = false;
                    break;

                case CallState.Accepted:
                case CallState.Conferenced:
                case CallState.None:
                case CallState.Offering:
                case CallState.OnHold:
                case CallState.OnHoldPendingConference:
                case CallState.OnHoldPendingTransfer:
                case CallState.Ringback:
                case CallState.SpecialInfo:
                case CallState.Unknown:
                    break;
            }
        }


        private static void SetupModemCommBuffer(TapiCall call)
        {
            try
            {
                Microsoft.Win32.SafeHandles.SafeFileHandle handle = _TapiCall.GetCommHandle();

                _TapiStream = _TapiCall.GetCommStream();

                if (_TapiStream == null)
                {
                    App.AppWindow.DisplayCommNotice("No data stream support found.");
                    return;
                }

                _ModemReader = new BinaryReader(_TapiStream);
                _ModemWriter = new BinaryWriter(_TapiStream);

            }
            catch (Exception ex)
            {
                throw new CommConnectException("Error creating Modem comm buffer", ex);
            }
        }


        public override bool Disconnect()
        {
            try
            {
                CcsModemConnection._ModemConnectedFlag = false;

                if (_TapiCall != null)
                {
                    if (_TapiCall.CallState == CallState.Connected)
                    {
                        try
                        {
                            App.AppWindow.DisplayCommNotice("Disconnecting from panel via modem...");

                            _TapiCall.Drop();
                        }
                        catch
                        {
                        }

                        //_TapiCall.Line.CallStateChanged -= modemLine_CallStateChanged;

                        //try
                        //{
                        //   _TapiCall.Dispose();
                        //}
                        //catch
                        //{
                        //}
                    }
                    else
                    {
                        App.AppWindow.DisplayCommNotice("Already Disconnected");
                    }
                }
            }
            catch (Exception ex)
            {
                throw new CommConnectException("Error disconnecting from panel via modem", ex);
            }

            return true;
        }

        public override CommErrorEnum SendData(byte[] message, int length)
        {
            try
            {
                _ModemWriter.Write(message, 0, length);
            }
            catch (Exception ex)
            {
                throw new CommIoException("SendData: Error Writing to Modem", ex, CommErrorActionEnum.None);
            }
            return CommErrorEnum.CommOk;
        }

        public override CommErrorEnum GetData(ref byte[] message, int length)
        {
            // Check for a response.
            int totalBytes = 0;
            int bytesRead = 0;
            int bytesRemaining = length;
            Byte[] buffer = new Byte[CommConstants.COMM_MAX_MESSAGE];

            try
            {
                do
                {
                    bytesRead = _ModemReader.Read(buffer, 0, bytesRemaining);

                    Array.Copy(buffer, 0, message, totalBytes, bytesRead);
                    totalBytes += bytesRead;
                    bytesRemaining -= bytesRead;
                }
                while (totalBytes < length);
            }
            catch (Exception ex)
            {
                throw new CommIoException("GetData: Error Receiving Data From Modem", ex, CommErrorActionEnum.Reset);
            }
            return CommErrorEnum.CommOk;
        }

        public override void FlushReadBuffer()
        {
            try
            {
                App.AppWindow.DisplayCommNotice("Flushing buffer...");

                _ModemReader.BaseStream.Flush();
            }
            catch (Exception ex)
            {
                throw new CommIoException("FlushReadBuffer: Error Getting Data From Modem", ex, CommErrorActionEnum.None);
            }
        }

        /// <summary>
        /// Run through list of connections and pick-out list of TAPI-Capable Data Modems.
        /// </summary>
        /// <param name="tapiManager"></param>
        /// <returns></returns>
        public List<TapiLine> FindTapiDataModems(TapiManager tapiManager)
        {
            List<TapiLine> modemLines = new List<TapiLine>();

            if (tapiManager.Lines.Length > 0)
            {
                foreach (TapiLine line in tapiManager.Lines)
                {
                    if ((line.Capabilities.MediaModes & MediaModes.DataModem) != 0)
                    {
                        modemLines.Add(line);
                        break;
                    }
                }
            }
            else
            {
                App.AppWindow.DisplayCommNotice("No TAPI lines found.");
            }

            return modemLines;
        }
    }
}
Coordinator
Sep 9, 2013 at 10:27 PM
This should be fixed in 1.04.
Sep 12, 2013 at 10:16 PM
Thanks, Mark.

I Just downloaded 1.04 and it certainly fixed the lack of CallStateChanged events on the initial call. It didn't fix the "40 second" problem where, after disconnecting, I can't place another call for 40 seconds.

Another interesting note: after disconnecting, waiting 40 seconds, then dialing another call, I get all subsequent CallState events at the same time; but only after the connection is successful.

Paul
Coordinator
Sep 12, 2013 at 10:32 PM
How are you disconnecting the call? Are you disposing it? What notifications are you getting? It sounds like the TSP thinks the call is still alive for some reason.. have you tried the same code on a different TSP to see if it has the same behavior?


Sep 26, 2013 at 3:29 PM
Hi Mark,

I'm currently using Drop() when I determine that data has stopped flowing. I tried using Dispose() as well but that doesn't seem to do anything. I suppose I'm still looking for the right way to clean-up a failed connection then reconnect. Right now, when the current connection fails, I send several messages to the UI but all are blocked and are never shown to the user. I need to resolve the whole UI issue with regard to ATAPI. None of my messaging gets displayed until the entire connectio process either passes or fails. I'm using Windows Forms.

I've started using Shutdown() when my application closes to make sure all the bits-and-pieces are properly disposed of...and it seems to help on shutdown. Do you think I should also use Shutdown() when the connection fails?

With regard to other TSPs...I have used many different modems with earlier versions of TAPI and C++. I'm Always communicating with the same the same embedded control system.

Paul
Coordinator
Oct 2, 2013 at 3:00 PM
Shutdown will kill the TAPI connection - which would certainly reset everything. Although it's up to the hardware to decide what to do with active calls (drop them, redirect them, etc.) So, it's a modem you are using with the Unimodem provider then?

mark
Oct 2, 2013 at 5:52 PM
I tried shutdown and you're right...it does way too much.

I'm using a US Robotics USB 56K modem with their driver. I'm not sure where the "Unimodem provider" fits-in with all this...

My normal scenario is that a user initiates a call (data connection) from his PC to the control system that has an embedded industrial modem. When he's done he hangs-up. As long as he waits 45 seconds before making another call, all is well.

Most problems occur when the call is dropped by the control system or the phone company. In these cases, the user has to manually initiate another call and that is where I have some problems with re-connecting. In these cases I added a call to Dispose() after a call to Drop() and it seems to work better. In some cases, the data just quits flowing...but I haven't been able to recreate that problem regularly.

In general, I can ship the code I have working now. The problem that really bugs me is that the call process locks-out the UI such that all the nice status messaging is not seen by the user until the entire connection process succeeds (or fails). Then all the messages flow to the display at once.

Paul