AGI-Skript in Python sagt die aktuelle CPU-Temperatur des Asterisk-Servers auf

15.03.2023

Dieses AGI-Skript für Python ist eine nette Spielerei und sagt dem Anrufer die aktuelle Temperatur der CPU des Asterisk-Servers auf. Die Ansage wird wiederholt und ist in diesem Beispiel auf Englisch. Es gibt zwei Versionen. Dier erste Version sagt die Temperatur nur in Celsius auf. Die zweite Version sagt die Temperatur in Celsius und in Fahrenheit auf.

So ganz ist es doch keine Spielerei, wenn man per Anruf die Temperatur des Asterisk-Servers erfährt. Liegt sie im Bereich des Normalen, ist das ein Indiz dafür, das alles im grünen Bereich ist.

Hier das Skript, das ich temperatur-server-cpu.py genannt habe: Getestet wurde es auf Raspbian Buster mit Asterisk 16.2.1

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

# Sagt die aktuelle Temperatur in Grad Celsius der
# der CPU des Asterisk-Servers gerundet auf Englisch auf.
# 14:31 Mittwoch, 15. März 2023
# temperatur-server-cpu.py von Volker Lange-Janson SM5ZBS für Python 2.7

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)

result = subprocess.check_output(["cat", "/sys/class/thermal/thermal_zone0/temp"])
cpu_temp = round(int(result) / 1000.0)

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

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

time_duration = 1
time.sleep(time_duration) 

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

saynumber(cpu_temp);
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 349
#
# exten => 349,1,Noop(Anruf erfolgt von: "${CALLERID(name)}" <${CALLERID(num)}>) 
#  same => n,Answer()    
#  same => n,AGI(temperatur-server-cpu.py)
#  same => n,Hangup()

Wir brauchen noch eine zusätzliches Soundfile für die Ansage: Sie ist in /usr/share/asterisk/sounds/eigene/statusmeldungen/ zu kopieren

Zum Download des Soundfiles: Soundfiles-temp-in-celsius.zip. Es ist die WAV-Datei zu verwenden. Nicht die MP3 nehmen. So erstellt man die Audiofiles:

Online lassen sich Audio-Dateien in Formate umwandeln, die für Asterisk geeignet sind. Aber es geht noch viel mehr.
Erzeugen von Telefon-Ansagen in verschiedenen Sprachen – 3.11.2022: Für einen Anrufbeantworter oder meinen Asterisk-Server benötige ich Sprach-Ansagen in verschiedenen Sprachen. Die weiblichen oder männlichen Stimmen sollten professionell und deutlich klingen. Wie mache ich das für private Zwecke ohne einen Cent dafür zu zahlen? Und alles soll möglichst online erstellt werden können. – weiter

Falls jemand die temperatur-server-cpu.py von Windows nach Linux kopiert, ist sie noch für Linux geeignet und ausführbar zu machen. Das geschieht mit folgenden Befehlen, die einmalig auszuführen sind:

sudo dos2unix /usr/share/asterisk/agi-bin/temperatur-server-cpu.py
sudo chmod 777 /usr/share/asterisk/agi-bin/temperatur-server-cpu.py
sudo python /usr/share/asterisk/agi-bin/temperatur-server-cpu.py

In der extension.conf kommt folgende Wahlregel für den Aufruf des Skripts zum Einsatz:

exten => 349,1,Noop(Anruf erfolgt von: "${CALLERID(name)}" <${CALLERID(num)}>) 
  same => n,Answer()    
  same => n,AGI(temperatur-server-cpu.py)
  same => n,Hangup()

Wählt man die 349, erfährt man die aktuelle Temperatur der CPU des Asterisk-Servers.

Wie funktioniert das Skript temperatur-server-cpu.py? Das Skript kommuniziert mit Asterisk und gibt die aktuelle Temperatur der CPU des Asterisk-Servers in Grad Celsius auf Englisch aus. Die Temperatur wird durch Auslesen des Dateipfads /sys/class/thermal/thermal_zone0/temp ermittelt und dann gerundet. Das Skript wird über die AGI-Schnittstelle von Asterisk aufgerufen und verwendet die Funktionen sayit und saynumber, um die Ausgabe der Temperatur auf Englisch zu realisieren. Die Umsetzung dieser Funktionen für die AGI-Schnittstelle wird in den ersten 87 Zeilen beschrieben.

