Question

I am trying to send AT commands to a modem connected via USB with C++ code. I am testing using "AT+MDN" which on this particular modem returns back its phone number and works correctly when tested via Putty which connects to its serial port. GetComPort determins the com port number that the modem is connected to and creates a handle to it. The sendCommand() method opens the port, sends the command, flushes the buffer then attempts to read the result but only receives back the command that was sent, so in this example it receives "AT+MDN". Ive tried all of the various fixes for this problem that I could find (flush the buffer, add a sleep between the write and read) but none have worked.

SerialATDT.cpp

#include "SerialATDT.h"
#include "EventSem.h"
#include "Traces.h"
#include <setupapi.h>
#include <devguid.h>
#include <regstr.h>

SerialATDT::SerialATDT(String ident) : mComPortIdentifier(ident)
{
}

SerialATDT::~SerialATDT(void)
{
}

String SerialATDT::getComPortId() 
{
  HDEVINFO hDevInfo;
  SP_DEVINFO_DATA DeviceInfoData;
  int devOffset = 0;
  DWORD propertyDataType;
  HKEY devKey;
  DWORD portNameSize;
  DWORD result;
  String comPort = "";
  BYTE friendlyName[4096];
  TCHAR devName[4096];
  TCHAR portName[4096];

  // Create a HDEVINFO with all present devices.
  hDevInfo = SetupDiGetClassDevs(&GUID_DEVCLASS_MODEM, 0, 0, DIGCF_PRESENT);
  if (hDevInfo == INVALID_HANDLE_VALUE)
  {
    TRACE_L(TEXT("\nSetupDiGetClassDevs() failed: %d\n"), GetLastError());
    return "";
  }

  // Enumerate through all devices in Set.
  DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
  while (SetupDiEnumDeviceInfo(hDevInfo, devOffset++, &DeviceInfoData))
  {

    if (!SetupDiGetDeviceRegistryProperty(hDevInfo, &DeviceInfoData, SPDRP_FRIENDLYNAME,
      &propertyDataType, friendlyName, sizeof(friendlyName), NULL))
    {
      TRACE_L(TEXT("\nSetupDiEnumDeviceInfo() failed: %d\n"), GetLastError());
      continue;
    }

    // Look for identifying info in the name
    if ( mComPortIdentifier.size() > 0 ) {
      const char *temp = strstr((const char*)friendlyName, mComPortIdentifier.c_str());

      if ( temp == 0 ) {
        continue;
      }
    }

    // Get the device name.
    if (!SetupDiGetDeviceInstanceId(hDevInfo, &DeviceInfoData, devName, MAX_PATH, NULL))
    {
      TRACE_L(TEXT("\nSetupDiGetDeviceInstanceId() failed: %d\n"), GetLastError());
      continue;
    }

    // Open the registry key associated with the device.
    devKey = SetupDiOpenDevRegKey(hDevInfo, &DeviceInfoData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);
    if (devKey == INVALID_HANDLE_VALUE)
    {
      TRACE_L(TEXT("\nSetupDiOpenDevRegKey() failed: %d\n"), GetLastError());
      continue;
    }

    // Read the PortName registry key.
    portNameSize = sizeof(portName);
    result = RegQueryValueEx(devKey, TEXT("PortName"), NULL, NULL, (LPBYTE) portName, &portNameSize);
    if(result != ERROR_SUCCESS)
    {
      TRACE_L(TEXT("\nRegQueryValueEx() failed: %d\n"), result);
      continue;
    }

    // We are not guaranteed a null terminated string from RegQueryValueEx, so explicitly NULL-terminate it.
    portName[portNameSize / sizeof(TCHAR)] = TEXT('\0');

    // Close the registry key.
    result = RegCloseKey(devKey);
    if (result != ERROR_SUCCESS)
    {
      TRACE_L(TEXT("\nRegCloseKey() failed: %d\n"), result);
      continue;
    }

    // Try to open the COM port.
    comPort = portName;
  }

  SetupDiDestroyDeviceInfoList(hDevInfo);

  return comPort;
}

bool SerialATDT::getComPort(HANDLE *hFile)
{
  String comPort = getComPortId();

  *hFile = INVALID_HANDLE_VALUE;

  if ( comPort.size() > 0 ) {
    String comPortStr;

    comPortStr.Format("\\\\.\\%s", comPort.c_str());

    *hFile = ::CreateFile( comPortStr.c_str(),
      GENERIC_READ | GENERIC_WRITE,
      0,
      NULL,
      OPEN_EXISTING,
      0,
      NULL );

    if ( *hFile == INVALID_HANDLE_VALUE ) {
      TRACE_L("AT file open error %ld", GetLastError());
    }
  }

  return *hFile != INVALID_HANDLE_VALUE;
}

