Bitcoin-Preis-Ansage per Telefon mit Asterisk und Python: Ein AGI-Skript

28.12.2024

Mit diesem Asterisk Gateway Interface (AGI)-Skript für Asterisk kannst Du Anrufern den aktuellen Bitcoin-Kurs in USD ansagen lassen. Das Skript integriert nahtlos Python und die CoinGecko-API, um den Echtzeitpreis von Bitcoin abzurufen und auszugeben. Es ist ein ideales Beispiel dafür, wie moderne Web-APIs mit der leistungsstarken VoIP-Plattform Asterisk verbunden werden können.

Hauptfunktionen: API-Integration: Das Skript nutzt die CoinGecko-API, um aktuelle Daten über den Bitcoin-Kurs abzurufen.

Sprachausgabe: Der aktuelle Kurs des Bitcoin wird über selbst erstellte Sounddateien und Zahlenansage-Funktionen vorgelesen.

Fehlerhandlung: Bei Verbindungsfehlern oder API-Problemen wird eine alternative Ansage abgespielt.

Flexibilität: Das Skript ist leicht anpassbar, falls Du andere APIs oder Währungen nutzen möchtest.

Btc Blutbad
Wenn der Kurs gen Süden abstürzt: Blutbad!

Technische Details: Das Skript liest die AGI-Umgebungsvariablen, kommuniziert mit der API und steuert die Ausgabe über die STREAM FILE- und SAY NUMBER-Funktionen von Asterisk. Dabei wurde darauf geachtet, robuste Fehlerbehandlungen und Wiederholungsansagen zu implementieren.

Das AGI-Skript in Python: Es befindet sich bei mir im Ordner /usr/share/asterisk/agi-bin/

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Mit dem Linux-Befehl "chmod +x bitcoin.py" 
# die notwendige Dateiberechtigung setzen
# Stelle sicher, dass die Datei in UTF-8 kodiert ist.

# Dieses Skript sagt den aktuellen Bitcoinkurs in US-Dollar auf.
# Autor: Volker Lange-Janson SM5ZBS mit Hilfe von ChatGPT
# Datum: 28.12.2024
# Version 1.1
# Getestet mit Asterisk 18.xxx
# Dateiname: bitcoin.py

import requests
import sys
import re
import time

# Nachfolgendes wird für die Kommunikation mit Asterisk benötigt:
# https://www.asterisk.name/asterisk/0596009623/asterisk-chp-9-sect-4.html
# http://www.asteriskdocs.org/en/2nd_Edition/asterisk-book-html-chunk/asterisk-CHP-9-SECT-4.html
####################################################################
# Read the incoming AGI environment variables
env = {}
tests = 0;

while 1:
   line = sys.stdin.readline().strip()

   if line == '':
      break
   key,data = line.split(':')
   if key[:4] != 'agi_':
      #skip input that doesn't begin with agi_
      sys.stderr.write("Did not work!\n");
      sys.stderr.flush()
      continue
   key = key.strip()
   data = data.strip()
   if key != '':
      env[key] = data
    
sys.stderr.write("AGI Environment Dump:\n");
sys.stderr.flush()
for key in env.keys():
   sys.stderr.write(" -- %s = %s\n" % (key, env[key]))
   sys.stderr.flush()
   
def checkresult (params):
   params = params.rstrip()
   if re.search('^200',params):
      result = re.search('result=(\d+)',params)
      if (not result):
         sys.stderr.write("FAIL ('%s')\n" % params)
         sys.stderr.flush()
         return -1
      else:
         result = result.group(1)
         #debug("Result:%s Params:%s" % (result, params))
         sys.stderr.write("PASS (%s)\n" % result)
         sys.stderr.flush()
         return int(result)
   else:
      error_code = re.search('result=(-?\d+)',params)
      if error_code:
         error_code = int(error_code.group(1))
         sys.stderr.write("FAIL (unexpected result '%s', error code %d)\n" % (params, error_code))
         sys.stderr.flush()
         return error_code
      else:
         sys.stderr.write("FAIL (unexpected result '%s')\n" % params)
         sys.stderr.flush()
         return -2

      
def saynumber (params):
   sys.stderr.write("SAY NUMBER %s \"\"\n" % params)
   sys.stderr.flush()
   sys.stdout.write("SAY NUMBER %s \"\"\n" % params)
   sys.stdout.flush()
   result = sys.stdin.readline().strip()
   return checkresult(result)
  
   
def sayit (params):
   sys.stderr.write("STREAM FILE %s \"\"\n" % str(params))
   sys.stderr.flush()
   sys.stdout.write("STREAM FILE %s \"\"\n" % str(params))
   sys.stdout.flush()
   result = sys.stdin.readline().strip()
   checkresult(result)

##################################################################
# Hier fängt das eigentliche Programm an



def get_bitcoin_price():
    """Fetches the current Bitcoin price in USD from the CoinGecko API."""
    url = "https://api.coingecko.com/api/v3/simple/price"
    params = {
        'ids': 'bitcoin',  # Bitcoin identifier
        'vs_currencies': 'usd'  # Convert to USD
    }
    
    try:
        response = requests.get(url, params=params)
        response.raise_for_status()  # Raise an error for bad responses
        data = response.json()
        return data['bitcoin']['usd']
    except requests.exceptions.RequestException as e:
        # print(f"An error occurred: {e}")
        sayit("/usr/share/asterisk/sounds/eigene/btc/btc-error")
        sayit("/usr/share/asterisk/sounds/eigene/btc/donotpanic")
        return None

