3D Druck | T-Rex Chrome Smartphone Halter

Ein kleiner aber schöner Halter für das Smartphone. Für das Model wurde als Vorlage das Projekt von „mightynozzle“ verwendet. Der Anschluss des Ladegeräts ist in diesem Halter kein Problem. Das Smartphone kann, durch die vielen verschiedenen Stufen auf dem Rücken des T-Rex, in verschiedene Positionen stabil aufgestellt werden. Die Position der beiden T-Rex auf der Grundplatte ist so gewählt, dass das Smartphone in jeder Position stabil auf dem Tisch steht.

Gedruckt wurde das Model in PLA (schwarz). Dabei wurde das Model auf eine Größe 80% skaliert. Die Größe ist ausreichend für die gängigen Smartphones. Der Druck dauert mit dem Anycubic Kossel plus ca. 9 Stunden. Hilfsstrukturen (Support) werden benötigt um die Hohlräume unterhalb des T-Rex zu überbrücken. Gefüllt (Infill) ist das Projekt zu 25%. Dadurch wird der Halter extrem leicht, bleibt aber robust genug. Gedruckt wurde mit einer Auflösung (Resolution) der Düse von 0,2 mm.

Das Model steht zum Download bei Thingiverse unter folgendem Link zur Verfügung: T-Rex – Chrome Phone Holder Phone Stand

Das Model ist Lizenziert über  creativecommons.org

E-Mail Sprachmailbox und Cisco IP Phone

Ich erhalte von meinem Servicerufnummeranbieter E-Mails mit Sprachnachrichten, die bei Abwesenheit für mich hinterlassen wurden. In den E-Mails befinden sich .wav Dateien mit den hinterlassenen Nachrichten. Wäre dies eine normale Sprachmailbox, könnte ich die Rufnummer einfach in meinem Cisco Telefon hinterlegen und die Sprachnachrichten per Telefonanruf abhören. Die .wav Dateien mit den Sprachaufzeichnungen aus den E-Mails lassen sich nicht so einfach über das Telefon abhören.

 

Auslesen des E-Mail Postfachs

Mit dieser PHP Funktion lese ich alle E-Mails in meinem Postfach, die einen bestimmten Betreff „$subject“ besitzen.

function imapMailList($user, $pass, $server, $subject){

	$mailbox = imap_open($server,$user,$pass,NULL, 1, 
			array('DISABLE_AUTHENTICATOR' => 'PLAIN'))or 
		           die(var_dump(imap_errors())); 

	$arr = imap_search($mailbox, $subject, FT_UID); 

	$data = imap_fetch_overview($mailbox, implode(",",$arr), FT_UID);

	for($i=0; $i <count($data); $i++){
		
		$data[$i]->number = getNumber($data[$i]->subject);
	}

	imap_close($mailbox);

	return $data;
}

Anmerkungen:

Der Username „$user“ und das Passwort „$pass“ sind die Postfachzugangsdaten, die auch für eine Einrichtung in einem E-Mail Programm verwendet werden. Der Servername wird wie folge erstellt.

$server = {ihre.server.adresse:993/imap/ssl}INBOX

Ich ermittle hier noch zusätzlich die Telefonnummer „$data[]->number“ über eine Parsing Funkion. Da die E-Mails nicht unbedingt bei jedem identisch aussehen, sollte hier jeder seine eigene Parsing Funktion erstellen. Die Ermittlung der Telefonnummer ist nicht zwingend notwendig, ermöglicht mir aber einen „Redail“ Button anzuzeigen, um den Anrufer direkt nach Abhören der Nachricht zurückzurufen.

 

Anzeige der E-Mails in einer Liste

Ich verwende für die Anzeige der Informationen die Template Engine Smarty. Dabei setze ich noch vor der Anzeige meiner Daten über das Template im PHP Script den Header „header(„Content-type: text/xml“);“

<?xml version="1.0" encoding="utf-8"?>
<CiscoIPPhoneIconFileMenu>
   <Title>E-Mail Messages</Title>
   <Prompt></Prompt>
{foreach $data as $item}
   <MenuItem>
	<IconIndex>{if $item->seen}0{else}1{/if}</IconIndex>
     <Name>From {$item->number}</Name>
     <URL>http://ihr.raspberry.server/messages.php?a=messages_detail&amp;b={$item->uid}</URL>
   </MenuItem>
{/foreach}
   <IconItem>
     <Index>0</Index>
     <URL>http://ihr.raspberry.server/PNG/letter_open.png</URL>
   </IconItem>
  <IconItem>
     <Index>1</Index>
     <URL>http://ihr.raspberry.server/PNG/letter.png</URL>
   </IconItem>
