Cisco IP Phone und Symcon

Hallo zusammen,
ich möchte euch gerne mein privates Projekt zur Nutzung von Symcon 4.0 mit Cisco IP Phones vorstellen.
In dieses Projekt wurden etliche Stunden Arbeit gesteckt. Falls ich jemanden von Euch eine Freude damit machen kann würde ich mich über Spenden freuen :slight_smile:

Über die Telefone möchte ich nicht viel erzählen, sie sind eben ideal was IP-Telefonie, Stromverbrauch, Robustheit, RJ45 LAN-Anschluss, Display und Bedienbarkeit betrifft…und damit ein interessantes Dashboard für Symcon.

Anbei ein paar Screenshots.

00 Standardbildschirm.jpg01 Menue.jpg02 Lichter.jpg03 Licht angeschaltet.jpg04 Strom.jpg05 Rolladen.jpg06 Heizung.jpg07 Heizung Detail.jpg08 Pia Camera.jpg

Was ist notwendig?

  • Symcon 4.0 Server (WebServer Port auf 80)
  • TFTP Server für Konfigurationsdateien der Telefone (SEP…cnf, Firmware, etc…)
  • SIP Server (zur Telefonie, z.B. Fritzbox) --> auf dieses Thema gehe ich weniger ein, weil nix mit Symcon zu tun
  • Cisco IP Phone 7965, 7945…es gehen auch andere Modelle, aber ich beziehe mich auf diese :slight_smile:
  • SIP Firmware auf den Telefonen (beste Erfahrung mit Version 8.5.4)

Kommunikationswege:

IP Symcon Server <–> Telefon ruft auf WebServer, Port 80 geeignete XML Dateien ab, Einsprungspunkt ist der Services Link (servicesURL)

Der Floorplan ist ein Trick:
Per Push Request wird dem Telefon ein Befehl gesendet, dass es ein neues Hintergrundbild abholen soll. Das frische Bild wird dann von Symcon direkt gerendert und bereitgestellt. Diese Funktionalität ist vom Hersteller nicht dokumentiert und hat mich graue Haare gekostet :slight_smile:

Das Telefon wird mit einer XML-Datei (SEP…XML) auf dem TFTP Server konfiguriert.
Zunächst muss man dem Telefon mitteilen, wo es den IPS Server findet:

<servicesURL>http://10.10.10.3/user/cisco/services.php</servicesURL>

Diese Datei ist der Einsprungspunkt für die Taste mit der Weltkugel.

Die Schnellwahltasten werden über

<line button="2"> <!-- Service auf Kurzwahltaste -->
			<featureID>20</featureID>
			<featureLabel>Licht Barbereich</featureLabel>
			<serviceURI>http://10.10.10.3/user/cisco/befehl-execute.php?ID=21099></serviceURI>
</line>

konfiguriert.
„befehl-execute.php“ ist hierbei das Script, das auf dem Webserver ausgeführt wird wenn man die Taste drückt (wird später erklärt).

Menü
…/webfront/user/cisco/services.php:


<?xml version="1.0" encoding="iso-8859-1"?>
<?php
header("Content-type: text/xml; charset=ISO-8859-1");
$server = "http://10.10.10.3/user/cisco/";

if ( isset($_GET['name']) ) {
 if ($_GET['name'] == 'SEP...') { $name = "Esszimmer"; };
 if ($_GET['name'] == 'SEP...') { $name = "Küche"; };
 ...
 if ($_GET['name'] == 'SEP...') { $name = "Kinderzimmer"; };
 if ($_GET['name'] == 'SEP...') { $name = "Schlafzimmer"; };
} else {
 $name = "unbekannt";
}
echo '<CiscoIPPhoneGraphicFileMenu appId="Cisco/Unity">';
echo '<Prompt>Du bist hier: ' . $name . '</Prompt>';
echo '<LocationX>-1</LocationX><LocationY>-1</LocationY>';
echo '<URL>'.$serverIPaddr.'services-bild.php</URL>';

