APRO CLX

Exploring the Modem Database

 

The APRO CLX modem capabilities database contains quite a bit of information in addition to the basics of establishing a data connection.  By exploring the entries in the database, you can add more functionality to your programs.  You can also support a wider set of modems by not relying on generic AT commands or responses from the modem.

 

A good starting place for exploring the modem cap database is the ExLibMdm example program.  This program will allow you to see all information about a modem in a convenient tree view.

Getting the Modem’s Responses

All known responses that a modem can generate are kept in the modem database under the Responses key.  This information is very useful for setting up data triggers or data packets to pick up exactly how a specific modem is going to respond.

 

For example, if there is no dial tone, the Supra FAX Modem 144i can respond with “NO DIALTONE”, “NO DIAL TONE” or the numeric response, "6“.  Most modems have multiple responses like this.

 

In addition, caller ID responses can be found in the Date, Time, Number, Name and Msg sections of the Responses key.

 

The following example, will load all the interesting responses into a TMemo component.  You will need to have AxMisc and AxLibMdm in your uses clause for this.  Modem information can also be accessed via the LmModem and LibModem properties of the TApxModem component (the TApxModem component will handle the loading of the modem information automatically as well), but for this example, we will get the information directly.

 

procedure TForm1.Button1Click(Sender: TObject);

var

  LibModem : TApxLibModem;

  LmModem : TLmModem;

  i : Integer;

begin

  Screen.Cursor := crHourglass;

  try

    LibModem := TApxLibModem.Create(self);

    try

 

Note: If you have installed the APRO Modem Database, a modemcap symlink will be automatically created in the /etc directory.  You can use this to access the database without having to worry about where the actual database is stored.

 

      LibModem.LibModemPath := ‘/etc/modemcap’;

 

Note: In this example, I am going to load the Supra FAX Modem 144i from the  mdmsupra file.  The modem database provides functions for locating a specific modem in the database.  The ExModem and ExLibMdm provide examples of how to locate a specific modem.   

GetModem allows you to do some pretty sophisticated things.  Leaving the first two parameters blank will cause it to prompt you for the modem to use.  If the second parameter is blank, all modems in the specified file will be loaded.

 

      i :=Libmodem.GetModem (‘mdmsupra.xml’, ‘SupraFAXModem 144i’, LmModem);

      Memo1.Lines.Clear;

      if i <> ecOK then begin

        ShowMessage(‘Modem not found, i = ' + IntToStr(i));

        Exit;

      end;

      with LmModem do begin

        Memo1.Lines.Add (‘RESPONSES’);

 

Note: What follows is a pretty repetitive section of code that extracts all the different response types out.

 

        Memo1.Lines.Add (‘OK’);

        for i := 0 to Responses.OK.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.OK[i]).Response);

        Memo1.Lines.Add (‘NEGOTIATION PROGRESS’);

        for i := 0 to Responses.NegotiationProgress.Count - 1 do

          Memo1.Lines.Add (‘  ' +

             PLmResponseData (Responses.NegotiationProgress[i]).Response);

        Memo1.Lines.Add (‘CONNECT’);

        for i := 0 to Responses.Connect.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Connect[i]).Response);

        Memo1.Lines.Add (‘ERROR’);

        for i := 0 to Responses.Error.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Error[i]).Response);

        Memo1.Lines.Add (‘NO CARRIER’);

        for i := 0 to Responses.NoCarrier.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.NoCarrier[i]).Response);

        Memo1.Lines.Add (‘NO DIAL TONE’);

        for i := 0 to Responses.NoDialTone.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.NoDialTone[i]).Response);

        Memo1.Lines.Add (‘BUSY’);

        for i := 0 to Responses.Busy.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Busy[i]).Response);

        Memo1.Lines.Add (‘NO ANSWER’);

        for i := 0 to Responses.NoAnswer.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.NoAnswer[i]).Response);

        Memo1.Lines.Add (‘RING’);

        for i := 0 to Responses.Ring.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Ring[i]).Response);

        Memo1.Lines.Add (‘RING DURATION’);

        for i := 0 to Responses.RingDuration.Count - 1 do

          Memo1.Lines.Add (‘  ' +

                           PLmResponseData Responses.RingDuration[i]).Response);

        Memo1.Lines.Add (‘RING BREAK’);

        for i := 0 to Responses.RingBreak.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.RingBreak[i]).Response);

        Memo1.Lines.Add (‘DATE’);

        for i := 0 to Responses.Date.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Date[i]).Response);

        Memo1.Lines.Add (‘TIME’);

        for i := 0 to Responses.Time.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Time[i]).Response);

        Memo1.Lines.Add (‘NUMBER’);

        for i := 0 to Responses.Number.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Number[i]).Response);

        Memo1.Lines.Add (‘NAME’);

        for i := 0 to Responses.Name.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Name[i]).Response);

        Memo1.Lines.Add (‘MESSAGE’);

        for i := 0 to Responses.Msg.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Msg[i]).Response);

        Memo1.Lines.Add (‘SINGLE RING’);

        for i := 0 to Responses.SingleRing.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.SingleRing[i]).Response);

        Memo1.Lines.Add (‘DOUBLE RING’);

        for i := 0 to Responses.DoubleRing.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.DoubleRing[i]).Response);

        Memo1.Lines.Add (‘TRIPLE RING’);

        for i := 0 to Responses.TripleRing.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.TripleRing[i]).Response);

        Memo1.Lines.Add (‘VOICE’);

        for i := 0 to Responses.Voice.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Voice[i]).Response);

        Memo1.Lines.Add (‘FAX’);

        for i := 0 to Responses.Fax.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Fax[i]).Response);

        Memo1.Lines.Add (‘DATA’);

        for i := 0 to Responses.Data.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Data[i]).Response);

        Memo1.Lines.Add (‘OTHER’);

        for i := 0 to Responses.Other.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmResponseData (Responses.Other[i]).Response);

      end;

    finally

      LibModem.Free;

    end;

  finally

    Screen.Cursor := crDefault;

  end;