</CiscoIPPhoneIconFileMenu>

Anmerkungen:

Für gelesene und ungelesene Nachrichten blende ich hier unterschiedliche Symbole ein. Als Überschrift verwende ich die vorher ermittelte Telefonnummer.

CB-IT_Cisco_Phone_messages

 

Anzeigen einer E-Mail und .wav Datei aus Anhang speichern

Über den „Select“ Button öffnet sich ein neues Fenster auf dem Cisco Phone, das die E-Mail Details anzeigt. Im Hintergrund wird auf dem Raspberry Pi der Anhang der E-Mail gelesen und temporär auf als Datei auf dem Raspberry Pi abgelegt.

function imapMailContent($user, $pass, $server, $messageID){

	$mailbox = imap_open($server,$user,$pass,NULL, 1, 
			array('DISABLE_AUTHENTICATOR' => 'PLAIN'))or 
		           die(var_dump(imap_errors())); 

	$data = imap_fetch_overview($mailbox, $messageID, FT_UID); 

	imapMailAttachment($mailbox, $messageID);

	imap_close($mailbox);

	$data[0]->number = getNumber($data[0]->subject);

	return $data;
}

Anmerkungen:

Die „$massageID“ erhalte ich aus den Übergabeparametern, die ich beim Klicken auf den „Select“ Button über die aufgerufene URL mitgebe.

Den Anhang lese ich über die Funktion „imapMailAttachment“. Wenn ich einen Anhang ermitteln kann, werden im temporären Ordner „temp/“ Alle Dateien mit die mit „tmes“ beginnen gelöscht. Ich benötige für mein Telefon immer nur die aktuelle .wav Datei, daher leere ich das Verzeichnis komplett.

Danach wird der Anhang ausgelesen und in das Verzeichnis „/tmes“ auf dem Raspberry Pi Webserver kopiert. Um die .wav Datei wieder zu erkennen, benenne ich die Datei nach der E-Mail ID.

function imapMailAttachment($mailbox, $messageID) {

    $actMail = imap_fetchstructure($mailbox, $messageID, FT_UID);
    $PartsInActMail = count($actMail->parts);

    if ($PartsInActMail >= 2) 
    {
	for ($p=1; $p<$PartsInActMail; $p++)
	{

		$files = glob('temp/tmes*'); 
		foreach($files as $file){ 
			if(is_file($file))
			unlink($file); 
		}

		$inhalt = base64_decode(imap_fetchbody($mailbox, $messageID, $p+1, FT_UID));
		$fp = fopen("temp/tmes".$messageID.".wav", "w+"); 
		$files [$messageID] = "tmes".$messageID.".wav";
		fwrite ($fp, $inhalt) or die ("Error!"); 
		fclose ($fp);
	}
    }

}

Den Inhalt der E-Mail zeige ich erneut über ein Template an. Zuvor wird wieder der Header „header(„Content-type: text/xml“);“ gesetzt. Für das Abspielen der .wav Datei auf dem Cisco Telefon erstelle ich hier die Buttons „Play“ und „Stop“. Diese senden über „Notify“ nur den Status, dass abgespielt oder gestoppt werden soll an den Raspberry Pi Server.

<?xml version="1.0" encoding="utf-8"?>
<CiscoIPPhoneText>
   <Title>From {$data[0]->number}</Title>
   <Prompt></Prompt>
   <Text>{$data[0]->subject}&#13;&#13;{$data[0]->date|replace:" +0100":""} </Text>
 <SoftKeyItem>
   <Name>Update</Name>
   <URL>SoftKey:Update</URL>
   <Position>1</Position>
 </SoftKeyItem>
<SoftKeyItem>
    <Name>Play</Name>
   <URL>Notify:http:ihr.raspberry.server:80:messages.php?p={$data[0]->uid}</URL>
   <Position>2</Position>
 </SoftKeyItem>
<SoftKeyItem>
    <Name>Exit</Name>
   <URL>SoftKey:Exit</URL>
   <Position>3</Position>
 </SoftKeyItem>
<SoftKeyItem>
    <Name>Redail</Name>
   <URL>Dial:{$data[0]->number}</URL>
   <Position>4</Position>
 </SoftKeyItem>
<SoftKeyItem>
    <Name>Stop</Name>
   <URL>Notify:ihr.raspberry.server:80:messages.php?s={$data[0]->uid}</URL>
   <Position>5</Position>
 </SoftKeyItem>
</CiscoIPPhoneText>

Anmerkungen:

Es wird absichtlich nur ein „Notify“ an den Server gesendet, da hier keine weitere Oberfläche generiert werden muss. Es soll lediglich der Stream zum Telefon gestartet oder gestoppt werden. Der „Notify“ löst im nächsten Schritt den Stream vom Raspberry Pi Server zum Cisco Telefon aus.

