Laufzeit des Asterisk-Servers mit einem AGI-Skript in Python aufsagen lassen

18.03.2023

Dieses kleine mit Python realisierte AGI-Skript  sagt die Laufzeit des Linux-Rechners, auf dem Asterisk läuft, gerundet in Tagen an. Da mein Asterisk-Server selbstverständlich im Dauerbetrieb arbeitet, lässt sich ermitteln, wie viele Tage der letzte Stromausfall zurückliegt. Ist man nicht daheim, lässt sich durch einen einfachen Anruf feststellen, ob es kürzlich einen Stromausfall gab. Diese Information kann in bestimmten Fällen sehr nützlich sein. Deshalb gehört dieses Programm auf jede Nebenstellenanlage auf der Basis von Asterisk.

Zum Beispiel kann man vom Urlaubsort kontrollieren, wie lange der Stromausfall ungefähr war und ob die Lebensmittel im Kühlschrank und der Gefriertruhe zu verderben drohen. Außerdem ist die Laufzeit eines Asterisk-Servers ein Indiz für die Zuverlässigkeit und Verlässlichkeit des Telefondienstes.

Getestet auf meinem Raspberry Pi 3 B+, der immer durchläuft bis zum nächsten Stromausfall

Getestet wurde das Skript auf einem Raspberry Pi 3 B+ mit Raspbian Buster, Python 2.7 und Asterisk 16.2.1. Es trägt den Namen linux-runtime.py:

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

# 17:54 Freitag, 17. März 2023 linux-runtime.py Volker Lange-Janson

import subprocess
import sys
import re
import time

# Nachfolgendes wird für die Kommunikation mit Asterisk benötigt:
####################################################################
# 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

time_duration = 1
time.sleep(time_duration) 

sayit("/usr/share/asterisk/sounds/en_US_f_Allison/hello")
sys.stdout.flush()
time_duration = 1
time.sleep(time_duration)

# Laufzeit von Linux in Sekunden abrufen
output3 = subprocess.check_output('cat /proc/uptime', shell=True)
uptime_seconds = float(output3.split()[0])

# Laufzeit in Tagen umwandeln und runden
uptime_days = int(uptime_seconds / (60 * 60 * 24))

#######################################################################
#######################################################################

sayit("/usr/share/asterisk/sounds/eigene/statusmeldungen/uptime_days")
sys.stdout.flush()
time_duration = 1
time.sleep(time_duration)

saynumber(uptime_days);
sys.stdout.flush()

time_duration = 1
time.sleep(time_duration) 

#######################################################################
#######################################################################

sayit("/usr/share/asterisk/sounds/en_US_f_Allison/goodbye")
sys.stdout.flush()
time_duration = 1
time.sleep(time_duration)

#######################################################################

# Beispiel, wie das Skript in der extensions.conf aufgerufen wird
# durch die Wahl der 350
#
# exten => 350,1,Noop(Linux-Laufzeit: "${CALLERID(name)}" <${CALLERID(num)}>) 
#  same => n,Answer()    
#  same => n,AGI(linux-runtime.py)
#  same => n,Hangup()

# Sprachansagen:
# Laufzeit dieses Asterisk-Servers in Tagen:

Es müsste sowohl auf Python 2.7 und 3.x laufen.

So wird das AGI-Skript in der extensions.conf aufgerufen, wenn man die 350 wählt:

# Beispiel, wie das Skript in der extensions.conf aufgerufen wird 
# durch die Wahl der 350 
# 
# 
exten => 350,1,Noop(Aufruf Linux-Laufzeit von: "${CALLERID(name)}" <${CALLERID(num)}>)
   same => n,Answer()
   same => n,AGI(linux-runtime.py)
   same => n,Hangup()

Dann wird noch eine Soundfile für die Sprachansage benötigt: uptime.zip

 

**************************

Version mit Laufzeitangabe in Tagen und Stunden:

Diese Variante gibt die  Tage und Stunden in einem vollständigen Satz wieder.

Beispiel einer fiktiven Ansage in Tagen und Stunden:

Das Python-AGI-Skript:

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

# Montag, 20. März 2023 linux-runtime.py Volker Lange-Janson

# Diese Variante gibt die Laufzeit in Tagen und Stunden 
# in einem vollständigen Satz an

import subprocess
import sys
import re
import time

# Nachfolgendes wird für die Kommunikation mit Asterisk benötigt:
####################################################################
# 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

time_duration = 1
time.sleep(time_duration) 

sayit("/usr/share/asterisk/sounds/en_US_f_Allison/hello")
sys.stdout.flush()
time_duration = 1
time.sleep(time_duration)

# Ermittlung der Linux-Laufzeit, Umrechnung, Übergabe in Variablen

# Laufzeit von Linux in Sekunden abrufen
output3 = subprocess.check_output('cat /proc/uptime', shell=True)
uptime_seconds = float(output3.split()[0])

# Laufzeit in Tagen umwandeln und runden
uptime_days = int(uptime_seconds / (60 * 60 * 24))

