Freitag, 27. Juli 2012

Outlook Kalender als interaktives Türschild

Wenn man eine begrenzte Anzahl von Meeting Räumen hat, führt man früher oder später einen Gemeinsamen Kalender ein, um die Belegung zu organisieren. Wäre irgendwie praktisch, wenn diese Reservierungen zusätzlich direkt am Raum visualisiert würden und eventuell sogar ein potentieller Gast namentlich begrüßt würde.



Für die Umsetzung gibt es drei Herausforderungen:
  1. Hardwareauswahl für die Anzeige
  2. Datenexport aus MS-Exchange
  3. Visualisierung der Reservierungen
Zur Hardware:
Da das Projekt möglichst wenig kosten sollte, habe ich zunächst an einen WLAN-Bilderrahmen gedacht (inspiriert vom Info Frame Projekt). Allerdings verhielt sich mein Kodak W730S Pulse etwas zickig und war mit knapp 130EUR auch nicht gerade günstig.
Schließlich entschied ich mich für ein billiges 8" Android Tablet (Arnova 8 G2) für knapp 140EUR. Da ich nur einen Webbrowser benötige, ist es auch zu verschmerzen, dass sich die uralt Android Version wahrscheinlich nicht aktualisieren lässt.

Das Tablet ist in ein schickes Gehäuse verpackt und per WLAN ans Netz gekoppelt.


Der Datenexport aus Exchange 2007 erwies sich als etwas größere Hürde. Der einzig gangbare Weg scheint über die ExchangeWebServices (EWS) zu führen. Dafür existieren diverse Frameworks in Perl (EWS::Client) und PHP (php-ews).
Da sich die Abhängigkeiten in meinem Debian Squeeze für das PHP-Framework leichter auflösen ließen (php5-pear und php5-curl), habe ich letztendlich php-ews verwendet.

Folgende Anpassungen musste ich für meinen Classen-Auto-Loader vornehmen:

mkdir EWS
mv EWS_Exception.php EWS/Exception.php

Leider hat die curl-Bibliothek des Debian Squeeze (7.21) ein Problem mit der NTLM-Authentifzierung. Deshalb habe ich das aktuelle cURL 7.25 kompiliert und nach /usr/local installiert. Danach musste ich nur die bereits installierte /usr/lib/libcurl.so.4.2.0 auf die /usr/local/lib/libcurl.so.4.2.0 umbiegen:

mv /usr/lib/libcurl.so.4.2.0 /usr/lib/libcurl.so.4.2.0.orig
ln -s /usr/local/lib/libcurl.so.4.2.0 /usr/lib/libcurl.so.4.2.0

Nun beschwert sich curl zwar beim Ausführen über nicht auffindbare Versionsinformationen - es funktioniert aber alles...
Mein fertiges sync_outlook.php Script sieht folgendermaßen aus:

#!/usr/bin/php5
<?

function __autoload($classname)
{
        $filename = 'php-ews/' . str_replace( '_', '/', $classname ) . '.php';
        include_once $filename;
}

$host='exchange.ourdomain.local';
$username='alex';
$password='secret';

$ews = new ExchangeWebServices($host, $username, $password, ExchangeWebServices::VERSION_2007_SP1);

$request = new EWSType_FindItemType();
$request->Traversal = EWSType_ItemQueryTraversalType::SHALLOW;

$request->ItemShape = new EWSType_ItemResponseShapeType();
$request->ItemShape->BaseShape = EWSType_DefaultShapeNamesType::DEFAULT_PROPERTIES;

$request->CalendarView = new EWSType_CalendarViewType();
$request->CalendarView->StartDate = date('c', time() - 60 * 60 * 24 * 7);
$request->CalendarView->EndDate = date('c', time() + 60 * 60 * 24 * 28);

// if you know exact folder id, then use this piece of code instead. For example
$folder_id = 'AQEuAAADGkRzkKpmEc2byACqAC/EWgMAecPQ3xYTOEq6vqAXocCzBQE5UjLZnQAAAA==';
$request->ParentFolderIds->FolderId = new EWSType_FolderIdType();
$request->ParentFolderIds->FolderId->Id = $folder_id;

// request
$response = $ews->FindItem($request);;

$con = mysql_connect('localhost', 'calendar', 'another_secret');
mysql_select_db('calendar');

mysql_query( "delete from event" );

