PPP detecting offline

Hi,

We are using a Telit modem using the PPP class and can get online all well.

We are testing stability - i.e. long runtimes and doing things like removing the antenna to simulate disconnects.

While we have a NetworkChange.NetworkAvailabilityChanged event handler and get events when the network connects, it appears nothing is received when the network goes down.

The PPP object has no events for disconnect either. The property IsLinkConnected shows true even though we know the modem is ‘offline’.

This causes socket connections to either hang or give an exception on send.

How should we detect a PPP link going offline?

What about ping before send data to server?

Ping is a potential workaround, but the server I am connecting to doesn’t reply to a ping… and adding other servers is another product dependency.

Ideally there must be a way to know when a PPP link has failed. My Windows 95 dial up modem definitely used to know 15 years ago…

PPP stack does not include a mechanism to detect if the connection is still on or not.
It terminates the connection only if it gets the termination packet from the modem otherwise it will never know if the connection is terminated or not.

You need to create this mechanism on a higher level (pinging a server is one of the solutions).

Say I ping a another server and the ping works but the connection then drops as we move to the web post (code snippet below), does anyone have a suggestion as to how to stop client.GetRequestStream() locking ‘forever’ when the connection goes down? I have applied the timeouts, and looked around for other suggestions… if a full .NET stack then using an asynchronous BeginGetRequestStream appears an option, which we don’t appear to have.


try
{
	HttpWebRequest client = HttpWebRequest.Create("http://domain/service/") as HttpWebRequest;

	client.ContentType = "application/json; charset=utf-8";

	client.Method = "POST";

	client.Timeout = 5000;
	client.ReadWriteTimeout = 5000;


	byte[] data = new byte[100];
	client.ContentLength = 100;


	using (Stream dataStream = [b]client.GetRequestStream())[/b]
	{

		dataStream.Write(data, 0, 100);

		// Get a response from the server.
		using (WebResponse resp = client.GetResponse())
		{


			// Get the network response stream to read the page data.
			if (resp != null)
			{
				using (Stream respStream = resp.GetResponseStream())
				{
					string page = "";
					int bytesRead = 0;
					byte[] byteData = new byte[4096];

					// allow 5 seconds for reading the stream
					respStream.ReadTimeout = 5000;

					if (resp.ContentLength != -1)
					{
						if (resp.ContentLength < 4096)
						{
							bytesRead = respStream.Read(byteData, 0, (int)resp.ContentLength);

							page = new String(System.Text.Encoding.UTF8.GetChars(byteData));

						}
					}
				}
			}

			resp.Close();
		}
	}
}
catch (Exception e)
{
}

update: I’m going to drop down a level to the socket level and do the POST manually myself and see if that helps… will probably be quicker to run anyway…

No, totally doesn’t help.

Socket.Connect locks up forever too… same problem!

Maybe use UDP?

I’m sending data to a web server. UDP doesn’t really help there.

So I’m not the first to discover this kind of whatever.

http://www.dotnetsolutions.co.uk/blog/archive/2010/09/09/socket-connect-a-word-of-warning/

Looks like starting 2 threads, a socket and timeout one and doing a thread.abort on the socket thread after the timeout might be a working workaround. Perhaps. More testing will tell what other issues that brings to the table.

Yes I remember other community members following this solution.

We are planning to work on enhancing the TCP/IP preformance in NETMF 4.2 but this will take some time.

I have to say I am finding using NETMF a little painful so far. I am used to working at an embedded level in C and a C# level in Windows… and I expected basic stuff like sockets to ‘just work’.

Instead you have to workaround issues that shouldn’t really be there which makes otherwise clean code quite clunky.

For instance having worked around that, I found that after around 60 socket connects/disconnects, I was getting an exception after every connect when trying to Write to the socket… turns out sockets ‘linger’ and actually need another option setting to prevent that:

            connection.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.Linger, new byte[] { 0, 0, 0, 0 });

            connection.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);