# Laufzeit in Tagen und Stunden umwandeln und runden
tag = int(uptime_seconds // (60 * 60 * 24))
stunde = int((uptime_seconds // (60 * 60)) % 24)

# Die drei Soundfiles:
# 1. The runtime of this Asterisk server is
# 2. days and
# 3. hours.

#######################################################################
#######################################################################

# Eine For-Schleife wiederholt die Ansage 4 Mal.
# Die Ansage besteht aus 3 Soundfiles runtime1, runtime2 und runtime3. 
# Dazwischen werden zwei
# Nummern für die Tage und Stunden aufgesagt.

for i in range(4):
    sayit("/usr/share/asterisk/sounds/eigene/statusmeldungen/runtime1")
    sys.stdout.flush()
    saynumber(tag);
    sys.stdout.flush()
    sayit("/usr/share/asterisk/sounds/eigene/statusmeldungen/runtime2")
    sys.stdout.flush()
    saynumber(stunde);
    sys.stdout.flush()
    sayit("/usr/share/asterisk/sounds/eigene/statusmeldungen/runtime3")
    sys.stdout.flush()
    time_duration = 3
    time.sleep(time_duration) 

#######################################################################

# sayit("/usr/share/asterisk/sounds/eigene/statusmeldungen/uptime_days")
# sys.stdout.flush()
# time_duration = 1
# time.sleep(time_duration)
# 
# saynumber(uptime_days);
# sys.stdout.flush()
# 
# time_duration = 1
# time.sleep(time_duration) 

#######################################################################
#######################################################################

sayit("/usr/share/asterisk/sounds/en_US_f_Allison/goodbye")
sys.stdout.flush()
time_duration = 1
time.sleep(time_duration)

#######################################################################

# Beispiel, wie das Skript in der extensions.conf aufgerufen wird
# durch die Wahl der 350
#
# exten => 350,1,Noop(Serverlaufzeit aufgerufen von: "${CALLERID(name)}" <${CALLERID(num)}>) 
#  same => n,Answer()    
#  same => n,AGI(linux-runtime.py)
#  same => n,Hangup()

# 

Die drei Soundfiles: runtime1.wav, runtime2.wav und runtime3.wav

Download des Projekt mit allen notwendigen Dateien:  Projekt Tage Stunden Server Runtime.zip

Zu beachten ist, dass bei dieser Ansage grammatikalische Fehler auftreten, da die Ansage immer von “days” und “hours”, also von der Mehrzahl, ausgeht. Wer will, kann mit IF-Schleifen auch die Fälle in der Einzahl berücksichtigen und wenn Null Tage und Null Stunden auftreten. Ich denke jedoch mit diesem Schönheitsfehler leben zu können.

Andere Sprachen: Die Ansagen sind alle auf Englisch, da Englisch die am meisten verstandene Sprache auf der Welt ist. Selbstverständlich lassen sich auf Asterisk weitere Sprachen installieren und die Soundfiles in anderen Sprachen erstellen. Wie das geht, ist unter https://elektronikbasteln.pl7.de/erzeugen-von-telefon-ansagen-in-verschiedenen-sprachen erklärt.

*******

Weiterführend und ergänzend:

Der Programmier-Code ist fehlerfrei. Trotzdem läuft das AGI-Skript nicht. Warum verflixt noch einmal?
AGI-Skripte für Asterisk unter Windows editieren – Was ist zu beachten? – 9. März 2023: Windows ist weit verbreitet. Deshalb schreiben nicht wenige ihre AGI-Skripte auf Windows, um sie dann in ihren Raspberry Pi zu kopieren. Wer einige Dinge nicht beachtet, wird die Skripte nicht zum Laufen bekommen. Hier sind die Fallstricke beschrieben, die mich viel Zeit gekostet haben. Die Beispiele beziehen sich auf Python. Sie haben für andere Programmiersprachen die entsprechende Gültigkeit. – weiter

Mein Telefon-Server läuft wartungsfrei rund um die Uhr auf einem Raspberry Pi 3 B+. Die Telefon-Teilnehmer sind weltweit verteilt. Jeder kann mitmachen.
Hurra! Mein Asterisk-Telefon-Server-Tutorial ist fertiggestellt – 9. März 2023: Das Tutorial besteht aus genau 40 Abschnitten und es ermöglicht Anfängern, auf einem Raspberry Pi einen eigenen Telefon-Server zu betreiben und diesen mit anderen Asterisk-Servern zu vernetzen. Zusätzlich sind zahlreiche wirkungsvolle Schutzmaßnahmen wie APIBAN und Fail2Ban beschrieben, um einen sicheren Betrieb zu gewährleisten. Asterisk läuft bei mir dank APIBAN jetzt wartungsfrei rund um die Uhr. Das Tutorial beinhaltet auch die Programmierung von AGI-Skripten auf Python und anderen Programmiersprachen, wodurch es fast keine Grenzen bei der Umsetzung gibt. – weiter