echo '<CiscoIPPhoneGraphicFileMenu appId="Cisco/Unity">';

echo '<Prompt>Du bist hier: ' . $name . '</Prompt>';
echo '<LocationX>-1</LocationX><LocationY>-1</LocationY>';
echo '<URL>'.$server .'services-bild.php</URL>';

echo '<MenuItem><Name>Licht</Name><URL>'.$server.'menu.php?ID=42927&R=10</URL></MenuItem>';
echo '<MenuItem><Name>Strom</Name><URL>'.$server.'menu.php?ID=43964&R=60</URL></MenuItem>';
echo '<MenuItem><Name>Rolladen</Name><URL>'.$server.'menu.php?ID=22939&R=60</URL></MenuItem>';
echo '<MenuItem><Name>Heizung</Name><URL>'.$server.'menu.php?ID=42155&R=60</URL></MenuItem>';
echo '<MenuItem><Name>Modus</Name><URL>'.$server.'menu.php?ID=36903&R=60</URL></MenuItem>';
echo '<MenuItem><Name>Infos</Name><URL>'.$server.'infos.php</URL></MenuItem>';
echo '<MenuItem><Name>PiaCam</Name><URL>'.$server.'camera.php</URL></MenuItem>';

echo '<MenuItem><Name>-</Name><URL></URL></MenuItem>';
echo '<MenuItem><Name>-</Name><URL></URL></MenuItem>';

echo '<MenuItem><Name>Anrufliste</Name><URL>'.$server.'anrufliste.php</URL></MenuItem>';
echo '<MenuItem><Name>Intern</Name><URL>'.$server.'intern.php</URL></MenuItem>';
echo '<MenuItem><Name>Telefonbuch</Name><URL>'.$server.'directory.php</URL></MenuItem>';

echo '</CiscoIPPhoneGraphicFileMenu>';
?>

services-bild.php:


<?php
//include '_rpc_head.inc';
$data = base64_decode(IPS_RunScriptWait(37143)); 
$im = imagecreatefromstring($data);
if ($im !== false) {
    header('Content-Type: image/png');
    imagepng($im);
    imagedestroy($im);
} else {
    echo 'An error occurred.';
}
?>

menu.php:


<?php
if (isset($_GET['R']) ) {
	$refresh = $_GET['R'];
} else {
    $refresh = 0;
}

include '_header_xml.inc';

if ( isset($_GET["ID"]) ) {
	echo IPS_RunScriptWait($_GET['ID']); 
} else {
	echo "kein Objekt angegeben";
}
?>


Objekt 37143 (Script):


<?
$serverIPaddr  = "http://10.10.10.3/user/cisco/";

$fontpath = "../webfront/user/fonts/";

$font  = $fontpath."TerminusTTF.ttf";
$fonta = $fontpath."ArialNarrowBold.ttf";
$fontb = $fontpath."TerminusTTF-Bold.ttf";
$fontc = $fontpath."ArialNarrow.ttf";

$im = @imagecreatetruecolor(298, 156);

$textfarbe    = imagecolorallocate($im, 100, 100, 100);
$schwarz      = imagecolorallocate($im,   0,   0,   0);
$weiss        = imagecolorallocate($im, 255, 255, 255);
$hintergrund  = imagecolorallocate($im, 228, 228, 228);
$beschriftung = imagecolorallocate($im, 115, 157, 176);
$grau         = imagecolorallocate($im, 180, 180, 180);
$rot          = imagecolorallocate($im, 180,  10,  10);
$hellblau     = imagecolorallocate($im, 115, 157, 176);
$hellgrau     = ImageColorAllocate ($im, 200, 200, 200);

$breite  = 288;
$abstand = 5;
$offset  = 5;
$anzahl  = 4;
$y1 = 10;
$y2 = 156/2 - 5;

imagefill($im, 0, 0, $hellgrau);
imagefilledrectangle ( $im , 0, 0 , 145 , 156, $hintergrund);