end;

 

For most of the responses, the value obtained is the actual response returned by the modem.  Carriage returns (#$0d) are indicated by a “<cr>" and line feeds (#$0a) are indicated by a “<lf>”. 

The only exception to this is Connect response.  The Connect response may also return a “<StandardConnect>” value.  This value indicates that a normal looking connect response can be returned from the modem.  This is defined as the word “CONNECT” followed by an optional baud rate and then error correction and data compression tags separated by slashes.  The response can be preceded or trailed by carriage returns and line feeds and whitespace may appear between any element.

 

Examples of standard connections are:

<cr><lf>CONNECT<cr><lf>

<cr><lf>CONNECT 9600<cr><lf>

<cr><lf>CONNECT 1200/NONE<cr><lf>

<cr><lf>CONNECT 4800/ARQ/LAPM/V42BIS<cr><lf>

Using Voice

In addition to the responses a modem can send, useful voice information is also stored in the modem database.  Accessing this is similar to accessing the responses.

 

procedure TForm1.Button2Click(Sender: TObject);

var

  LibModem : TApxLibModem;

  LmModem : TLmModem;

  i : Integer;

begin

  Screen.Cursor := crHourglass;

  try

    LibModem := TApxLibModem.Create(self);

    try

      LibModem.LibModemPath := ‘/etc/modemcap’;

      i :=Libmodem.GetModem (‘mdm3com.xml’, ‘U.S. Robotics 56K Voice EXT’, LmModem);

      Memo1.Lines.Clear;

      if i <> ecOK then begin

        ShowMessage(‘Modem not found, i = ' + IntToStr(i));

        Exit;

      end;

      with LmModem do begin

        Memo1.Lines.Add (‘VOICE SETTINGS’);

        Memo1.Lines.Add (‘  Voice Profile = ' + Voice.VoiceProfile);

        Memo1.Lines.Add (‘  Handset Close Delay = ' + IntToStr (Voice.HandsetCloseDelay));

        Memo1.Lines.Add (‘  Speaker Phone Specs = ' + Voice.SpeakerPhoneSpecs);

        Memo1.Lines.Add (‘  Abort Play = ' + Voice.AbortPlay);

        Memo1.Lines.Add (‘  Caller ID OutSide = ' + Voice.CallerIDOutSide);

        Memo1.Lines.Add (‘  Caller ID Private = ' + Voice.CallerIDPrivate);

        Memo1.Lines.Add (‘  Terminate Play = ' + Voice.TerminatePlay);

        Memo1.Lines.Add (‘  Terminate Record = ' + Voice.TerminateRecord);

        Memo1.Lines.Add (‘  Voice Manufacturer ID = ' + Voice.VoiceManufacturerID);

        Memo1.Lines.Add (‘  Voice Product ID Wave In = ' + Voice.VoiceProductIDWaveIn);

        Memo1.Lines.Add (‘  Voice Product ID Wave Out = ' + Voice.VoiceProductIDWaveOut);

        Memo1.Lines.Add (‘  Voice Switch Features = ' + Voice.VoiceSwitchFeatures);

        Memo1.Lines.Add (‘  Voice Baud Rate = ' + IntToStr (Voice.VoiceBaudRate));

        Memo1.Lines.Add (‘  Voice Mixer Mid = ' + Voice.VoiceMixerMid);

        Memo1.Lines.Add (‘  Voice Mixer Pid = ' + Voice.VoiceMixerPid);

        Memo1.Lines.Add (‘  Voice Mixer Line ID = ' + Voice.VoiceMixerLineID);

        Memo1.Lines.Add (‘CLOSE HANDSET’);

        for i := 0 to Voice.CloseHandset.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.CloseHandset[i]).Command);

        Memo1.Lines.Add (‘ENABLE CALLER ID’);

        for i := 0 to Voice.EnableCallerID.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.EnableCallerID[i]).Command);

        Memo1.Lines.Add (‘ENABLE DISTINCTIVE RING’);

        for i := 0 to Voice.EnableDistinctiveRing.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.EnableDistinctiveRing[i]).Command);

        Memo1.Lines.Add (‘GENERATE DIGIT’);

        for i := 0 to Voice.GenerateDigit.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.GenerateDigit[i]).Command);

        Memo1.Lines.Add (‘HANDSET PLAY FORMAT’);

        for i := 0 to Voice.HandsetPlayFormat.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.HandsetPlayFormat[i]).Command);

        Memo1.Lines.Add (‘HANDSET RECORD FORMAT’);

        for i := 0 to Voice.HandsetRecordFormat.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.HandsetRecordFormat[i]).Command);

        Memo1.Lines.Add (‘LINE SET PLAY FORMAT’);

        for i := 0 to Voice.LineSetPlayFormat.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.LineSetPlayFormat[i]).Command);

        Memo1.Lines.Add (‘LINE SET RECORD FORMAT’);

        for i := 0 to Voice.LineSetRecordFormat.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.LineSetRecordFormat[i]).Command);

        Memo1.Lines.Add (‘OPEN HANDSET’);

        for i := 0 to Voice.OpenHandset.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.OpenHandset[i]).Command);

        Memo1.Lines.Add (‘SPEAKER PHONE DISABLE’);

        for i := 0 to Voice.SpeakerPhoneDisable.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.SpeakerPhoneDisable[i]).Command);

        Memo1.Lines.Add (‘SPEAKER PHONE ENABLE’);

        for i := 0 to Voice.SpeakerPhoneEnable.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.SpeakerPhoneEnable[i]).Command);

        Memo1.Lines.Add (‘SPEAKER PHONE MUTE’);

        for i := 0 to Voice.SpeakerPhoneMute.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.SpeakerPhoneMute[i]).Command);

        Memo1.Lines.Add (‘SPEAKER PHONE SET VOLUME GAIN’);

        for i := 0 to Voice.SpeakerPhoneSetVolumeGain.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.SpeakerPhoneSetVolumeGain[i]).Command);

        Memo1.Lines.Add (‘SPEAKER PHONE UNMUTE’);

        for i := 0 to Voice.SpeakerPhoneUnMute.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.SpeakerPhoneUnMute[i]).Command);

        Memo1.Lines.Add (‘START PLAY’);

        for i := 0 to Voice.StartPlay.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.StartPlay[i]).Command);

        Memo1.Lines.Add (‘START RECORD’);

        for i := 0 to Voice.StartRecord.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.StartRecord[i]).Command);

        Memo1.Lines.Add (‘STOP PLAY’);

        for i := 0 to Voice.StopPlay.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.StopPlay[i]).Command);

        Memo1.Lines.Add (‘STOP RECORD’);

        for i := 0 to Voice.StopRecord.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.StopRecord[i]).Command);

        Memo1.Lines.Add (‘VOICE ANSWER’);

        for i := 0 to Voice.VoiceAnswer.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.VoiceAnswer[i]).Command);

        Memo1.Lines.Add (‘VOICE DIAL NUMBER SETUP’);

        for i := 0 to Voice.VoiceDialNumberSetup.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.VoiceDialNumberSetup[i]).Command);

        Memo1.Lines.Add (‘VOICE TO DATA ANSWER’);

        for i := 0 to Voice.VoiceToDataAnswer.Count - 1 do

          Memo1.Lines.Add (‘  ' + PLmModemCommand (Voice.VoiceToDataAnswer[i]).Command);

        Memo1.Lines.Add (‘WAVE DRIVER’);

        Memo1.Lines.Add (‘  Baud Rate = '  + Voice.WaveDriver.BaudRate);

        Memo1.Lines.Add (‘  Wave Hardware ID = '  + Voice.WaveDriver.WaveHardwareID);

        Memo1.Lines.Add (‘  Wave Devices = '  + Voice.WaveDriver.WaveDevices);

        Memo1.Lines.Add (‘  Lower Mid = '  + Voice.WaveDriver.LowerMid);

        Memo1.Lines.Add (‘  Lower Wave In Pid = '  + Voice.WaveDriver.LowerWaveInPid);

        Memo1.Lines.Add (‘  Lower Wave Out Pid = '  + Voice.WaveDriver.LowerWaveOutPid);

        Memo1.Lines.Add (‘  Wave Out Mixer Dest = '  + Voice.WaveDriver.WaveOutMixerDest);

        Memo1.Lines.Add (‘  Wave Out Mixer Source = '  + Voice.WaveDriver.WaveOutMixerSource);

        Memo1.Lines.Add (‘  Wave In Mixer Dest = '  + Voice.WaveDriver.WaveInMixerDest);

        Memo1.Lines.Add (‘  Wave In Mixer Source = '  + Voice.WaveDriver.WaveInMixerSource);

        Memo1.Lines.Add (‘WAVE FORMAT’);

        for i := 0 to Voice.WaveDriver.WaveFormat.Count - 1 do begin

          Memo1.Lines.Add (‘  Speed ' + IntToStr (i + 1) + ' = ' +

                PLmWaveFormat (Voice.WaveDriver.WaveFormat[i]).Speed);

          Memo1.Lines.Add (‘  ChipSet ' + IntToStr (i + 1) + ' = ' +

                PLmWaveFormat (Voice.WaveDriver.WaveFormat[i]).ChipSet);

          Memo1.Lines.Add (‘  Sample Size ' + IntToStr (i + 1) + ' = ' +

                PLmWaveFormat (Voice.WaveDriver.WaveFormat[i]).SampleSize);

      end;

      end;

    finally

      LibModem.Free;

    end;

  finally

    Screen.Cursor := crDefault;

  end;

end;

 

As with the responses, <cr> indicates a carriage return and <lf> indicates a line feed.  In addition <hXX> indicates a raw hex value (where XX is the hex value).  With the Generate Digit command, <Digit> is the digit that you want to generate.  With Speaker phone set volume gain, Gain and Vol specify the gain and the volume.

 

Once you have the commands loaded, sending them to the modem is pretty straight forward.  Use the SendCommand method of the TApxModem component.

 

      with LmModem do

        if not ApxModem1.SendCommand (

                  PLmModemCommand (Voice.EnableCallerID[i]).Command) then begin

          ShowMessage (‘Error or Timeout while enabling caller id.’);

          Exit;

        end;

 

For caller ID, send the appropriate commands to the modem and wait for an OK response (as in the above example).  Set up either data triggers or data packets to look for the caller ID information to be returned from the modem (you can find out what these are by looking in the modem responses).

 

For voice applications, the WaveFormat record is needed to determine what format the wave data should be in.  This record contains three items, ChipSet, Speed, and SampleSize.  The ChipSet can be  either “None”, “Rockwell ADPCM”, “IMA ADPCM”, “Linear PCM”, “Rockwell ADPCM w/o fixed gain increase”, “u-law (G.711)”, “A-law (G.711)”, or “Unknown”.  Speeds are usally 4800, 7200 or 8000.  Sample sizes can be either 4 or 8.  While most modems only support a single wave format, some can support multiple formats.

 

To send a wave file to the modem, Use the StartPlay commands.  Once you get the OK response, send the raw wave data without the headers without the headers. 

 

When you call StartRecord, after you get the OK response, raw wave data will be returned to you from the port.  This data will be returned without headers, so you will need to attach headers to the data before you save it.  As you may expect, StopPlay and StopRecord will allow you to stop playing or recording.

Closing Thoughts

There are many other entries that may prove to be useful in the modem database.   For example, the modem’s model number is the same as the plug and play ID.   Non standard hardware settings are in the Hardware section, non standard faxing settings can be found in the fax section. 

Knowing just what is in the modem information database and how to get at the information can help you add new capabilities and more robustness to your applications.

 

This site is not affiliated, endorsed, or otherwise associated with the entity formerly known as TurboPower Software. The owners and maintainers of Aprozilla.com were merely meager employees of the aforementioned organization, providing this site out of the pure goodness of their collective hearts. SourceForge.net Logo

Last updated: July 22, 2003.