Telefonieren mit Access und Windows TAPI

27. Mai 2015

Hat man eine Datenbank mit Kontaktdaten (Adressen und Telefonnummern), so liegt der Wunsch nahe, dass man per Klick direkt aus der Datenbank telefonieren kann sowie bei eingehenden Telefonanrufen in der Datenbank automatisch den entsprechenden Kontakt (Kunden) angezeigt bekommt.

Zur Realisierung dieser beiden essentiellen Funktionen braucht man nicht mehr als eine TAPI-fähige Telefonanlage und das Windows TAPI.

Die benötigten Funktionen befinden sich in der Microsoft TAPI 3.0 Type Library, die man über die Verweise einbinden muss.

Außerdem deklariert man folgende Objekte und Konstanten:

Public oTapi As TAPI
Public oAddress As ITAddress

Public Const CS_CONNECTED = 2
Public Const CS_OFFERING = 4

TAPI-Devices

Jeder Benutzer muss eines der auf seinem Rechner vorhandenen TAPI-Devices auswählen, dessen Name in den Einstellungen des Benutzers gespeichert wird.

Zur Auswahl werden beim Start der Datenbank die vorhandenen TAPI-Devices ermittelt und in ein globales Array TapiDevices gespeichert (siehe unten: Initialisierung beim Start der Datenbank).

Event handler für ankommende Anrufe

Es wird ein leeres Formular TAPI definiert, welches beim Start der Datenbank mit der Eigenschaft Hidden=True geöffnet wird und welches lediglich die Aufgabe hat, in seinem Modul das Objekt zu beherbergen, welches mittels der Eigenschaft WithEvents die TAPI-Ereignisse - hier: die eingehenden Anrufe - behandelt. Im Formularmodul wird also deklariert:

Private WithEvents oTapiWithEvents As TAPI

Initialisierung beim Start der Datenbank

Dieser Event handler muss nun beim Starten der Datenbank - bzw. beim Öffnen des Formulars TAPI - initialisiert werden.

Dazu muss zunächst festgestellt werden, welches TAPI-Device der aktuelle Benutzer verwendet. Zu diesem Zweck werden sämtliche TAPI-Devices auf dem aktuellen Rechner aufgelistet und mit dem Eintrag in den Einstellungen des Benutzers verglichen. Wird ein passendes Device gefunden, so wird dieses verwendet; außerdem merkt man sich dessen Index in einer Variablen AktDeviceID.

Gleichzeitig füllen wir so unser Array TapiDevices, mit dem wir neuen Benutzern ein entsprechendes Auswahlfeld füllen können.

  Set oCollAddresses = oTapi.Addresses
  I = 0
  For Each oCollAddress In oCollAddresses
    TapiDevices(I) = oCollAddress.AddressName
    If TapiDevices(I) = BenutzerDeviceName Then
      Set oAddress = oCollAddress
      AktDeviceID = I
    End If
    I = I + 1
  Next
  Set oCollAddresses = Nothing

Wurde ein passendes Device gefunden, kann der Event handler nun initialisiert werden:

Dim CallbackInstance As Integer, MediaTypes As Integer
Dim RegToken As Long
Dim fOwner As Boolean, fMonitor As Boolean

  oTapi.EventFilter = TE_CALLSTATE
  Set oTapiWithEvents = oTapi
  fOwner = False
  fMonitor = True
  MediaTypes = TAPIMEDIATYPE_AUDIO
  CallbackInstance = 1
  RegToken = oTapi.RegisterCallNotifications(oAddress, _
         fMonitor, fOwner, MediaTypes, CallbackInstance)

Anruf per Klick

Je nach Telefonanlage muss einer in der Datenbank enthaltenen Nummer möglicherweise noch eine "0" vorangestellt werden. Außerdem müssen vermutlich die Formatierungszeichen entfernt werden, so dass die Nummer nur noch aus Ziffern besteht.

Mit folgendem Code wird die Nummer dann angewählt:

Dim oCall As ITBasicCallControl

  Set oCall = oAddress.CreateCall(Nummer, _ 
        LINEADDRESSTYPE_PHONENUMBER, TAPIMEDIATYPE_AUDIO)
  oCall.Connect False  ' False = asynchron