$tgroesse = 12;
$tstart   = 15;
$zgroesse = 15;

ImageTTFText ($im, $tgroesse-3,  0,   15,   15, $beschriftung, $fonta, "Hauptmenü");

imagefilledrectangle ( $im , 6 ,  $tstart -1+ $zgroesse * 1 , 12 , $tstart -1+ $zgroesse * 1 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    7,   $tstart -1+ $zgroesse * 1, $schwarz, $fonta, "1");
ImageTTFText ($im, $tgroesse,    0,   15,   $tstart + $zgroesse    * 1, $textfarbe, $fontb, "Licht");

imagefilledrectangle ( $im , 6 ,  $tstart -1+ $zgroesse * 2 , 12 , $tstart -1+ $zgroesse * 2 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    7,   $tstart -1 + $zgroesse * 2, $schwarz, $fonta, "2");
ImageTTFText ($im, $tgroesse,    0,   15,   $tstart + $zgroesse    * 2, $textfarbe, $fontb, "Strom");

imagefilledrectangle ( $im , 6 ,  $tstart -1+ $zgroesse * 3 , 12 , $tstart -1+ $zgroesse * 3 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    7,   $tstart -1 + $zgroesse * 3, $schwarz, $fonta, "3");
ImageTTFText ($im, $tgroesse,    0,   15,   $tstart + $zgroesse    * 3, $textfarbe, $fontb, "Rolladen");

imagefilledrectangle ( $im , 6 ,  $tstart -1+ $zgroesse * 4 , 12 , $tstart -1+ $zgroesse * 4 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    7,   $tstart -1 + $zgroesse * 4, $schwarz, $fonta, "4");
ImageTTFText ($im, $tgroesse,    0,   15,   $tstart + $zgroesse    * 4, $textfarbe, $fontb, "Heizung");

imagefilledrectangle ( $im , 6 ,  $tstart -1+ $zgroesse * 5 , 12 , $tstart -1+ $zgroesse * 5 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    7,   $tstart -1 + $zgroesse * 5, $schwarz, $fonta, "5");
ImageTTFText ($im, $tgroesse,    0,   15,   $tstart + $zgroesse    * 5, $textfarbe, $fontb, "Modus");

imagefilledrectangle ( $im , 6 ,  $tstart -1+ $zgroesse * 6 , 12 , $tstart -1+ $zgroesse * 6 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    7,   $tstart -1 + $zgroesse * 6, $schwarz, $fonta, "6");
ImageTTFText ($im, $tgroesse,    0,   15,   $tstart + $zgroesse    * 6, $textfarbe, $fontb, "Infos");

imagefilledrectangle ( $im , 6 ,  $tstart -1+ $zgroesse * 7 , 12 , $tstart -1+ $zgroesse * 7 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    7,   $tstart -1 + $zgroesse * 7, $schwarz, $fonta, "7");
ImageTTFText ($im, $tgroesse,    0,   15,   $tstart + $zgroesse    * 7, $textfarbe, $fontb, "PiaCam");


imagefilledrectangle ( $im , 6 +145,  $tstart -1+ $zgroesse * 1 , 18 +145 , $tstart -1+ $zgroesse * 1 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    152,   $tstart -1 + $zgroesse * 1, $schwarz, $fonta, "10");
ImageTTFText ($im, $tgroesse,    0,    166,   $tstart + $zgroesse    * 1, $textfarbe, $fontb, "Anrufe");

imagefilledrectangle ( $im , 6 +145,  $tstart -1+ $zgroesse * 2 , 18 +145, $tstart -1+ $zgroesse * 2 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    152,   $tstart -1 + $zgroesse * 2, $schwarz, $fonta, "11");
ImageTTFText ($im, $tgroesse,    0,    166,   $tstart + $zgroesse    * 2, $textfarbe, $fontb, "Intern");

imagefilledrectangle ( $im , 6 +145,  $tstart -1+ $zgroesse * 3 , 18 +145, $tstart -1+ $zgroesse * 3 -9 , $weiss );
ImageTTFText ($im, $tgroesse-4,  0,    152,   $tstart -1 + $zgroesse * 3, $schwarz, $fonta, "12");
ImageTTFText ($im, $tgroesse,    0,    166,   $tstart + $zgroesse    * 3, $textfarbe, $fontb, "Telefonbuch");

header('Content-Type: image/png');
ob_start();
imagepng ($im);
$output = ob_get_contents();
ob_end_clean();

echo base64_encode( $output);
imagedestroy($im);
?>

Jetzt haben wir das Hauptmenü abgefrühstückt und kommen nun zum Menüeintrag Licht

Objekt 42927 (Script):


<?
$serverIPaddr = "http://10.10.10.3/user/cisco/";

echo '<CiscoIPPhoneIconFileMenu appId="Cisco/Unity" ><Title>Lichter</Title><Prompt>Sortiert nach letzter Aktivität</Prompt>';

function check($ID) {
 $aktuell = GetValue($ID);
 $wert = 0;
 if ( $aktuell != 0 ) {
  $wert = 1;
 }
 return $wert;
}

$lichter = array (
 15552 => array ("name" => "Essz. Mitte",       "script" => "40155"),
 52615 => array ("name" => "Flur 1",                "script" => "45441"),
...
 //Dimmer
 51746 => array ("name" => (GetValue(51746 /*[Geräte\Esszimmer\Esszimmer / Licht Esstisch\LEVEL]*/)*100). "% Essz. Esstisch",                       "script" => "17352"),
...
);


foreach ($lichter as $key => $val) {
 $updatedata = IPS_GetVariable($key) ;
 $update = $updatedata['VariableChanged'];
 $updates[$key] = $update ;
}

arsort ($updates);

foreach ($updates as $key => $val) {
 echo "<MenuItem><IconIndex>". check($key) ."</IconIndex><Name>". $lichter[$key]["name"] ."</Name><URL>" . $serverIPaddr . "befehl-execute.php?ID=". $lichter[$key]["script"] . "</URL></MenuItem>";
}

echo "<IconItem><Index>0</Index><URL>" . $serverIPaddr . "licht_aus.png</URL></IconItem>";
echo "<IconItem><Index>1</Index><URL>" . $serverIPaddr . "licht_an.png</URL></IconItem>";

echo '<SoftKeyItem><Name>Update</Name><URL>SoftKey:Update</URL><Position>2</Position></SoftKeyItem>';
echo '<SoftKeyItem><Name>Select</Name><URL>SoftKey:Select</URL><Position>1</Position></SoftKeyItem>';
echo '<SoftKeyItem><Name>Exit</Name><URL>SoftKey:Exit</URL><Position>3</Position></SoftKeyItem>';
echo "</CiscoIPPhoneIconFileMenu>";
?>

Um Befehle an IPS zu übergeben gibt es ein PHP Script auf dem WebServer:
befehl-execute.php:


<?xml version="1.0" encoding="iso-8859-1"?>
<?php
header("Content-type: text/xml");
header("Connection: close");
header("Expires: -1");
header("Refresh: 4; URL=SoftKey:Exit");
echo '<?xml version="1.0" encoding="iso-8859-1"?>';
echo '<CiscoIPPhoneImageFile onAppFocusLost="SoftKey:Update">';
echo '<Title>Server</Title>';
echo '<LocationX>-1</LocationX><LocationY>-1</LocationY>';
echo '<Depth>16</Depth><URL>http://10.10.10.3/user/cisco/befehl-execute-bild.php?ID='.$_GET['ID'].'</URL>';
echo '<SoftKeyItem><Name>Zurück</Name><URL>SoftKey:Exit</URL><Position>1</Position></SoftKeyItem>';
echo '</CiscoIPPhoneImageFile>';
?>