Ab Zeile 87 beginnt das eigentliche Programm. Das Skript wartet eine Sekunde, bevor es mit der Ausgabe beginnt. Dann wird die Begrüßung “hello” auf Englisch durch die Funktion sayit ausgegeben. Danach wird die Temperatur der CPU des Servers ausgelesen und gerundet. Die Ausgabe wird durch die Funktion sayit mit der Ansage “temp-in-celsius” eingeleitet, gefolgt von der Funktion saynumber, die die gerundete Temperatur ausgibt. Danach wird die Ausgabe wiederholt und zum Schluss wird mit der Funktion sayit die Verabschiedung “goodbye” ausgegeben. Das Skript wird über die AGI-Schnittstelle von Asterisk aufgerufen und kann in der Datei “extensions.conf” durch die Wahl der Erweiterung “349” aufgerufen werden.

Diese beiden Zeilen

result = subprocess.check_output(["cat", "/sys/class/thermal/thermal_zone0/temp"])
cpu_temp = round(int(result) / 1000)

lesen die aktuelle Temperatur der CPU des Computers aus, auf dem dieses Python-Skript ausgeführt wird. Dazu wird die Ausgabe des Shell-Kommandos “cat /sys/class/thermal/thermal_zone0/temp” als Byte-String in der Variable “result” gespeichert. Anschließend wird dieser Byte-String in eine Ganzzahl (Integer) umgewandelt, durch 1000 geteilt und auf die nächstkleinere Ganzzahl gerundet. Das Ergebnis ist die aktuelle CPU-Temperatur in Grad Celsius, die in der Variablen “cpu_temp” gespeichert wird.

Der Befehl für die Ausgabe der CPU-Temperatur auf Debian lautet:

cat /sys/class/thermal/thermal_zone0/temp

Der Befehl gibt die CPU-Temperatur in Tausendstel Grad Celsius (m°C) aus.

****

Erweiterte Version gibt die Temperatur in Fahrenheit und Celsius an:

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

# Sagt die aktuelle Temperatur in Grad Celsius der
# der CPU des Asterisk-Servers gerundet auf Englisch auf.
# 14:31 Mittwoch, 15. März 2023
# temperatur-server-cpu.py von Volker Lange-Janson SM5ZBS für Python 2.7

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)

result = subprocess.check_output(["cat", "/sys/class/thermal/thermal_zone0/temp"])
cpu_temp = round(int(result) / 1000.0)
fahrenheit = round(int(result) / 1000.0 * 1.8 + 32)

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

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

time_duration = 1
time.sleep(time_duration) 

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

saynumber(fahrenheit);
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 349
#
# exten => 349,1,Noop(Anruf erfolgt von: "${CALLERID(name)}" <${CALLERID(num)}>) 
#  same => n,Answer()    
#  same => n,AGI(temperatur-server-cpu.py)
#  same => n,Hangup()

Der Kern des Programms für die Berechnung von Celsius und Fahrenheit:

result = subprocess.check_output(["cat", "/sys/class/thermal/thermal_zone0/temp"]) 
cpu_temp = round(int(result) / 1000.0) 
fahrenheit = round(int(result) / 1000.0 * 1.8 + 32)

Die beiden Soundfiles für die Ansage von Fahrenheit und Celsius:

Temperatur in Celsius:

Temperatur in Fahrenheit:

Download der WAV-Dateien für Asterisk: Soundfiles Fahrenheit Celsius.zip

Das passiert im Asterisk CLI, wenn die 1088 die 349 anruft:

 -- Executing [349@janson:1] NoOp("SIP/1088-000000b1", "Anruf erfolgt von: "Volker Lange-Janson" <1088>") in new stack
    -- Executing [349@janson:2] Answer("SIP/1088-000000b1", "") in new stack
       > 0x741fc090 -- Strict RTP switching to RTP target address 192.168.1.44:10000 as source
    -- Executing [349@janson:3] AGI("SIP/1088-000000b1", "temperatur-server-cpu.py") in new stack
    -- Launched AGI Script /usr/share/asterisk/agi-bin/temperatur-server-cpu.py