if __name__ == "__main__":
    price = get_bitcoin_price()
    if price:
        sayit("/usr/share/asterisk/sounds/eigene/btc/btc-dollar")
        saynumber(price);
        sayit("/usr/share/asterisk/sounds/eigene/btc/irepeat")
        saynumber(price);
        sayit("/usr/share/asterisk/sounds/eigene/btc/irepeat")
        saynumber(price);
        sayit("/usr/share/asterisk/sounds/eigene/btc/donotpanic")
        
    else:
        sayit("/usr/share/asterisk/sounds/eigene/btc/btc-error")
        sayit("/usr/share/asterisk/sounds/eigene/btc/donotpanic")
        
        
# Aufruf in der extensions.conf:
        
# exten => 503,1,Noop(Anruf erfolgt von: "${CALLERID(name)}" <${CALLERID(num)}>) 
#  same => n,Answer()    
#  same => n,AGI(bitcoin.py)
#  same => n,Hangup()

# Sounddateien erzeugt mit https://ttsmp3.com/ Johanna US-English
# und https://g711.org/ in der Voreinstellung

Sounddateien: Die benötigten Sounddateien kannst Du ganz einfach online mit TTSMP3 und einem Online-G.711-Konverter (Voreinstellung verwenden) erstellen. Damit erreichst Du professionelle Sprachqualität im passenden Audioformat. Die Soundfiles liegen auf US-Englisch, gesprochen von Johanna, vor:

Sounddateien_BTC-Kurs_AGI-Skript_Python_Asterisk.zip

Anforderungen des AGI-Skripts:

Asterisk: Getestet mit Asterisk 18.x.x
Python: Version 3.x
Pakete: requests-Modul für API-Abfragen
Dateien: Sounddateien im G.711-Format, gespeichert im Verzeichnis /usr/share/asterisk/sounds/eigene/btc/.

So rufst Du das Skript auf: Nach der Konfiguration der extensions.conf kann ein Anrufer durch Wählen der entsprechenden Durchwahl (z. B. 503) den aktuellen Bitcoin-Kurs erfahren. Das Skript ist flexibel einsetzbar – beispielsweise für Finanzdienstleistungen oder als allgemeiner Informationsservice.

Auszug aus der extensions.conf: Wird die 503 gewählt, wird das Skript bitcoin.py aufgerufen.

exten => 503,1,Noop(Anruf erfolgt von: "${CALLERID(name)}" <${CALLERID(num)}>) 
 same => n,Answer()    
 same => n,AGI(bitcoin.py)
 same => n,Hangup()

Fazit: Dieses AGI-Skript zeigt, wie Du Python und Asterisk kombinieren kannst, um dynamische und nützliche Dienste bereitzustellen. Es lässt sich leicht erweitern, zum Beispiel um weitere Währungen oder historische Kursdaten zu integrieren. Probier es aus und hebe Deine Asterisk-Anwendungen auf das nächste Level!

Btcsteigt
Wenn der BTC durch die Decke „to the moon“ geht.

Kritik: Die Abfrage des momentanen Kurses per Telefon gibt nur einen Moment wieder, der wenig Aussagekraft besitzt. Besser ist es die Kursentwicklung in Echtzeit verfolgen zu können. Eine bekannte Seite ist zum Beispiel https://bitcoinwisdom.io/markets/binance/btcusdt, welche diese Echtzeitkurse rund um die Uhr bereitstellt.

ChatGPT: Diese Seite entstand mit Unterstützung von ChatGPT, das mir bei der Programmierung half und die humorvollen Zeichnungen gestaltete. Die KI schlug eigenständig Bildbeschreibungen vor, die DALL-E umsetzte. Für die Sprachdateien übersetzten ChatGPT und DeepL die Texte ins amerikanische Englisch, wobei ChatGPT zusätzlich inhaltliche Verbesserungsvorschläge einbrachte. TTSMP3 wandelte die Texte in natürlich klingende gesprochene Sprache um. Ein Großteil des Inhalts dieser Seite basiert auf Texten von ChatGPT. Nachdem ich der KI das hier vorgestellt Python-Script gezeigt hatte, entwarf sie daraufhin einen passenden Text für meine Webseite. Und das ist erst der Anfang einer Entwicklung, die mir in Zukunft noch mehr Arbeit abnehmen wird.

Weiterführend:

Auf einem Raspberry Pi lässt sich ein kompletter Telefonserver betreiben, der seinen Teilnehmern über Landesgrenzen hinweg kostenloses Telefonieren von unterwegs oder daheim ermöglicht.
Asterisk-Telefonserver auf einem Raspberry Pi – Installation, Konfiguration, Programmierung, SIP, IAX2, AGI-Skripte, Sicherheit und Tipps zum praktischen Betrieb – 2.11.2022: Diese Seite richtet sich an jene, welche einen Asterisk-Telefon-Server auf einem Raspberry Pi betreiben möchten und später ein kleines Netzwerk aus Asterisk-Servern planen, um ein eigenständiges Telefonnetz aufzubauen. Los geht es mit der Installation von Raspbian und Asterisk auf einem Raspberry Pi und dann nach Lust und Laune immer tiefer in die Programmierung von Asterisk. Die Themen werden laufend erweitert.

Selbstverständlich muss es nicht unbedingt ein Raspberry Pi sein. Andere Linux-Rechner gehen auch. – weiter