befehl-execute-bild.php:


<?
$server = "http://10.10.10.3/user/cisco/";
try {
	$predata = IPS_RunScriptWaitEx(45298, Array("ID" => $_GET['ID']));
	$data = base64_decode($predata);
	$im = imagecreatefromstring($data);
	if ($im !== false) {
    	header('Content-Type: image/png');
    	imagepng($im);
    	imagedestroy($im);
	} else {
  		ob_clean();
  		header('Content-Type: image/png');
  		$im = imagecreatefrompng("http://10.10.10.3/user/cisco/befehl-execute-fehler.png");
  		imagepng($im);
  		imagedestroy($im);
	}
} catch (Exception $e) {
 	ob_clean();
 	header('Content-Type: image/png');
 	$im = imagecreatefrompng("http://10.10.10.3/user/cisco/befehl-execute-fehler.png");
 	imagepng($im);
 	imagedestroy($im);
}
?>

Es wird das Toggle Script auf IPS aufgerufen, das dann Plain Text die Antwort über den Vorgang liefert. Z.B. bei Licht:


<?
if (GetValueBoolean($LichtSTATE) == true) {
 HM_WriteValueBoolean($LichtHM,"STATE",false);
 $Wert = "0";
 $Antwort = "ausgeschaltet";
} else {
 HM_WriteValueBoolean($LichtHM,"STATE",true);
 $Antwort = "angeschaltet";
 $Wert = "1";
}
echo utf8_encode(IPS_GetName($LichtHM). ' ' . $Antwort.'.;'.$Wert);
?>

Das wars nun zunächst mit dem Services Menü Licht.
Kommen wir nun zum spannenden Teil mit dem Statusdashboard das immer gezeigt wird.
Hierzu verwenden wir selbst erzeugte Bilder die wir dem Telefon direkt aus IPS senden zusenden:


<?

$ip == "IP-ADDRESSE DES TELEFONS";
$version = "7965";

if (Sys_Ping($ip, 50) === true) {
 $port = "80";
 $uid = "default";
 $pwd = "user";
 $response = "";
 $auth = base64_encode($uid.":".$pwd);
 $xml = "<setBackground><background><image>http://10.10.10.3/background".$version.".php</image><icon>http://10.10.10.3/background-thumb.png</icon></background></setBackground>";
 $xml = "XML=".$xml;
 $post = "POST /CGI/Execute HTTP/1.0
";
 $post .= "Host: $ip
";
 $post .= "Authorization: Basic $auth
";
 $post .= "Connection: close
";
 $post .= "Content-Type: text/xml
";
 $post .= "Content-Length: ".strlen($xml)."

";
 $fp = fsockopen ( $ip, $port, $errno, $errstr, 30);
 if(!$fp){
  echo "$errstr ($errno)<br>
";
 } else {
  fputs($fp, $post.$xml);
  flush();
  while (!feof($fp)) {
   $response .= fgets($fp, 128);
   flush();
  }
 }
 fclose($fp);
}
?>

Auf dem WebServer:
background7965.php:


<?php

try {
 $data = base64_decode(GetValue(20561)); 
 $im = imagecreatefromstring($data);
 //Check ob sinnvolles Bild ankommt
 if ($im != false) {
  ob_clean();
  header('Content-Type: image/png');
  imagepng($im);
  imagedestroy($im);
 } else {
  ob_clean();
  header('Content-Type: image/png');
  $im = imagecreatefrompng("http://10.10.10.3/user/cisco/background-fehler.png");
  imagepng($im);
  imagedestroy($im);
 }
} 

//Fehlerbehandlung bei allg. Fehler im oberen Abschnitt
catch (Exception $e) {
 ob_clean();
 header('Content-Type: image/png');
 $im = imagecreatefrompng("http://10.10.10.3/user/cisco/background-fehler.png");
 imagepng($im);
 imagedestroy($im);
}

?>

Objekt 20561 ist hierbei unser PNG Bild in IPS das wir Base64 codiert haben (denke das brauche ich nicht zu erklären).

Für die Authentifizierung am Telefon muss auf dem TFTP Server noch in der SEP…XML Datei folgender Eintrag hinein:


<sshUserId>default</sshUserId>
<sshPassword>user</sshPassword>

und


<authenticationURL>http://10.10.10.3/user/cisco/auth.php</authenticationURL>

und die „Antwort-Datei“ auf dem WebServer:
auth.php:


<?php
echo "AUTHORIZED";
?>

So das wars fürs erste!

Vielleicht habe ich den Spieltrieb bei dem einen oder anderen geweckt? :wink:

Coole Sache - Spieltrieb ist definitiv bei mir geweckt, nur nutzte ich SNOM-Telefone.

Aber die Leute von SNOM hab genaus sowas kürzlich erst als Showcase auf Basis von Snom.io gezeigt, gibt ein Video dazu bei Youtube.

Da geht was :slight_smile:

Hallo Wolfgang,
vielen Dank für den interessanten Beitrag. Können die von Dir erwähnten Cisco-Telefone 7965, 7945 auch Bilder von einer Webcam streamen (wie beim Snom 821)? Muss man eine customized Firmware installieren, um die Telefone zB. an einer Frtzbox betreiben zu können?

Gruß
Peter

Hallo Peter,
danke!
Die Telefone können leider Video nicht direkt verarbeiten. Der Trick ist mit Hilfe eines Webservers den Videostrom einer Kamera als Bild zu capturen und das dann in eine XML Hülle zu packen und an das Telefon zu senden. Mit Hilfe eines HTML Reload Headers wird dann das XML Dokument vom Telefon immer wieder neu vom Webserver angefordert. Das ‚Video‘ wäre dann maximal ein Bild pro Sekunde schnell.
Für den Betrieb des Telefons mit einer Fritzbox ist die original SIP Firmware erforderlich.
Alle Beispiele hier beziehen sich unter der Verwendung der original SIP-Version 8.5.
Es gibt zahlreiche Foren die sich mit dem Thema Telefonie und den notwendigen SEP…
cnf Konfigurationsdateien auf dem TFTP Server beschäftigen. Möchte hier möglichst nur auf die Symcon Themen fokussieren, weil das Thema sehr sehr komplex ist.
Ich habe fast ein Jahr gebraucht, die Schnittstellen und das Verhalten des Telefons zu verstehen, vieles war nur mühseliges Ausprobieren. Aber es hat sich gelohnt ;))

Vielen Dank für die Info. Ich werde mir mal so ein Ding zum Spielen besorgen :slight_smile:

Gefällt mir sehr gut.

Gleich mal bei Ebay schauen nach einem gebrauchten Gerät.

Tolle Sache, ich bin gerade dabei das ganze direkt in Symcon zu integrieren, sodass der separate Webserver nicht mehr notwendig ist und die Telefone direkt mit Symcon kommunizieren können.
Das Problem ist, dass IPS auf Port 3777 läuft und ich die Telefone zwingend den Port 80 wollen.

Erstell dir eine Webserver Instanz auf Port 80 ;).

Jaaa, das geht!!! Danke!
Jetzt noch die Pfade anpassen :slight_smile:

Jetzt noch irgendwie dem Symcon Webserver mitteilen dass er PHP-Code in XML Dateien ausführt :slight_smile:
Gefunden: /Applications/Symcon.app/Contents/Service/mime.types

Ich konnte alles zum Symcon Webserver migrieren. Alle notwendigen Erweiterungen (z.B. GD) sind vorhanden. Die XML Problematik habe ich ganz simpel über eine Umbenennung der Datei von XML in PHP gemacht. Ist wahrscheinlich sicherer als den Webserver zu verbiegen.
Alles läuft nun geschmeidiger ab weil die JSON RPC Schnittstelle entfallen ist :slight_smile:
Cool.