Die Cisco Funktion „Play“, die für das Abspielen von Audiodateien standardmäßig verwendet werden kann, ist in der Abspiellänge begrenzt, sodass ich mich für längere Sprachnachrichten für den Stream entschieden habe.

CB-IT_Cisco_Phone_messages_detail

 

„Play“ oder „Stop“ des Audio Streams

Beim Klicken des „Play“ oder „Stop“ Buttons werden auf dem Raspberry Pi zwei Schritte ausgeführt. Der Stream muss gestartet werden und das Cisco Telefon muss per Push informiert werden, dass es den Stream empfangen soll. Zur Vereinheitlichung habe ich die XML Push Nachrichten über eine zentrale PHP Funktion gesteuert und benötige nur die Telefon IP Adresse „$phoneIP“, den Benutzernamen „$phoneUser“ und das Passwort „$phonePass“, sowie die Daten „$data“, die gesendet werden sollen.

function pushXMLToCisco( $phoneIP, $phoneUser, $phonePass, $data) {

	header("Content-type: text/xml");

	$auth = base64_encode($phoneUser.":".$phonePass);

	$xml = '<CiscoIPPhoneExecute><ExecuteItem URL="'.$data.'"/></CiscoIPPhoneExecute>';	
	$xml = 'XML='.urlencode($xml);

	$post = "POST /CGI/Execute HTTP/1.0\r\n";
	$post .= "Host: ".$phoneIP."\r\n";
	$post .= "Authorization: Basic $auth\r\n";
	$post .= "Connection: close\r\n";
	$post .= "Content-Type: application/x-www-form-urlencoded\r\n";
	$post .= "Content-Length: ".strlen($xml)."\r\n\r\n";

	$fp = fsockopen ( $phoneIP, 80, $errno, $errstr, 30);

	if(!$fp){ 
		echo "$errstr ($errno)<br>\nIP: $phoneIP<br>\n"; 
	} else {
		fputs($fp, $post.$xml);
		flush();
		$response= "";
		while (!feof($fp)) {

			$response .= fgets($fp, 128);
			flush();
		}
	}
}

 

Die Steuerung für „Play“

Zuerst schließe ich die „Notify“ Verbindung vom Telefon, um die weiteren Aufgaben abzuhandeln. Dann sende ich per XML Push den Befehl „RTPRx:“.$serverIP.“:20482:10″ zum Öffnen des Ports für den Stream auf das Telefon. Danach starte ich, wenn die .wav Datei vorhanden ist, den ffmpeg Stream. Falls weitere ffmpeg Streams vorhanden sind, werden diese geschlossen. Ist der Stream abgeschlossen, wird auch auf dem Telefon per XML Push die Übertragung beendet.