bool SerialATDT::sendCommand(String command, String &response)
{
  bool retFlag = true;
  HANDLE hFile = NULL;

  response = "";

  if ( !getComPort(&hFile) ) {
    TRACE("SerialATDT-Unable to get comport");
    return false;
  }

  ::SetupComm( hFile, 2048, 2048 );

  DCB dcb = { 0 };
  dcb.DCBlength = sizeof(DCB);
  ::GetCommState( hFile, &dcb );

  dcb.BaudRate = 9600;

  // set additional parameters for 8-bit, no parity, one stop bit, no flow control
  dcb.fRtsControl     = RTS_CONTROL_DISABLE;
  dcb.ByteSize        = 8;
  dcb.Parity          = NOPARITY;
  dcb.StopBits        = ONESTOPBIT;
  dcb.fOutxCtsFlow    = FALSE;
  dcb.fOutxDsrFlow    = FALSE;
  dcb.fDtrControl     = DTR_CONTROL_DISABLE;
  dcb.fDsrSensitivity = FALSE;
  dcb.fOutX           = FALSE;
  dcb.fInX            = FALSE;

  ::SetCommState( hFile, &dcb );

  // Retrieve the timeout parameters for all read and write operations on the port. 
  COMMTIMEOUTS CommTimeouts;
  ::GetCommTimeouts (hFile, &CommTimeouts);

#define BUFFER_LENGTH 256

  // Change the COMMTIMEOUTS structure settings.
  CommTimeouts.ReadIntervalTimeout = 50;  
  CommTimeouts.ReadTotalTimeoutMultiplier = (2000/BUFFER_LENGTH);  // Only wait 2 seconds (2000ms).
  CommTimeouts.ReadTotalTimeoutConstant = 50;    
  CommTimeouts.WriteTotalTimeoutMultiplier = 50;  
  CommTimeouts.WriteTotalTimeoutConstant = 50;    

  // Set the timeout parameters for all read and write operations on the port. 
  ::SetCommTimeouts (hFile, &CommTimeouts);

  char buffer[BUFFER_LENGTH+1];
  OVERLAPPED m_ReadSync;
  unsigned error = 0;
  EventSem readWait;
  bool finished = false;


  memset(&m_ReadSync, 0, sizeof(OVERLAPPED));

  m_ReadSync.hEvent = readWait.GetHandle();
  m_ReadSync.Pointer = (void*)buffer;

  static int index = 0;

  // Edited here
  command += "\r";

  // First write the command out to the serial port
  UInt32 numBytesWritten = 0;
  UInt32 totalBytesWritten = 0;
  do {
    if ( !::WriteFile(hFile, command.c_str(), command.length(), &numBytesWritten, NULL) ) {
      TRACE("SerialATDT-sendCommand failed on sending.");
      retFlag = false;
      break;
    }

    totalBytesWritten += numBytesWritten;
  } while (totalBytesWritten < command.length() );

  ::FlushFileBuffers(hFile);

  // Read in the response
  if ( retFlag ) {
    Sleep(1000);

    while ( !finished ) {
      UInt32 numBytesRead = 0;

      buffer[0] = '\0';

      if ( !::ReadFile(hFile, buffer, BUFFER_LENGTH, &numBytesRead, &m_ReadSync ) )
        {
        if ( ( error = ::GetLastError() ) == ERROR_IO_PENDING )
          {
          error = 0;
          if ( !::GetOverlappedResult( hFile, &m_ReadSync, &numBytesRead, true ) )
            {
            error = ::GetLastError();
            }
          }
        }

      // We read something
      if ( numBytesRead > 0 ) {
        buffer[numBytesRead] = '\0';
        response += buffer;
      } else { // We timed out so just drop out
        finished = true;
      }
    }
  }

  // Remove the command from what we read in the response
  if ( response.find(command) != std::string::npos ) {
    response = response.substr(command.length());
  }

  if ( hFile != INVALID_HANDLE_VALUE ) {
    ::CloseHandle( hFile );
  }

  return retFlag;
}

and SerialATDT.h

#ifndef SERIALATDT_H
#define SERIALATDT_H

#pragma once

#include "SgiString.h"
#include "FileSystem.h"

using namespace sgi;

class SerialATDT
{
private:
  String mComPortIdentifier;

  bool getComPort(HANDLE *hFile);
  String getComPortId();

public:
  SerialATDT(String ident);
  ~SerialATDT(void);

  bool sendCommand(String command, String &response);
};

#endif
Was it helpful?

Solution

When you get the written string back, that's likely an intentional echo from modem (that's one way you can see what you typed in Putty). Have you tried to read again? Also, have you checked for different line endings on your write string. Some modems want just a linefeed "\n", some want a carriage return linefeed "\r\n". Without the right line ending, the modem may not be executing your AT command.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top