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.

Kommentare (5)

  • michael
    am 21.09.2016
    Hallo,
    erst mal vielen Dank für ihre Musterdatenbank.

    Es ist wirklich super wie das klappt. Leider ist mir noch nicht alles klar.
    Ich versuche nun seit Tagen eine kleine Veränderung zu programmieren. Ich möchte, dass wenn das Gespräch beendet (Gespräch beendet oder der Anrufer legt auf) die Telefonnummer aus dem Feld "eingehender Anruf" gelöscht wird.

    Ich habe dazu eingebaut Case CS_DISCONNECTED
    und einen Haltepunkt an dieser Stelle gesetzt. Leider ohne Erfolg.

    Haben Sie eine Tipp für mich?

    Danke Michael
    • Anna
      am 22.09.2016
      Ich habe Michael per E-Mail kontaktiert, konnte ihm aber leider nicht helfen, da ich Windows TAPI nur für die Funktionen "anrufen" und "Anrufe erkennen" verwende.
  • MW
    am 13.05.2018
    Ich habe die Datenbank heruntergeladen und es hat direkt einwandfrei geklappt. Echt genial...

    Ich dachte nun (als Access-Anfänger), dass ich die heruntergeladene Datei in eine bestehende Access-DB importiere und dann die Telefonie überall dort einsetzen kann.

    Es kommt aber immer ein Fehler beim Öffnen des Formulars TAPI_Eventhändler: Benutzerdedinierter Typ nicht definiert.

    Was habe ich falsch gemacht?
    • Anna
      am 14.05.2018
      Vermutlich ist in VBA unter "Extras" "Verweise" die "Microsoft TAPI 3.0 Type Library" nicht eingebunden.
  • HB
    am 29.08.2018
    Leider bricht das Formular TAPI-Eventhandler mit der Meldung ab:

    Laufzeitfehler 9:

    Index außerhalb des gültigen Bereichs

    und beim debuggen wird auf die Zeile
    " TapiDevices(I) = oCollAddress.AddressName" verwiesen.

    Was habe ich falsch gemacht?
    • Anna
      am 30.08.2018
      Das Feld TapiDevices() ist mit der Größe 30 deklariert. Der Überlauf kann also nur passieren, wenn mehr als 30 TAPI-Treiber vorhanden sind. Sie müssten dann halt das Feld entsprechend größer deklarieren.
  • Robert Dannenmüller
    am 30.10.2018
    Zuerst: Danke für die Datanbank, ist genau was ich gesucht hatte :)
    Hum... passt alles bis zur Öffnung des Formulares "TAPI_Treiber", aber bei der Auswahl des treibers ist kein Eintrag vorhanden (TAPI Library scheint "eingebunden" zu sein...
    Können Sie mir bitte helfen?
    Danke
    Grüsse aus Frankreich
    RD
    • Anna Emde
      am 31.10.2018
      Meine Rückfrage per Mail:
      Haben Sie schon einmal getestet, ob ein TAPI-Treiber installiert ist, z.B. mit dial.exe? Falls TAPI-Treiber korrekt installiert ist, am besten mal die Funktion zum Auflisten der TAPI-Treiber in der Access-Datenbank Schritt für Schritt durchlaufen, dann kann man das Problem vielleicht erkennen.

      Und die Antwort:
      Hat geklappt. War kein TAPI Device installiert.
  • Thomas
    am 05.11.2018
    Hi, besten Dank. Die Anwendung läuft gut, aber wie beende ich einen abgehenden anruf? Wenn ich z.B. irgendwo anrufe und der Angerufene nimmt nicht ab, will ich es nicht ewig klingeln lassen. Wie baue ich also einen "Auflegen" Button?
    • Anna
      am 06.11.2018
      Kommunikation per Mail, Thomas hat folgende Lösung:

      * Im Modul "Telefonie" die Variable oCall auf Public stellen.
      Public oCall As ITBasicCallControl
      * In der Funktion Tele_Outbound die Variable oCall nicht erneut definieren.
      * Folgende neue Sub:
      Public Sub Tele_hangup()
        oCall.Disconnect DC_NORMAL
      End Sub

      Ob das klappt oder nicht, hängt offensichtlich von der Telefonanlage bzw. dem TAPI-Treiber ab - bei mir selbst funktioniert es leider nicht.

Neue Antwort auf Kommentar schreiben

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

Windows TAPI

Weitere Infos zu Windows Tapi findet man in der

» Microsoft Dokumentation.