And yes, I will be moving to a keepalive on the socket, but just saying… it’s not as smooth sailing as I’d hoped. Things like the socket just blocking forever to me is a pretty basic ‘has anyone tried using this’ thing, and yet we’re in release 4+ of the platform!

Power management is also a area in need of much improvement… The EMX module uses too much power when it’s basically doing nothing. You’re not going to tell me there are not lower CPU modes that couldn’t be used when everything is sat in a Thread.Sleep…

I do actually like the platform! :slight_smile: Just feel it needs some more TLC.

Normally a modem drops its DCD line when it looses connection. It’s up to the stack (or higher level) to detect DCD changes and do something with it (f.e. send ATH and ATZ commands).

Well my joys continue… I am using a parallel thread on Socket connections to detect blocks and abort the thread… on 3 failures I then drop back and totally reset the modem layer, including power cycling the Telit. A clean start.

Appeared to be working fine and kept HTTP posts running all day.

However now I get back and find that after 8 modem reconnects today that PPP.Connect is now the thing hanging. Completely.

The rest of the unit is running fine processing serial data, flashing LEDs etc.

I am doing a PPP.Disconnect and then Connect to restart PPP after the modem is reset and AT commands pass.

Should I for some reason use PPP.Disable/Enable as well?

What am I doing wrong?

It is hard to guess what you are doing if we do not see the code. Anyway these might give you some hints:

Discard UART In buffer after you disconnect.
Discard UART Out buffer before you colse the uart, if you are doing that in the code.

After I PPP.Disconnect, I then don’t reset the UART at all, however I do read and write to it to run through the AT command processing again.

I check all responses… so the UART is operational at the point of calling PPP.Connect.

Telit module code below:

using System;
using Microsoft.SPOT;
using System.IO.Ports;
using System.Text;
using System.Threading;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Net;
using Microsoft.SPOT.Net.NetworkInformation;


namespace Link2.Hardware
{
    public class Telit
    {
        public SerialPort port;

        static byte[] cr = new byte[1];

        Thread modem;

        private bool restart = true;

        public Telit(string serialPort)
        {
            cr[0] = Link2.Encoding.CR;

            port = new SerialPort(serialPort, 115200);

            PPP.Enable(port);

            port.Handshake = Handshake.XOnXOff;
            port.Open();

            NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);

            modem = new Thread(ModemThread);
            modem.Start();
        }

        void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
        {
            //Connected = e.IsAvailable;
        }

        public bool Connected = false;

        private int restartcnt;

        public void ModemThread()
        {
            while (true)
            {
                if (!Connected)
                {
                    if (restart)
                    {
                        ResetModem();
                        restart = false;
                        restartcnt = 0;
                    }

                    if (ClientConnectModem())
                    {
                        NetworkInterface[] netif = NetworkInterface.GetAllNetworkInterfaces();

                        Connected = false;

                        // attempt PPP connection
                        Debug.Print("Connect PPP");
                        switch (PPP.Connect("", ""))
                        {
                            case PPP.ConnectionStatus.Authentication_Faild:
                                Debug.Print("Authentication_Failed");
                                break;
                            case PPP.ConnectionStatus.Connection_Faild:
                                Debug.Print("Connection_Failed");
                                break;
                            case PPP.ConnectionStatus.Connected:
                                // PPP setting will overload the first interface settings since only one interface is supported.
                                netif = NetworkInterface.GetAllNetworkInterfaces();

                                Debug.Print("PPP Network settings:");
                                Debug.Print("IP Address: " + netif[0].IPAddress);
                                Debug.Print("Subnet Mask: " + netif[0].SubnetMask);
                                Debug.Print("Default Getway: " + netif[0].GatewayAddress);
                                Debug.Print("DNS Server: " + netif[0].DnsAddresses[0]);

                                Connected = true;
                                restartcnt = 0;
                                break;
                            case PPP.ConnectionStatus.Disconnected:
                                Debug.Print("Disconnected");
                                break;

                        }

                    }
                    else
                    {
                        if (restartcnt++ > 3)
                        {
                            restart = true;
                        }
                    }
                }

                Thread.Sleep(1000);
            }
        }

