{******************************************************************************} { CnPack For Delphi/C++Builder } { 中国人自己的开放源码第三方开发包 } { (C)Copyright 2001-2018 CnPack 开发组 } { ------------------------------------ } { } { 本开发包是开源的自由软件,您可以遵照 CnPack 的发布协议来修 } { 改和重新发布这一程序。 } { } { 发布这一开发包的目的是希望它有用,但没有任何担保。甚至没有 } { 适合特定目的而隐含的担保。更详细的情况请参阅 CnPack 发布协议。 } { } { 您应该已经和开发包一起收到一份 CnPack 发布协议的副本。如果 } { 还没有,可访问我们的网站: } { } { 网站地址:http://www.cnpack.org } { 电子邮件:master@cnpack.org } { } {******************************************************************************} unit CnModem; {* |
================================================================================
* 软件名称:网络通讯组件包
* 单元名称:CnModem标准调制解调器组件单元
* 单元作者:周劲羽 (zjy@cnpack.org)
* 备    注:CnModem组件由CnRS232串口通讯组件派生而来
*           提供利用AT命令通过串口直接操作调制解调器的功能
* 开发平台:PWin98SE + Delphi 5.0
* 兼容测试:PWin9X/2000/XP + Delphi 5/6
* 本 地 化:该单元中的字符串均符合本地化处理方式
* 单元标识:$Id$
* 修改记录:2002.04.08 V1.0
*                创建单元
*                增加注释
================================================================================
|
} interface {$I CnPack.inc} uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, CnConsts, CnNetConsts, CnRS232, IniFiles; type //------------------------------------------------------------------------------ // 标准调制解调器组件 //------------------------------------------------------------------------------ { TCnModem } TDialResult = (drConnect, drOpenCommFail, drNoModem, drNoDialtone, drBusy, drNoAnswer, drNoCarrier, drTimeout, drUnknow); {* Modem拨号结果类型 |
     drConnect:         - 连接成功
     drOpenCommFail:    - 打开串口失败
     drNoModem:         - 没有检测到Modem
     drNoDialtone:      - 无拨号音
     drBusy:            - 检测到忙信号
     drNoAnswer:        - 无应答信号
     drNoCarrier:       - 没有检测到载波信号
     drTimeout:         - 超时错
     drUnknow:          - 未知错误
   |
} TATResult = (arOk, arConnect, arRing, arNoCarrier, arError, arNoDialtone, arBusy, arNoAnswer, arTimeout, arUnknow); {* AT命令执行结果类型 |
     arOk:              - 成功
     arConnect:         - 已连接
     arRing:            - 振铃信号
     arNoCarrier:       - 没有检测到载波信号
     arError:           - 执行错误
     arNoDialtone:      - 无拨号音
     arBusy:            - 检测到忙信号
     arNoAnswer:        - 无应答信号
     arTimeout:         - 超时错
     arUnknow:          - 未知错误
   |
} TModemVolume = (mvLowest, mvLow, mvMiddle, mvHigh); {* Modem 音量 |
     mvLowest:          - 最小音量
     mvLow:             - 小音量
     mvMiddle:          - 中等音量
     mvHigh:            - 大音量
   |
} TRingEvent = procedure(Sender: TObject; var Answer: Boolean) of object; {* 接收到振铃事件,变量参数Answer决定是否应答} TConnectEvent = procedure(Sender: TObject; Rate: Integer) of object; {* 已连接成功事件,参数Rate为连接速度} TInvalidCommandEvent = procedure(Sender: TObject; const Command: string) of object; {* 非法的AT命令事件,参数为出错的命令行} TModemState = (msUnknow, msOffline, msOnline, msOnlineCommand, msConnecting); {* 当前Modem状态类型 |
     msUnknow:          - 未知状态
     msOffline:         - 离线状态
     msOnline:          - 在线状态
     msOnlineCommand:   - 在线命令状态
     msConnecting:      - 正在连接状态
   |
} TStateChangeEvent = procedure(Sender: TObject; State: TModemState) of object; {* 当前Modem状态改变事件} TCnModem = class(TCnRS232) {* 标准调制解调器通讯组件 |
     * 组件由TCnRS232派生而来,通过向串口发送AT命令来控制标准 Modem 通讯。
     * 使用时可直接调用 Dial 方法进行拨号连接,拨号完成返回执行结果。
     * 当 Modem 检测到振铃信号时,产生 OnRing 事件。
     * Hangup 方法可挂机拆除连接,通讯时如果连接中断,将产生 OnDisConnect 事件。
     * 连接成功后通过使用继承来的方法 WriteCommData 向 Modem 发送数据。
     * 只有当 Modem 处于在线状态时,收到数据才会产生 OnReceiveData 事件。
   |
} private { Private declarations } FCheckDialtone: Boolean; FCheckBusy: Boolean; FAutoAnswer: Boolean; FVolume: TModemVolume; FWaitEscapeTime: Integer; FWaitDialtoneTime: Integer; FWaitCarrierTime: Integer; FInitATCommand: string; FModemState: TModemState; FOnConnect: TConnectEvent; FOnDisConnect: TNotifyEvent; FOnRing: TRingEvent; FOnInvalidCommand: TInvalidCommandEvent; FOnStateChange: TStateChangeEvent; FWaitATResult: Boolean; FATResult: string; FConnectRate: Integer; procedure SetAutoAnswer(const Value: Boolean); procedure SetVolume(const Value: TModemVolume); procedure SetInitATCommand(const Value: string); procedure SetWaitCarrierTime(const Value: Integer); procedure SetWaitDialtoneTime(const Value: Integer); procedure SetWaitEscapeTime(const Value: Integer); procedure SetCheckBusy(const Value: Boolean); procedure SetCheckDialtone(const Value: Boolean); procedure SetModemState(const Value: TModemState); function WaitATResult(Delay: Cardinal): string; function SendATOk(AT: string; Delay: Cardinal = 200): Boolean; function StrToIntEx(const Str: string): Integer; protected { Protected declarations } procedure GetComponentInfo(var AName, Author, Email, Comment: string); override; function CommOpened: Boolean; function OpenComm: Boolean; procedure Changed; procedure ReceiveData(Buffer: PAnsiChar; BufferLength: WORD); override; procedure _SendDataEmpty; override; procedure Ring; virtual; procedure Connect(Rate: Integer); virtual; procedure DisConnect; virtual; procedure InvalidCommand(const Command: string); virtual; procedure Escape; procedure Resume; function Answer: TDialResult; property ModemState: TModemState read FModemState write SetModemState; public { Public declarations } procedure Assign(Source: TPersistent); override; {* 对象赋值方式} constructor Create(AOwner: TComponent); override; destructor Destroy; override; function InitModem: Boolean; {* 初始化Modem,一般不需要手工调用} function Dial(const Number: string): TDialResult; {* 拨号方法,参数为对方电话机号码} procedure WriteATCommand(const Command: string; Return: Boolean = True); {* 写AT命令方法,允许用户手工向 Modem 发送AT命令。 |
       Command: string  - AT命令行
       Return: Boolean  - 是否自动在命令行末尾增加回车,默认为是
     |
} procedure Hangup; {* 挂机拆除当前连接} procedure ReadFromIni(Ini: TCustomIniFile; const Section: string); override; procedure WriteToIni(Ini: TCustomIniFile; const Section: string); override; property State: TModemState read FModemState; {* 当前的 Modem 状态,运行期只读属性} property ConnectRate: Integer read FConnectRate; {* 当前的连接速度,运行期只读属性} published { Published declarations } property CheckDialtone: Boolean read FCheckDialtone write SetCheckDialtone default True; {* 拨号前是否检测拨号音} property CheckBusy: Boolean read FCheckBusy write SetCheckBusy default True; {* 拨号前是否检测忙信号} property AutoAnswer: Boolean read FAutoAnswer write SetAutoAnswer default False; {* 是否允许自动应答。如果允许,不管在 OnRing 事件是否允许,都将自动应答} property Volume: TModemVolume read FVolume write SetVolume default mvMiddle; {* Modem 音量} property WaitDialtoneTime: Integer read FWaitDialtoneTime write SetWaitDialtoneTime default 2; {* 等待拨号音的最长时间,单位为秒} property WaitCarrierTime: Integer read FWaitCarrierTime write SetWaitCarrierTime default 50; {* 等待检测载波的最长时间,单位为秒} property WaitEscapeTime: Integer read FWaitEscapeTime write SetWaitEscapeTime default 50; {* 切换到在线命令状态的等待时间,单位为 20 毫秒} property InitATCommand: string read FInitATCommand write SetInitATCommand; {* 用于初始化 Modem 的额外AT命令,仅建议高级用户使用} property OnRing: TRingEvent read FOnRing write FOnRing; {* 振铃事件} property OnConnect: TConnectEvent read FOnConnect write FOnConnect; {* 连接成功事件} property OnInvalidCommand: TInvalidCommandEvent read FOnInvalidCommand write FOnInvalidCommand; {* 检测到无效的AT命令事件} property OnDisConnect: TNotifyEvent read FOnDisConnect write FOnDisConnect; {* 连接中断事件} property OnStateChange: TStateChangeEvent read FOnStateChange write FOnStateChange; {* Modem 状态改变事件} end; implementation //------------------------------------------------------------------------------ // 标准调制解调器组件 //------------------------------------------------------------------------------ { TCnModem } // 对象赋值方法 procedure TCnModem.Assign(Source: TPersistent); begin if Source is TCnModem then begin FCheckDialtone := TCnModem(Source).FCheckDialtone; FCheckBusy := TCnModem(Source).FCheckBusy; FAutoAnswer := TCnModem(Source).FAutoAnswer; FWaitEscapeTime := TCnModem(Source).FWaitEscapeTime; FWaitDialtoneTime := TCnModem(Source).FWaitDialtoneTime; FWaitCarrierTime := TCnModem(Source).FWaitCarrierTime; FInitATCommand := TCnModem(Source).FInitATCommand; end; inherited; end; // 初始化 constructor TCnModem.Create(AOwner: TComponent); begin inherited; FCheckDialtone := True; FCheckBusy := True; FAutoAnswer := False; FVolume := mvMiddle; FWaitDialtoneTime := 2; FWaitCarrierTime := 50; FWaitEscapeTime := 50; FInitATCommand := ''; FModemState := msOffline; FWaitATResult := False; FATResult := ''; FConnectRate := 0; CommConfig.Outx_CtsFlow := True; CommConfig.Outx_DsrFlow := True; end; // 释放 destructor TCnModem.Destroy; begin Hangup; inherited; end; // 通讯状态 function TCnModem.CommOpened: Boolean; begin Result := Handle <> 0; end; // 打开串口,返回成功标记 function TCnModem.OpenComm: Boolean; begin Result := CommOpened; if not Result then begin try StartComm; Result := True; except Exit; end; end; end; // 属性已变更 procedure TCnModem.Changed; begin if (ComponentState * [csDesigning, csLoading, csDestroying] = []) and CommOpened then InitModem; end; // 发送数据缓冲区空 procedure TCnModem._SendDataEmpty; begin if ModemState = msOnline then // 仅在离线状态下产生事件 inherited; end; // 字符串转换为整数 function TCnModem.StrToIntEx(const Str: string): Integer; var SInt: string; i: Integer; begin SInt := ''; for i := 1 to Length(Str) do if {$IFDEF UNICODE}CharInSet(Str[i], ['0'..'9']){$ELSE}Str[i] in ['0'..'9']{$ENDIF} then // 仅取数字字符 SInt := SInt + Str[i]; if SInt <> '' then Result := StrToInt(SInt) else Result := 0; end; // 发送AT命令到串口 procedure TCnModem.WriteATCommand(const Command: string; Return: Boolean); var s: AnsiString; begin if (csDesigning in ComponentState) or not CommOpened then Exit; if Return then s := {$IFDEF UNICODE}AnsiString{$ENDIF}(Command) + #13 else s := {$IFDEF UNICODE}AnsiString{$ENDIF}(Command); WriteCommData(PAnsiChar(s), Length(s)); end; // 等待一条AT命令执行结果 function TCnModem.WaitATResult(Delay: Cardinal): string; var Tick: Cardinal; begin FWaitATResult := True; try FATResult := ''; Tick := GetTickCount; while (GetTickCount - Tick < Delay) and (FATResult = '') do Application.HandleMessage; Result := FATResult; FATResult := ''; finally FWaitATResult := False; end; end; // 发送一条AT命令,返回是否成功 function TCnModem.SendATOk(AT: string; Delay: Cardinal): Boolean; var i, j: Integer; s: string; begin Result := False; for i := 0 to 2 do begin WriteATCommand(AT); for j := 0 to 2 do begin s := Trim(UpperCase(WaitATResult(Delay))); if Pos('OK', s) > 0 then begin Result := True; Exit; end else if Pos('ERROR', s) > 0 then begin InvalidCommand(AT); Exit; end; end; end; end; // 接收到数据 procedure TCnModem.ReceiveData(Buffer: PAnsiChar; BufferLength: WORD); var s: AnsiString; begin if FWaitATResult then // 正在等待AT命令执行结果 begin FATResult := {$IFDEF UNICODE}String{$ENDIF}(Buffer); Exit; end; s := Buffer; s := {$IFDEF UNICODE}AnsiString{$ENDIF}(Trim(UpperCase({$IFDEF UNICODE}String{$ENDIF}(s)))); if (ModemState in [msOffline, msOnlineCommand, msConnecting]) and (s = 'RING') then Ring // 振铃信号 else if (ModemState = msOnline) and (s = 'NO CARRIER') then DisConnect // 载波丢失 else inherited; end; // 拨号 function TCnModem.Dial(const Number: string): TDialResult; var s: string; begin if not OpenComm then begin Result := drOpenCommFail; Exit; end; Result := drNoModem; if InitModem then begin WriteATCommand('ATD' + Number); ModemState := msConnecting; s := Trim(UpperCase(WaitATResult(Round(WaitCarrierTime * 1000 * 1.2)))); if Pos('CONNECT', s) > 0 then begin Result := drConnect; FConnectRate := StrToIntEx(s); ModemState := msOnline; Exit; end; if Pos('NO DIALTONE', s) > 0 then Result := drNoDialtone else if Pos('BUSY', s) > 0 then Result := drBusy else if Pos('NO CARRIER', s) > 0 then Result := drNoCarrier else if Pos('NO ANSWER', s) > 0 then Result := drNoAnswer else if s = '' then Result := drTimeout else Result := drUnknow; ModemState := msOffline; end; end; // 应答 function TCnModem.Answer: TDialResult; var s: string; begin Result := drUnknow; if CommOpened and (ModemState = msOffline) then begin WriteATCommand('ATA'); ModemState := msConnecting; s := Trim(UpperCase(WaitATResult(Round(WaitCarrierTime * 1000 * 1.2)))); if Pos('CONNECT', s) > 0 then begin FConnectRate := StrToIntEx(s); ModemState := msOnline; Connect(FConnectRate); Result := drConnect; Exit; end; if Pos('NO DIALTONE', s) > 0 then Result := drNoDialtone else if Pos('BUSY', s) > 0 then Result := drBusy else if Pos('NO CARRIER', s) > 0 then Result := drNoCarrier else if Pos('NO ANSWER', s) > 0 then Result := drNoAnswer else if s = '' then Result := drTimeout else Result := drUnknow; ModemState := msOffline; end; end; // 切换到在线命令状态 procedure TCnModem.Escape; var Tick: Integer; begin if CommOpened and (ModemState = msOnline) then begin Tick := Round(FWaitEscapeTime * 20 * 1.3); Sleep(Tick); WriteATCommand('+++', False); Sleep(Tick); ModemState := msOnlineCommand; end; end; // 回到在线状态 procedure TCnModem.Resume; begin if CommOpened and (ModemState = msOnlineCommand) then begin if SendATOk('ATO') then ModemState := msOnline else Hangup; end; end; // 挂机 procedure TCnModem.Hangup; begin if CommOpened then begin Escape; WriteATCommand('ATH'); Sleep(1000); ModemState := msOffline; StopComm; end; end; // 初始化 Modem function TCnModem.InitModem: Boolean; const AutoAnswers: array[Boolean] of Integer = (0, 1); Checks: array[Boolean, Boolean] of Integer = ((0, 2), (3, 4)); begin Result := False; if not OpenComm then Exit; if ModemState <> msOffline then Hangup; if not SendATOk('ATQ0E0V1') then Exit; //命令不回显,以字符形式显示结果码 if not SendATOk('ATX' + IntToStr(Checks[CheckDialtone, CheckBusy])) then Exit; if not SendATOk('ATL' + IntToStr(Ord(FVolume))) then Exit; if not SendATOk('ATS0=' + IntToStr(AutoAnswers[AutoAnswer])) then Exit; if not SendATOk('ATS6=' + IntToStr(WaitDialtoneTime)) then Exit; if not SendATOk('ATS7=' + IntToStr(WaitCarrierTime)) then Exit; if not SendATOk('ATS12=' + IntToStr(WaitEscapeTime)) then Exit; Result := True; if InitATCommand <> '' then SendATOk(InitATCommand); end; // 非法AT命令 procedure TCnModem.InvalidCommand(const Command: string); begin if Assigned(FOnInvalidCommand) then FOnInvalidCommand(Self, Command); end; // 已连接 procedure TCnModem.Connect(Rate: Integer); begin if Assigned(FOnConnect) then FOnConnect(Self, Rate); end; // 连接中断 procedure TCnModem.DisConnect; begin if Assigned(FOnDisConnect) then FOnDisConnect(Self); end; // 振铃事件 procedure TCnModem.Ring; var Ans: Boolean; begin Ans := True; if Assigned(FOnRing) then FOnRing(Self, Ans); if not AutoAnswer and Ans then Answer; end; // 设置Modem状态 procedure TCnModem.SetModemState(const Value: TModemState); begin if FModemState <> Value then begin FModemState := Value; if Assigned(FOnStateChange) then FOnStateChange(Self, FModemState); end; end; // 设置自动应答 procedure TCnModem.SetAutoAnswer(const Value: Boolean); begin if FAutoAnswer <> Value then begin FAutoAnswer := Value; Changed; end; end; // 设置音量 procedure TCnModem.SetVolume(const Value: TModemVolume); begin if FVolume <> Value then begin FVolume := Value; Changed; end; end; // 设置初始化AT命令 procedure TCnModem.SetInitATCommand(const Value: string); begin if FInitATCommand <> Value then begin FInitATCommand := UpperCase(Trim(Value)); if Pos('AT', FInitATCommand) <> 1 then FInitATCommand := 'AT' + FInitATCommand; if FInitATCommand = 'AT' then FInitATCommand := ''; Changed; end; end; // 设置等待载波时间 procedure TCnModem.SetWaitCarrierTime(const Value: Integer); begin if FWaitCarrierTime <> Value then begin FWaitCarrierTime := Value; Changed; end; end; // 设置等待拨号音时间 procedure TCnModem.SetWaitDialtoneTime(const Value: Integer); begin if FWaitDialtoneTime <> Value then begin FWaitDialtoneTime := Value; Changed; end; end; // 设置切换到在线命令状态的等待时间 procedure TCnModem.SetWaitEscapeTime(const Value: Integer); begin if FWaitEscapeTime <> Value then begin FWaitEscapeTime := Value; Changed; end; end; // 设置检测忙音 procedure TCnModem.SetCheckBusy(const Value: Boolean); begin if FCheckBusy <> Value then begin FCheckBusy := Value; Changed; end; end; // 设置检测拨号音 procedure TCnModem.SetCheckDialtone(const Value: Boolean); begin if FCheckDialtone <> Value then begin FCheckDialtone := Value; Changed; end; end; const csCheckDialtone = 'CheckDialtone'; csCheckBusy = 'CheckBusy'; csAutoAnswer = 'AutoAnswer'; csWaitEscapeTime = 'WaitEscapeTime'; csWaitDialtoneTime = 'WaitDialtoneTime'; csWaitCarrierTime = 'WaitCarrierTime'; csInitATCommand = 'InitATCommand'; // 从INI中读参数 procedure TCnModem.ReadFromIni(Ini: TCustomIniFile; const Section: string); begin inherited; FCheckDialtone := Ini.ReadBool(Section, csCheckDialtone, FCheckDialtone); FCheckBusy := Ini.ReadBool(Section, csCheckBusy, FCheckBusy); FAutoAnswer := Ini.ReadBool(Section, csAutoAnswer, FAutoAnswer); FWaitEscapeTime := Ini.ReadInteger(Section, csWaitEscapeTime, FWaitEscapeTime); FWaitDialtoneTime := Ini.ReadInteger(Section, csWaitDialtoneTime, FWaitDialtoneTime); FWaitCarrierTime := Ini.ReadInteger(Section, csWaitCarrierTime, FWaitCarrierTime); FInitATCommand := Ini.ReadString(Section, csInitATCommand, FInitATCommand); FInitATCommand := UpperCase(Trim(FInitATCommand)); if Pos('AT', FInitATCommand) <> 1 then FInitATCommand := 'AT' + FInitATCommand; if FInitATCommand = 'AT' then FInitATCommand := ''; end; // 写参数到INI procedure TCnModem.WriteToIni(Ini: TCustomIniFile; const Section: string); begin inherited; Ini.WriteBool(Section, csCheckDialtone, FCheckDialtone); Ini.WriteBool(Section, csCheckBusy, FCheckBusy); Ini.WriteBool(Section, csAutoAnswer, FAutoAnswer); Ini.WriteInteger(Section, csWaitEscapeTime, FWaitEscapeTime); Ini.WriteInteger(Section, csWaitDialtoneTime, FWaitDialtoneTime); Ini.WriteInteger(Section, csWaitCarrierTime, FWaitCarrierTime); Ini.WriteString(Section, csInitATCommand, FInitATCommand); end; // 取组件注释 procedure TCnModem.GetComponentInfo(var AName, Author, Email, Comment: string); begin AName := SCnModemName; Author := SCnPack_Zjy; Email := SCnPack_ZjyEmail; Comment := SCnModemComment; end; end.