// #undef TRACE using System; using System.Text; 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 byte[] _received; private ByteString _requestMsg; private ByteString _responseMsg; private ByteString _timeoutMsg; private ByteString _expectedMsg; private ByteString _unexpectedMsg; private ByteString _logMsg; private ByteString _timeUnit; private StringBuilder _msg; private void WriteLog(ByteString logMessage) { _msg.Clear(); _msg.Append(logMessage.ToString()); EngrLogger.writeToColumns(EngrLogger.ColumnNums.comment, EngrLogger.getPrefix(_msg)); } private void LogRequest(byte[] request) { _logMsg.Clear(); _logMsg.Append(_requestMsg); _logMsg.Append(request, request.Length); WriteLog(_logMsg); } private void LogRequest(ByteString request) { _logMsg.Clear(); _logMsg.Append(_requestMsg); WriteLog(_logMsg); } private void LogExpectedResponse(byte[] response, int length) { _logMsg.Clear(); _logMsg.Append(_responseMsg); _logMsg.Append(response, length); _logMsg.Append(_expectedMsg); WriteLog(_logMsg); } private void LogUnexpectedResponse(byte[] response, int length) { _logMsg.Clear(); _logMsg.Append(_requestMsg); _logMsg.Append(response, length); _logMsg.Append(_unexpectedMsg); WriteLog(_logMsg); } private void LogTimeOut(int timeout) { _logMsg.Clear(); _logMsg.Append(_timeoutMsg); Conversions.IntegerToByteString(ref _logMsg, _timeoutMsg.Length, timeout); _logMsg.Append(_timeUnit); WriteLog(_logMsg); } private void SendCommand(byte[] command) { LogRequest(command); _A3LA.SendLine(command); } private Response WaitForResponse(byte[] response, out int responseLength, byte[] prompt, byte[] terminator, int timeout) { int matchPromptPos = 0; int matchTerminatorPos = 0; int receivedLength; ResponseStates state; byte ch; responseLength = 0; receivedLength = 0; state = (prompt == null) ? ResponseStates.MatchingTerminator : ResponseStates.MatchingPrompt; DateTime timeoutAt = DateTime.Now.AddMilliseconds(timeout); // Receive lines until timed out while ((DateTime.Now < timeoutAt) | (timeout == -1)) { while (_A3LA.Available() == true) { Thread.Sleep(1); ch = _A3LA.GetChar(); // Save received character but remove embedded CRLFs if ((ch != IridiumStrings.CR[0]) && (ch != IridiumStrings.LF[0])) _received[receivedLength++] = ch; // 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"); LogExpectedResponse(_received, receivedLength); return Response.Match; } } else { //Trace("Terminator string did not Match"); matchPromptPos = (ch == terminator[0]) ? 1 : 0; } break; } } } _logMsg.Clear(); _logMsg.Append(_responseMsg); if (DateTime.Now > timeoutAt) LogTimeOut(timeout); else LogUnexpectedResponse(_received, receivedLength); return ((DateTime.Now > timeoutAt) ? Response.Timeout : 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]; _received = new byte[512]; _A3LA = new Uart(serialPort, baudRate); _requestMsg = new ByteString("Host ===> IRIDIUM: "); _responseMsg = new ByteString("Host <=== IRIDIUM: "); _expectedMsg = new ByteString(" Expected Response"); _unexpectedMsg = new ByteString(" Unexpected Response"); _timeoutMsg = new ByteString("Host <=== Modem: Timeout after "); _timeUnit = new ByteString(" ms"); _logMsg = new ByteString(); _msg = new StringBuilder(); _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); } public void PowerOff() { } public void Register() { int unused; // 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 int SignalQuality() { int length; SendCommand(IridiumStrings.AT_CSQ_RQ); if (WaitForResponse(_response, out length, IridiumStrings.CSQ_RSP, IridiumStrings.OK, 120000) != 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_PUBX); // 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)); //Debug.Print(Utils.StringFromBytes(_request, _requestLength - 2) + " ===> Modem"); _A3LA.SendRawData(_request, 0, _requestLength); rsp = WaitForResponse(null, out unused, null, IridiumStrings.OK, 5000); 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); } } }