#undef TRACE using System; using System.IO; using System.IO.Ports; using System.Threading; using Microsoft.SPOT; namespace CPF { // This class is used for communicating with the A3LA through the serial interface using // Hayes compatible AT commands public class A3LA : IDisposable { #region Public classes and types public enum Result { Success, Failure, TimeOut }; public enum Response { Match, NoMatch, Timeout }; public enum BuffersToClear { MobileOriginated, MobileTerminated, BothOriginatedAndTerminated }; public const int MT_Offset = 256; public const int MO_TransferredSuccessfully = 0; public const int MO_Transferred_MT_TooBig = -1; public const int MO_TransferredNoLocationUpdate = -2; public const int MO_TransferDidNotFinishInAllowedTime = -10; public const int MO_GSS_ReportsMsqQueueFull = -11; public const int MO_MessageHasTooManySegments = -12; public const int MO_GSS_ReportsSessionDidNotComplete = -13; public const int MO_InvalidSegmentSize = -14; public const int MO_AccessDenied = -15; public const int MO_ModemLocked = -16; public const int MO_GatewayNotResponding = -17; public const int MO_RF_Lost = -18; public const int MO_LinkFailure = -19; public const int MO_UnableToInitiateCall = -32; public const int MO_ModemBusy = -35; public const int MT_NoMessageReceived = -(MT_Offset + 0); public const int MT_MessageReceived = -(MT_Offset + 1); public const int MT_MessageReceptionError = -(MT_Offset + 2); #endregion static void Trace(string s) { #if (TRACE) Debug.Print(s); #endif } #region Internal properties and functions // true when disposed private enum ResponseStates { MatchingPrompt, ExtractingResponse, MatchingTerminator }; private Uart _A3LA; private byte[] _request; private int _requestLength; private byte[] _response; private int _responseLength; private bool _disposed; private void SendCommand(byte[] command) { _A3LA.SendLine(command); } private Response WaitForResponse(byte[] response, out int responseLength, byte[] prompt, byte[] terminator, int timeout) { int matchPromptPos = 0; int matchTerminatorPos = 0; ResponseStates state; byte ch; responseLength = 0; Trace("*************************************************************"); state = (prompt == null) ? ResponseStates.MatchingTerminator : ResponseStates.MatchingPrompt; DateTime timeoutAt = DateTime.Now.AddMilliseconds(timeout); // Recieve lines until timed out while ((DateTime.Now < timeoutAt) | (timeout == -1)) { while (_A3LA.Available() == true) { Thread.Sleep(1); ch = _A3LA.GetChar(); Trace("Read " + Utils.StringFromByte(ch)); switch (state) { case ResponseStates.MatchingPrompt: Trace("Matching Prompt State"); if (ch == prompt[matchPromptPos]) { Trace("Match in Prompt String"); matchPromptPos++; if (matchPromptPos == prompt.Length) { Trace("All of prompt string matched"); state = ResponseStates.ExtractingResponse; } } else { Trace("Prompt string did not Match"); matchPromptPos = (ch == prompt[0]) ? 1 : 0; } break; case ResponseStates.ExtractingResponse: Trace("Extracting Response State"); if (response != null) { if (ch == terminator[0]) { _A3LA.PushBackChar(ch); Trace("Push back CR to input FIFO"); state = ResponseStates.MatchingTerminator; Trace("End of response string"); } else { Trace("Writing " + Utils.StringFromByte(ch) + " To Response Buffer"); response[responseLength++] = ch; } } break; case ResponseStates.MatchingTerminator: Trace("Matching Terminator State"); if (ch == terminator[matchTerminatorPos]) { Trace("Match in Terminator String"); matchTerminatorPos++; if (matchTerminatorPos == terminator.Length) { Trace("All of terminator String Matched"); return Response.Match; } } else { Trace("Terminator string did not Match"); matchPromptPos = (ch == terminator[0]) ? 1 : 0; } break; } } } Trace("Match not found or timeout"); return Response.NoMatch; } #endregion #region Constructor/Destructor // Create new Iridium A3LA Driver Interface class public A3LA(String serialPort, int baudRate = 19200) { _request = new byte[512]; _response = new byte[512]; _A3LA = new Uart(serialPort, baudRate); _disposed = false; } // Dispose AT Interface public void Dispose() { try { if (_disposed == false) _A3LA.Dispose(); } finally { _disposed = true; } } #endregion public void PowerOn() { int unused; // Do whatever is needed to turn the iridium modem on here.... // Wait if necessary.... // Probe the modem to make sure it is attached and working SendCommand(IridiumStrings.AT); if (WaitForResponse(null, out unused, null, IridiumStrings.OK, -1) != A3LA.Response.Match) throw new A3LA_Exception(A3LA_Exception.FAILED_INITIALIZE); // Turn off command echo (it confuses the response parsing) SendCommand(IridiumStrings.AT_E0); if (WaitForResponse(null, out unused, null, IridiumStrings.OK, -1) != A3LA.Response.Match) throw new A3LA_Exception(A3LA_Exception.FAILED_INITIALIZE); // Disable flow control SendCommand(IridiumStrings.AT_AMP_K0); if (WaitForResponse(null, out unused, null, IridiumStrings.OK, -1) != A3LA.Response.Match) throw new A3LA_Exception(A3LA_Exception.FAILED_INITIALIZE); // Ensure there is enough signal to communicate if (SignalQuality() < 1) throw new A3LA_Exception(A3LA_Exception.SIGNAL_QUALITY_TOO_LOW); // Register with Central Services SendCommand(IridiumStrings.AT_SBDAREG); if (WaitForResponse(null, out unused, null, IridiumStrings.OK, -1) != A3LA.Response.Match) throw new A3LA_Exception(A3LA_Exception.COULD_NOT_REGISTER); } public void PowerOff() { } public int SignalQuality() { int length; SendCommand(IridiumStrings.AT_CSQ_RQ); if (WaitForResponse(_response, out length, IridiumStrings.CSQ_RSP, IridiumStrings.OK, -1) != A3LA.Response.Match) throw new A3LA_Exception(A3LA_Exception.ERROR); if (length != 1) throw new A3LA_Exception(A3LA_Exception.DOMAIN_ERROR); int result = (int) (_response[0] - 0x30); if (result < 0 || result > 5) throw new A3LA_Exception(A3LA_Exception.RANGE_ERROR); return result; } public Result Position(byte[] response, out int responseLength) { int pos = 0; SendCommand(IridiumStrings.AT_GGA); WaitForResponse(response, out responseLength, IridiumStrings.GGA_RSP, IridiumStrings.OK, -1); if (!Utils.Match(response, responseLength, IridiumStrings.NMEA_GPGGA, ref pos)) return Result.Failure; else return Result.Success; } public Result SatelliteTimeAndDate(byte[] response, out int responseLength) { int pos = 0; SendCommand(IridiumStrings.AT_ZDA); WaitForResponse(response, out responseLength, IridiumStrings.ZDA_RSP, IridiumStrings.OK, -1); if (!Utils.Match(response, _responseLength, IridiumStrings.NMEA_GPZDA, ref pos)) return Result.Failure; else return Result.Success; } public Response SendTextMessage(byte[] message, int length) { Response rsp; int unused; _requestLength = 0; // Copy the command in the request buffer foreach (byte ch in IridiumStrings.AT_SBDWT) _request[_requestLength++] = ch; // Copy an equals sign "=" into the buffer _request[_requestLength++] = IridiumStrings.EQUALS[0]; // Copy the message into the request buffer[src]; for (int src = 0; src < length; ) _request[_requestLength++] = message[src++]; // Append CR-LF _request[_requestLength++] = IridiumStrings.CR[0]; _request[_requestLength++] = IridiumStrings.LF[0]; Debug.Print("Length of send message request:\r\n" + length.ToString()); Debug.Print("Data in send message request:\r\n" + Utils.StringFromBytes(message, length)); _A3LA.SendRawData(_request, 0, _requestLength); rsp = WaitForResponse(null, out unused, null, IridiumStrings.OK, 1000); return rsp; } public Result ClearMobileOriginatedSequenceNumber() { Response rsp; int error; bool eol; bool success; int pos = 0; ; SendCommand(IridiumStrings.AT_SBDC); rsp = WaitForResponse(_response, out _responseLength, null, IridiumStrings.OK, -1); error = NumberParser.BytesToInt(_response, _responseLength, ref pos, out eol, out success); if (!success) throw new A3LA_Exception(A3LA_Exception.FORMAT_ERROR); if (error == 0) return Result.Success; else return Result.Failure; } public Result ClearBuffers(BuffersToClear action) { Response rsp; int error; bool eol; bool success; int pos = 0; ; switch (action) { case BuffersToClear.MobileOriginated : SendCommand(IridiumStrings.AT_SBDD0); break; case BuffersToClear.MobileTerminated : SendCommand(IridiumStrings.AT_SBDD1); break; case BuffersToClear.BothOriginatedAndTerminated : SendCommand(IridiumStrings.AT_SBDD2); break; default: throw new A3LA_Exception(A3LA_Exception.RANGE_ERROR); } rsp = WaitForResponse(_response, out _responseLength, null, IridiumStrings.OK, -1); error = NumberParser.BytesToInt(_response, _responseLength, ref pos, out eol, out success); if (!success) throw new A3LA_Exception(A3LA_Exception.FORMAT_ERROR); if (error == 0) return Result.Success; else return Result.Failure; } public void ReceiveTextMessage(byte[] message, out int length) { Response rsp; SendCommand(IridiumStrings.AT_SBDRT); rsp = WaitForResponse(message, out length, IridiumStrings.SBDRT_RSP, IridiumStrings.I_NEED_DOLLARS, -1); } public void SendBinaryMessage(byte[] message, int length) { int checksum = 0; // Copy the command in the request buffer foreach (byte ch in IridiumStrings.AT_SBDWT) _request[_requestLength++] = ch; // Insert length of text message into the request buffer _request[_requestLength++] = (byte) ((length & 0xFF00) >> 8); _request[_requestLength++] = (byte) (length & 0x00FF); // Copy the message into the request buffer[src]; for (int src = 0; src < length; ) { checksum += message[src]; _request[_requestLength++] = message[src++]; } // Insert the checksum into request buffer _request[_requestLength++] = (byte)((checksum & 0xFF00) >> 8); _request[_requestLength++] = (byte) (checksum & 0x00FF); _request[_requestLength++] = IridiumStrings.CR[0]; _request[_requestLength++] = IridiumStrings.LF[0]; _A3LA.SendRawData(_request, 0, _requestLength); } public void Status(out int mo_Status, out int mo_NextSeqNum, out int mt_Status, out int mt_NextSeqNum, out int mt_Length, out int mt_Queued) { int pos = 0; bool eol; bool success; Response rsp; SendCommand(IridiumStrings.AT_SBDIX); rsp = WaitForResponse(_response, out _responseLength, IridiumStrings.SBDIX_RSP, IridiumStrings.OK, -1); Trace("Response from Status Message:\t\t" + Utils.StringFromBytes(_response, _responseLength)); if (rsp != A3LA.Response.Match) throw new A3LA_Exception(A3LA_Exception.ERROR); // Parse the MO Status, 0 <= status <= 2 mo_Status = NumberParser.BytesToInt(_response, _responseLength, ref pos, out eol, out success); if (!success) throw new A3LA_Exception(A3LA_Exception.FORMAT_ERROR); if ((mo_Status < 0) || (mo_Status > 2)) throw new A3LA_Exception(A3LA_Exception.RANGE_ERROR); // Parse the modem originated sequence # mo_NextSeqNum = NumberParser.BytesToInt(_response, _responseLength, ref pos, out eol, out success); if (!success) throw new A3LA_Exception(A3LA_Exception.FORMAT_ERROR); if ((mo_NextSeqNum < 0) || (mo_NextSeqNum > 65535)) throw new A3LA_Exception(A3LA_Exception.RANGE_ERROR); // Parse the MT Status, 0 <= status <= 2 mt_Status = NumberParser.BytesToInt(_response, _responseLength, ref pos, out eol, out success); if (!success) throw new A3LA_Exception(A3LA_Exception.FORMAT_ERROR); if ((mt_Status < 0) || (mt_Status > 2)) throw new A3LA_Exception(A3LA_Exception.RANGE_ERROR); // Parse the modem terminated sequence # mt_NextSeqNum = NumberParser.BytesToInt(_response, _responseLength, ref pos, out eol, out success); if (!success) throw new A3LA_Exception(A3LA_Exception.FORMAT_ERROR); // Parse the message Length # mt_Length = NumberParser.BytesToInt(_response, _responseLength, ref pos, out eol, out success); if (!success) throw new A3LA_Exception(A3LA_Exception.FORMAT_ERROR); // Parse the number of queued messages mt_Queued = NumberParser.BytesToInt(_response, _responseLength, ref pos, out eol, out success); if (!success) throw new A3LA_Exception(A3LA_Exception.FORMAT_ERROR); if (eol != true) throw new A3LA_Exception(A3LA_Exception.FORMAT_ERROR); } } }