<SIP/1088-000000b1>AGI Tx >> agi_request: temperatur-server-cpu.py
<SIP/1088-000000b1>AGI Tx >> agi_channel: SIP/1088-000000b1
<SIP/1088-000000b1>AGI Tx >> agi_language: en
<SIP/1088-000000b1>AGI Tx >> agi_type: SIP
<SIP/1088-000000b1>AGI Tx >> agi_uniqueid: 1678990470.382
<SIP/1088-000000b1>AGI Tx >> agi_version: 16.2.1~dfsg-1+deb10u2
<SIP/1088-000000b1>AGI Tx >> agi_callerid: 1088
<SIP/1088-000000b1>AGI Tx >> agi_calleridname: Volker Lange-Janson
<SIP/1088-000000b1>AGI Tx >> agi_callingpres: 0
<SIP/1088-000000b1>AGI Tx >> agi_callingani2: 0
<SIP/1088-000000b1>AGI Tx >> agi_callington: 0
<SIP/1088-000000b1>AGI Tx >> agi_callingtns: 0
<SIP/1088-000000b1>AGI Tx >> agi_dnid: 349
<SIP/1088-000000b1>AGI Tx >> agi_rdnis: unknown
<SIP/1088-000000b1>AGI Tx >> agi_context: janson
<SIP/1088-000000b1>AGI Tx >> agi_extension: 349
<SIP/1088-000000b1>AGI Tx >> agi_priority: 3
<SIP/1088-000000b1>AGI Tx >> agi_enhanced: 0.0
<SIP/1088-000000b1>AGI Tx >> agi_accountcode:
<SIP/1088-000000b1>AGI Tx >> agi_threadid: 1840911264
<SIP/1088-000000b1>AGI Tx >>
<SIP/1088-000000b1>AGI Rx << STREAM FILE /usr/share/asterisk/sounds/en_US_f_Allison/hello ""
    -- <SIP/1088-000000b1> Playing '/usr/share/asterisk/sounds/en_US_f_Allison/hello.gsm' (escape_digits=) (sample_offset 0) (language 'en')
<SIP/1088-000000b1>AGI Tx >> 200 result=0 endpos=6400
<SIP/1088-000000b1>AGI Rx << STREAM FILE /usr/share/asterisk/sounds/eigene/statusmeldungen/temp-in-celsius ""
    -- <SIP/1088-000000b1> Playing '/usr/share/asterisk/sounds/eigene/statusmeldungen/temp-in-celsius.slin' (escape_digits=) (sample_offset 0) (language 'en')
       > 0x741fc090 -- Strict RTP learning complete - Locking on source address 192.168.1.44:10000
<SIP/1088-000000b1>AGI Tx >> 200 result=0 endpos=36987
<SIP/1088-000000b1>AGI Rx << SAY NUMBER 40.0 ""
    -- <SIP/1088-000000b1> Playing 'digits/40.gsm' (language 'en')
<SIP/1088-000000b1>AGI Tx >> 200 result=0
<SIP/1088-000000b1>AGI Rx << STREAM FILE /usr/share/asterisk/sounds/eigene/statusmeldungen/temp-in-fahrenheit ""
    -- <SIP/1088-000000b1> Playing '/usr/share/asterisk/sounds/eigene/statusmeldungen/temp-in-fahrenheit.slin' (escape_digits=) (sample_offset 0) (language 'en')
<SIP/1088-000000b1>AGI Tx >> 200 result=0 endpos=36778
<SIP/1088-000000b1>AGI Rx << SAY NUMBER 103.0 ""
    -- <SIP/1088-000000b1> Playing 'digits/1.gsm' (language 'en')
    -- <SIP/1088-000000b1> Playing 'digits/hundred.gsm' (language 'en')
    -- <SIP/1088-000000b1> Playing 'digits/3.gsm' (language 'en')
<SIP/1088-000000b1>AGI Tx >> 200 result=0
<SIP/1088-000000b1>AGI Rx << STREAM FILE /usr/share/asterisk/sounds/en_US_f_Allison/goodbye ""
    -- <SIP/1088-000000b1> Playing '/usr/share/asterisk/sounds/en_US_f_Allison/goodbye.gsm' (escape_digits=) (sample_offset 0) (language 'en')
<SIP/1088-000000b1>AGI Tx >> 200 result=0 endpos=7520
    -- <SIP/1088-000000b1>AGI Script temperatur-server-cpu.py completed, returning 0
    -- Executing [349@janson:4] Hangup("SIP/1088-000000b1", "") in new stack
  == Spawn extension (janson, 349, 4) exited non-zero on 'SIP/1088-000000b1'
raspberrypi*CLI>

 

******

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