Steht kein TAPI-Provider zur Verfügung, erhält man beim Wählen folgende Fehlermeldung: "Die Methode Address für das Objekt ITCallInfo2 ist fehlgeschlagen."

Identifizierung eingehender Anrufe

Ankommende Anrufe werden vom Event handler bearbeitet. Dieser ist bei mir folgendermaßen definiert:

Private Sub oTapiWithEvents_Event( _
         ByVal TapiEvent As TAPI3Lib.TAPI_EVENT, _
         ByVal pEvent As Object)

Dim oReceivedCallInfo As ITCallInfo
Dim oCallState As ITCallStateEvent
Dim TelNummer As Variant

Const Err_CallInfoString = -2147467259  
   ' Die Methode 'CallInfoString' für das Objekt 
   ' ITCallInfo2 ist fehlgeschlagen
Const DiffSec = 60

Static LastTelNummer As Variant
Static LastTime As Variant
Static CallID As Long

  On Error GoTo Err_oTapiWithEvents_Event

  If IsEmpty(LastTelNummer) Then LastTelNummer = ""
  If IsEmpty(LastTime) Then LastTime = DateAdd("d", -1, Now)
  
  Select Case TapiEvent
    
    Case TE_CALLSTATE
      
      Set oCallState = pEvent
      Set oReceivedCallInfo = oCallState.Call
      TelNummer = _
         oReceivedCallInfo.CallInfoString(CIS_CALLERIDNUMBER)

      Select Case oCallState.State
         Case CS_OFFERING
            ' Falls neuer Anruf -> neue CallID generieren
            ' Dieser Status kommt aber auch noch mal direkt 
            ' vor dem CS_CONNECTED, deshalb brauchen wir 
            ' einen Toleranzzeitraum von DiffSec, damit das
            ' nicht als neuer Anruf gewertet wird.
            If (TelNummer <> LastTelNummer) _
                  Or (DateDiff("s", LastTime, Now) > DiffSec) Then
               Randomize
               CallID = Int((999999 * Rnd) + 1)
            End If
            LastTelNummer = TelNummer
            LastTime = Now
            CCallIDOffering = CallID
            Tele_OnCallerID CallID, TelNummer
        Case CS_CONNECTED
            Tele_OnConnected CallID
            ' damit ggf. ein Anruf von derselben Nummer 
            ' direkt danach wieder erkannt wird
            LastTime = DateAdd("d", -1, Now)
      End Select
      
  End Select
    
  Exit Sub

Err_oTapiWithEvents_Event:
  If Err = Err_CallInfoString Then
    ' Info über Nummer des Anrufers liegt noch nicht vor
    ' - einfach ignorieren
    Err = 0
  Else
    F E H L E R B E H A N D L U N G
  End If
  Exit Sub
  
End Sub

Mit dieser etwas kompliziert wirkenden Konstruktion hat es folgende Bewandnis:

Die Ereignisse CS_OFFERING und CS_CONNECTED treten beim Eingehen bzw. beim Annehmen eines Anrufs innerhalb einiger Millisekunden mehrmals hintereinander auf.

Um einen Anruf trotzdem (möglichst) eindeutig zu identifizieren, erhalten Anrufe von derselben Telefonnummer innerhalb eines gewissen Toleranzzeitraums (hier: 60 Sekunden) dieselbe CallID.

Mit dieser CallID wird der Telefonanruf nun bearbeitet: Beim Eingehen des Anrufs wird versucht, den Anrufer in der Datenbank zu ermitteln und anzuzeigen. Außerdem werden die Anrufe in einer Tabelle gespeichert; dabei wird vermerkt, ob der Anruf angenommen wurde oder nicht. So kann z.B. ein Mitarbeiter nach einer Abwesenheit vom Arbeitsplatz sehen, welche Anrufe er verpasst hat.

Hier findet man eine Beispiel-Datenbank mit den minimalen Funktionen, die man braucht, um mit Access via TAPI zu telefonieren: » Download wintapi.zip.

Datenbank mit TAPI-Funktionen

Hier findet man eine Beispiel-Datenbank mit den minimalen Funktionen, die man braucht, um mit Access via TAPI zu telefonieren:

» Download wintapi.zip