if(isset($_GET['p']) {

	ignore_user_abort(true);
	set_time_limit(0);

	ob_start();

	header('Connection: close', true);
	header('Content-Length: 0',true);
	
	flush();
	ob_flush();

	$data = "RTPRx:".$serverIP.":20482:10";
	pushXMLToCisco($phoneIP, $phoneUser, $phonePass, $data);


	if(is_file("temp/tmes".$_GET['p'].".wav")){

		shell_exec('pkill -f ffmpeg');

		shell_exec("ffmpeg -re -i /xxx/xxx/xxx/temp/tmes".$_GET['p'].".wav -filter_complex 'aresample=8000,asetnsamples=n=160' -f rtp rtp://".$phoneIP.":20482");

	}

	$data = "RTPRx:Stop";
	pushXMLToCisco($phoneIP, $phoneUser, $phonePass, $data);
}

 

Die Steuerung für „Stop“

Über den „Stop“ Button ist es möglich den Stream der .wav Datei jederzeit abzubrechen. Hierzu wird auf dem Raspberry Pi Server der ffmpeg Stream abgebrochen und eine XML Push Nachricht an das Cisco Telefon gesendet, um die Übertragung auch dort zu beenden.

if(isset($_GET['s'])) {

	ignore_user_abort(true);
	set_time_limit(0);

	ob_start();

	header('Connection: close', true);
	header('Content-Length: 0',true);
	
	flush();
	ob_flush();

	shell_exec('pkill -f ffmpeg');

	$data = "RTPRx:Stop";
	pushXMLToCisco($phoneIP, $phoneUser, $phonePass, $data);

}

 

Telefonnummer an Cisco IP Telefon senden (2. Teil)

sendNumberToCisco.js

In der Javascript-Datei „sendNumberToCisco.js“ werden alle Funktionen erstellt, die den zusätzlichen Eintrag im Kontextmenü erzeugen, die XML-Datei mit der Telefonnummer erstellen und die Verbindung zum Telefon herstellen. Als Erstes erstellen wir den Button im Kontextmenü.

chrome.runtime.onInstalled.addListener(function() {
	// Create an item for a selected text in the menu
	var id = chrome.contextMenus.create({"title": "Send to Cisco phone", "contexts":["selection"], "id": "numberToCiscoPhoneSelection"});
});

Anmerkungen:

Ich erstelle einen Listener, der dem Kontextmenü einen Eintrag mit dem Namen „Send to Cisco phone“ hinzufügt. Die Option „contexts“: [„section“] ermöglicht es mir, dass der Eintrag im Kontextmenü nur angezeigt wird, wenn auch tatsächlich etwas markiert ist. Natürlich bekommt der Button auch eine „id“: „numberToCiscoPhoneSelection“.

 

Jetzt erstellen ich einen weiteren Listener, der auf den Klick im Kontextmenü reagiert und die entsprechenden Funktionen ausführt, die es ermöglichen die Daten an das Telefon zu senden. Vorher validiere ich noch notdürftig die Telefonnummer. Dies lässt sich bestimmt noch weiter ausbauen, genügt aber für meine Zwecke.

// The onClicked callback function.
function onClickHandler(info, tab) {

	// Check if the correct button is clicked
	if(info.menuItemId == "numberToCiscoPhoneSelection"){

		var ip = "000.000.000.000";
		var uid = "XXXX";
		var pwd = "XXXX";
	
		if(info.selectionText == undefined){
			alert("nothing marked!");
			exit;
		} 

		// Replace '+' to '00'
		var number = info.selectionText.replace('+', '00')
		// Only numbers
		number = number.replace(/\D+/g,"");

		// Check if there is a number to call
		if(number == ""){
			alert("nothing marked!");
			exit;
		}

		// Build XML
		var xml = '<?xml version="1.0"? encoding="UTF-8">\r\n' +
		'<CiscoIPPhoneExecute>' +
		'<ExecuteItem Priority="0" URL="Dial:' + number + ':1:Cisco/Dialer"/>\r\n'+
		'</CiscoIPPhoneExecute>\r\n';

		// XML request
		var xhttp = new XMLHttpRequest();
		xhttp.open("POST", "http://"+ip+"/CGI/Execute", true);
		xhttp.setRequestHeader ("Authorization", "Basic " + btoa(uid + ":" + pwd));
		xhttp.setRequestHeader ("Content-type", "application/x-www-form-urlencoded");
		xhttp.onreadystatechange = function(){

			// Log response from phone for connection debug
			console.log( this.responseText );
		}
		xhttp.send("XML="+xml);		
	}
};

chrome.contextMenus.onClicked.addListener(onClickHandler);

Anmerkungen:

Ich erstelle einen „chrome.contextMenus.onClicked.addListener“ der den „onClickHandler“ aufruft. Ich prüfe ab, ob das geklickte Objekt die selbe ID hat wie der Eintrag, den ich dem Kontextmenü hinzugefügt habe („numberToCiscoPhoneSelection“).

Die Variablen „ip“, „uid“ und „pwd“ sollten hier nicht hart codiert eingetragen werden. Es gibt eine schönere Möglichkeit diese Daten z. B. im Speicher von Google Chrome abzulegen.

In den nächsten Schritten prüfe ich, ob auch Daten an die Funktion übergeben wurde. Ich ändere das „+“ in „00“ um, da oft „+49“ in den Telefonnummern angegeben ist. Danach entferne ich alles was, keine Zahl ist aus der Telefonnummer, z. B. – / oder Leerzeichen.

Jetzt das Wichtigste. Ich erstelle die XML-Datei, die ich übertragen möchte. Ich habe den genauen Aufbau der XML-Datei der Cisco Dokumentation entnommen.
Ich erstelle nun ein Objekt um XML Daten zu senden „new XMLHttpRequest();“, füge die Zieladresse ein „http://“+ip+“/CGI/Execute“ und melde mich am Telefon an „Authorization“, „Basic “ + btoa(uid + „:“ + pwd)“.

Zum Schluss kann ich die Daten senden „xhttp.send(„XML=“+xml);“. Als Hilfe lasse ich mir die Antwort des Telefons in der Konsole von Chrome anzeigen.

Um nun die Erweiterung (Add-on) in Chrome zu laden, muss ich zuerst in den Einstellungen die Erweiterungen aufrufen. Hier den Haken für den „Entwicklermodus“ setzten und über den Dialog hinter „Entpackte Erweiterung laden …“ den Ordner mit den zwei Dateien auswählen.

CB-IT-Services_Chrome-Add-On