        OutputPort gsmpower;
        OutputPort gsmreset;

        public void ResetModem()
        {
            Debug.Print("Reset modem");

            // bounce gsm reset
            if (gsmpower == null)
            {
                gsmpower = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.IO23, false);
            }
            else
            {
                gsmpower.Write(false);
            }
            Thread.Sleep(3000);
            gsmpower.Write(true);

            if (gsmreset == null)
            {
                gsmreset = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.IO49, false);
            }
            else
            {
                gsmreset.Write(false);
            }
            Thread.Sleep(200);
            gsmreset.Write(true);
        }

        byte[] reply = new byte[9];

        public bool SendATCommand(string command, bool connect = false)
        {
            bool ret = false;

            byte[] output = System.Text.Encoding.UTF8.GetBytes(command);

            Debug.Print(command);

            port.Flush();
            port.Write(output, 0, output.Length);
            port.Write(cr,0,1);

            DateTime expire = DateTime.Now.AddSeconds(3);

            // search for OK <CR>
            string compare = "OK\r";
            string sreply = "";

            if (connect)
            {
                // search for CONNECT <CR>
                compare = "CONNECT\r";
            }

            int expectedlen = compare.Length - 1;
            reply[compare.Length] = 0;

            while (!ret && (expire > DateTime.Now))
            {
                if (port.BytesToRead > 0)
                {
                    // mini fifo as we want the last bit of data, not an echo etc.
                    Array.Copy(reply, 1, reply, 0, expectedlen);
                    port.Read(reply, expectedlen, 1);

                    try
                    {
                        sreply = new string(System.Text.Encoding.UTF8.GetChars(reply));
                    }
                    catch
                    {
                        // ignore
                    }

                    ret = (sreply == compare);
                }

                Thread.Sleep(20);
            }

            Debug.Print(sreply);

            return ret;
        }

        public bool ClientConnectModem()
        {
            int retry = 0;

            // PPP disconnect command?
            port.Write(new byte[] { 0x7E, 0xFF, 0x7D, 0x23, 0xC0, 0x21, 0x7D, 0x25, 0x7D, 0x2A, 0x7D, 0x20, 0x7D, 0x30, 0x2B, 0xD3, 0x45, 0xAF, 0x7D, 0x20, 0x3C, 0xCD, 0x74, 0x7D, 0x20, 0x7D, 0x20, 0x7D, 0x20, 0x7D, 0x20, 0x2A, 0x9D, 0x7E }, 0, 34);// Terminate request          

            // try and disconnect
            SendATCommand("\r+++");
            SendATCommand("ATH");

            // Soft Reset, basic default restore
            bool ret = SendATCommand("ATZ");

            if (ret)
            {
                LED.RadioFlash();
            }

            // Echo Disabled
            ret = ret && SendATCommand("ATE");

            // select US frequencies
            ret = ret && SendATCommand("AT#BND=3");

            // XonXOff flow control
            ret = ret && SendATCommand("AT&K=1");

            if (ret)
            {
                do
                {
                    ret = SendATCommand("AT+cgdcont=2,\"IP\",\"broadband\"\r");
                } 
                while (!ret && (retry++ < 20));
            }


            if (ret)
            {
                retry = 0;

                do
                {
                    ret = SendATCommand("ATDT*99***2#", true);
                }
                while (!ret && (retry++ < 20));
            }

            return ret;
        }

        public void Restart()
        {
            PPP.Disconnect();

            restart = true;
            Connected = false;
        }
    }
}

The code seems fine.
So you are sure that AT CONNECT is recieved and then it is hanging in PPP.Connect(). It is not raising an exception.

I ran another 24 hour run and it didn’t lockup again. But this needs to run forever, as that is the purpose of an embedded controller. So one hang in that overnight test doesn’t leave a warm and fuzzy…

Correct and this is why embedded systems should use watchdog