if( is_array( $response->ResponseMessages->FindItemResponseMessage->RootFolder->Items->CalendarItem ) )
{
 foreach( $response->ResponseMessages->FindItemResponseMessage->RootFolder->Items->CalendarItem as $id )
 {
  $event = $id->Subject;
  $dtvon = date( 'Y-m-d H:i:s',  strtotime( $id->Start ) );
  $dtbis = date( 'Y-m-d H:i:s',  strtotime( $id->End   ) );

  mysql_query( "insert into event values ('', '". $event ."', '', '0000ff', '". $dtvon ."', '". $dtbis ."' )" );
 }
}
else
{
 $id = $response->ResponseMessages->FindItemResponseMessage->RootFolder->Items->CalendarItem;

 $event = $id->Subject;
 $dtvon = date( 'Y-m-d H:i:s',  strtotime( $id->Start ) );
 $dtbis = date( 'Y-m-d H:i:s',  strtotime( $id->End   ) );

 mysql_query( "insert into event values ('', '". $event ."', '', '0000ff', '". $dtvon ."', '". $dtbis ."' )" );
}

mysql_close($con);
?>

Die Folder-ID habe ich über das Beispiel aus dem php-ews Wiki ermittelt. Per Cron wird dieses Script alle paar Minuten aufgerufen.


Die dritte Herausforderung war die Anzeige der Belegtzeiten auf einer Webseite (und schlussendlich auf dem Tablet).
Wie in dem Script oben bereits zu entnehmen ist, werden die Exchange Daten in eine Datenbank geschrieben. Normalerweise bin ich ein Freund von K.I.S.S. und wäre niemals auf die Idee gekommen, auch noch eine Datenbank zu involvieren. Allerdings habe ich in PHP-Kalender eine derart perfekte Umsetzung des Outlook Kalenders gefunden, dass ich dieses Tool einfach verwenden musste - und damit auch die Datenbank.
PHP-Kalender ermöglicht normalerweise auch das Anlegen eines Kalendereintrags, jedoch ignoriere ich diese Funktionen und lösche bei jedem Aufruf von sync_outlook.php die Einträge (sämtliche Einträge werden in Outlook erfasst, es gibt keinen Abgleich zurück nach Exchange).

Zusätzlich habe ich ein paar Änderungen am Start-Script von PHP-Kalender vorgenommen und nicht benötigte Bedienelemente gelöscht. Hier ist das Ergebnis:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html>
  <head>
    <meta http-equiv="Refresh" content="300" />
    <meta http-equiv="Pragma" content="no-cache" />
    <title>Besprechungsraumkalender</title>
    <script src="js/calendar.js"></script>
  </head>
  <body>
<?PHP
$nMode = 2;
$nKW = date('W');
$nJahr = date('Y');
$nBreite = '800';
$nHoehe = '520';
$nAnzZeilenProStunde = 2;
$nZeilenHoehe = 20;
$bDisplayArbeitszeit = true;
$nDisplayStundenVon = 7;
$nDisplayStundenBis = 20;
$nArbeitszeitVon = 8;
$nArbeitszeitBis = 17;

include('connection.php');

$dtvon = date( 'Y-m-d H:i:s', time() + 60 * 0 );
$dtbis = date( 'Y-m-d H:i:s', time() - 60 * -1 );
$result=mysql_query( "select seventbez, dtvon, dtbis from event where dtvon <= '$dtvon' and dtbis >= '$dtbis'" );
if( $result ){$row = mysql_fetch_row($result); }
else{ die('Invalid query: ' . mysql_error()); }

if( $row )
{
 echo "   <h2 align=\"center\"><img src=\"img/firmenlogo.jpg\" /></h2>\n";
 echo "   <h1 align=\"center\"> <br />". date( 'd.m. H:i', strtotime( $row[1] ) ) ." - ". date( 'H:i', strtotime( $row[2] ) ) ." Uhr</h1>\n";
 echo "   <h1 align=\"center\"> <br />". $row[0] ."</h1>\n";
}
else
{

include('calendar.php');

$objCalendar = new calendar();

$objCalendar->setMode($nMode);
$objCalendar->setKW($nKW, $nJahr);
$objCalendar->setCalenderBreite($nBreite);
$objCalendar->setCalenderHoehe($nHoehe);
$objCalendar->setAnzZeilenProStunde($nAnzZeilenProStunde);
$objCalendar->setZeilenHoehe($nZeilenHoehe);
$objCalendar->showArbeitszeit($bDisplayArbeitszeit);
$objCalendar->setStunden($nDisplayStundenVon,$nDisplayStundenBis);
$objCalendar->setArbeitszeit($nArbeitszeitVon, $nArbeitszeitBis);

// Hier wird der Kalender initialisiert:
$objCalendar->init();
echo "</div>\n";
}
?>
  </body>
</html>
<?PHP
mysql_close($con);
?>

So sieht der Kalender aus, wenn kein Termin aktuell ist:
und so, wenn gerade jemand im Haus ist: