diff --git a/ajax/DeparturesComponent.php b/ajax/DeparturesComponent.php
deleted file mode 100644
index 88ed81e138dbdf865addff3d5bb9eb725625c14a..0000000000000000000000000000000000000000
--- a/ajax/DeparturesComponent.php
+++ /dev/null
@@ -1,140 +0,0 @@
-<?php
-
-	namespace App\Controller\Component;
-
-	use Cake\Controller\Component;
-
-	class DeparturesComponent extends Component {
-	
-		const VERSION			= "0.1";
-		const VRRF_VERSION		= "0.07";
-	
-		public function grabData($options) {
-			$alldata = array();
-			$rawdata = array();
-			foreach ($options['stops'] as $n => $stop) {
-				$stopsplit = explode(":",$stop);
-
-				// errors?
-				if ($stopsplit == $stop)
-					echo "<div error><h1>NAME ERROR!</h1>$stop<br/>Not a propper stop name. Usage: city:stop[;city:stop]*</div>";
-				
-				// get departures
-				$plain = file_get_contents("http://vrrf.finalrewind.org/$stopsplit[0]/$stopsplit[1].json?frontend=json");
-				$data = json_decode($plain, true);
-				// version
-				$alldata["version"] = $this::VERSION;
-				$alldata["vrrf_version"] = $data['version'];
-				$alldata["used_vrrf_version"] = $this::VRRF_VERSION;
-				// api_errors
-				$alldata["errors"]["$stopsplit[0] - $stopsplit[1]"] = $data['error'];
-				// data
-				$rawdata["$stopsplit[0] - $stopsplit[1]"] = $data['raw'];
-			}
-			
-			$reftime = date("dHi");
-			$alldata['lines'] = array();
-			foreach ($rawdata as $stop => $data) {
-				// process
-				foreach ($data as $i => $entry) {
-			
-					// filter
-					if (isset($options['filter']['bl'])) {
-						if (isset($options['filter']['bl']['line']) ) {
-							if (in_array($entry['line'], $options['filter']['bl']['line'])) {
-								continue;
-							}
-						}
-						if (isset($options['filter']['bl']['type']) ) {
-							if (in_array($entry['type'], $options['filter']['bl']['type'])) {
-								continue;
-							}
-						}
-						if (isset($options['filter']['bl']['platform']) ) {
-							if (in_array($entry['platform'], $options['filter']['bl']['platform'])) {
-								continue;
-							}
-						}
-					}
-					if (isset($options['filter']['wl'])) {
-						if (isset($options['filter']['wl']['line']) ) {
-							if (!in_array($entry['line'], $options['filter']['wl']['line'])) {
-								continue;
-							}
-						}
-						if (isset($options['filter']['wl']['type']) ) {
-							if (!in_array($entry['type'], $options['filter']['wl']['type'])) {
-								continue;
-							}
-						}
-						if (isset($options['filter']['wl']['platform']) ) {
-							if (!in_array($entry['platform'], $options['filter']['wl']['platform'])) {
-								continue;
-							}
-						}
-					}
-					// !filter
-					
-					$scheddate = explode(".",$entry['sched_date']);
-					$schedtime = explode(":",$entry['sched_time']);
-					$deptime = $scheddate[0].$schedtime[0].$schedtime[1];
-					//echo intval($deptime)." + ".intval($entry['delay'])." - ".intval($reftime)." = ".(intval($deptime) + intval($entry['delay']) - intval($reftime))."<br/>";
-					if (intval($deptime) + intval($entry['delay']) - intval($reftime) > 0) {
-						$buf = array('date' => $entry['sched_date'], 'time' => $entry['sched_time'], 'delay' => $entry['delay'], 'cancel' => $entry['is_cancelled'], 'name' => $stop, 'info' => $entry['info']);
-						foreach ($alldata['lines'] as $key => $value) {
-							if (($entry['key'] == $value['key']) && ($entry['lineref']['identifier'] == $value['identifier']) && ($entry['line'] == $value['line'])) {
-								$ibuf = sizeof($alldata['lines'][$key]['stops']);
-								while ($buf != null) {
-									if ($ibuf < 1 || strcmp($buf['time'], $alldata['lines'][$key]['stops'][$ibuf - 1]['time']) > 0) {
-										$alldata['lines'][$key]['stops'][$ibuf] = $buf;
-										$buf = null;
-									} else {
-										$alldata['lines'][$key]['stops'][$ibuf] = $alldata['lines'][$key]['stops'][$ibuf - 1];
-										$ibuf--;
-									}
-								}
-								break;
-							}
-						}
-						if ($buf != null) {
-							$dbuf = array('line' => $entry['line'], 'destination' => $entry['destination'], 'type' => $entry['type'], 'key' => $entry['key'], 'identifier' => $entry['lineref']['identifier'], 'stops' => array($buf));
-							$alldata['lines'][] = $dbuf;
-						}
-					}
-				}
-			
-			}
-				
-			// sort
-			usort($alldata['lines'], function($a, $b) {
-				// atime
-				$scheddate = explode(".",$a['stops'][0]['date']);
-				$schedtime = explode(":",$a['stops'][0]['time']);
-				$atime = $scheddate[2].$scheddate[1].$scheddate[0].$schedtime[0].$schedtime[1];
-				// btime
-				$scheddate = explode(".",$b['stops'][0]['date']);
-				$schedtime = explode(":",$b['stops'][0]['time']);
-				$btime = $scheddate[2].$scheddate[1].$scheddate[0].$schedtime[0].$schedtime[1];
-				// compare
-				return intval($atime) - intval($btime);
-			});
-			
-			// clean
-			$lines_count = count($alldata['lines']);
-			if ($lines_count > $options['max']) {
-				$max = min($lines_count, $options['max']);
-				for ($i = $max; $i < $lines_count; $i++) {
-					unset($alldata['lines'][$i]);
-				}
-			
-				foreach ($alldata['lines'] as $id => $line) {
-					unset($alldata['lines'][$id]['key']);
-					unset($alldata['lines'][$id]['identifier']);
-				}
-				
-				return $alldata;
-			}
-		
-		}
-	
-	}
\ No newline at end of file
diff --git a/panels/departure/departures.php b/panels/departure/departures.php
new file mode 100644
index 0000000000000000000000000000000000000000..c5c03bc4907194035f252108a904197ed3e9e612
--- /dev/null
+++ b/panels/departure/departures.php
@@ -0,0 +1,196 @@
+<?php
+	error_reporting (-1);    
+	ini_set ('display_errors', 1);
+	
+	// initialize frontend
+	header('Content-type: text/javascript');
+	
+	// set root
+	chdir($_SERVER['DOCUMENT_ROOT']);
+	
+	// version-info
+	$version = "0.05";
+	$vrrf_version = "0.07";
+	
+	// options
+	$options = array();
+	
+	// max entries
+	$max = 1000000;
+	
+	// configuration
+	if (isset($_GET['config'])) {
+		$file = $_GET['config'];
+		if (!file_exists($file)) {
+			$result['error'] = 'config file "'.$file.'" not found';
+			$result['config']['name'] = $file;
+			$result['options'] = $options;
+			echo json_encode($result);
+			exit;
+		}
+		$config = file_get_contents($file);
+		$buffer = json_decode($config, true);
+		$options['stops'] = $buffer['stops'];
+		$options['filter'] = $buffer['filter'];
+		if (is_numeric($buffer['max'])) {
+			$max = $buffer['max'];
+		}
+		if (count($options['stops']) == 0) {
+			$result['error'] = 'no stops defined';
+			$result['config']['name'] = $config_name;
+			$result['config']['type'] = 'panel/departure';
+			$result['options'] = $options;
+			echo json_encode($result);
+			exit;
+		}
+	} else {
+		$result['error'] = 'no config defined';
+		$result['config']['name'] = $config_name;
+		$result['config']['type'] = 'panel/departure';
+		$result['options'] = $options;
+		echo json_encode($result);
+		exit;
+	}
+	
+	
+	usort($options['stops'], function($a, $b) {
+		return strcmp($a, $b);
+	});
+	
+	// cache
+	$name = json_encode($options);
+	$cachefile = "cache/vrr_".md5($name).".json";
+	$cachetime = 60;
+	$cache_ablauf = 0;
+	$cacheused = false;
+	$rawdata = array();
+	$alldata = array();
+	
+	if (is_file($cachefile))
+		$cache_ablauf=filemtime($cachefile)+$cachetime;
+	if (!isset($_GET["nocache"]) && is_file($cachefile) && (time() < filemtime($cachefile)+$cachetime)) {
+		$plain = file_get_contents($cachefile);
+		$alldata = json_decode($plain, true);
+		$cacheused = true;
+	} else {
+		foreach ($options['stops'] as $n => $stop) {
+			$stopsplit = explode(":",$stop);
+
+			// errors?
+			if ($stopsplit == $stop)
+				echo "<div error><h1>NAME ERROR!</h1>$stop<br/>Not a propper stop name. Usage: city:stop[;city:stop]*</div>";
+			
+			// get departures
+			$plain = file_get_contents("http://vrrf.finalrewind.org/$stopsplit[0]/$stopsplit[1].json?frontend=json");
+			$data = json_decode($plain, true);
+			// version
+			$alldata["version"] = $version;
+			$alldata["vrrf_version"] = $data['version'];
+			$alldata["used_vrrf_version"] = $vrrf_version;
+			// api_errors
+			$alldata["errors"]["$stopsplit[0] - $stopsplit[1]"] = $data['error'];
+			// info
+			$alldata["info"] = ""; // TODO generate informations
+			// data
+			$rawdata["$stopsplit[0] - $stopsplit[1]"] = $data['raw'];
+		}
+		
+		$reftime = date("dHi");
+		$alldata['lines'] = array();
+		foreach ($rawdata as $stop => $data) {
+			// process
+			foreach ($data as $i => $entry) {
+		
+				// filter
+				if (isset($options['filter']['bl'])) {
+					if (isset($options['filter']['bl']['line']) ) {
+						if (in_array($entry['line'], $options['filter']['bl']['line'])) {
+							continue;
+						}
+					}
+					if (isset($options['filter']['bl']['type']) ) {
+						if (in_array($entry['type'], $options['filter']['bl']['type'])) {
+							continue;
+						}
+					}
+					if (isset($options['filter']['bl']['platform']) ) {
+						if (in_array($entry['platform'], $options['filter']['bl']['platform'])) {
+							continue;
+						}
+					}
+				}
+				if (isset($options['filter']['wl'])) {
+					if (isset($options['filter']['wl']['line']) ) {
+						if (!in_array($entry['line'], $options['filter']['wl']['line'])) {
+							continue;
+						}
+					}
+					if (isset($options['filter']['wl']['type']) ) {
+						if (!in_array($entry['type'], $options['filter']['wl']['type'])) {
+							continue;
+						}
+					}
+					if (isset($options['filter']['wl']['platform']) ) {
+						if (!in_array($entry['platform'], $options['filter']['wl']['platform'])) {
+							continue;
+						}
+					}
+				}
+				// !filter
+				
+				$scheddate = explode(".",$entry['sched_date']);
+				$schedtime = explode(":",$entry['sched_time']);
+				$deptime = $scheddate[0].$schedtime[0].$schedtime[1];
+				//echo intval($deptime)." + ".intval($entry['delay'])." - ".intval($reftime)." = ".(intval($deptime) + intval($entry['delay']) - intval($reftime))."<br/>";
+				if (intval($deptime) + intval($entry['delay']) - intval($reftime) > 0) {
+					$buf = array('date' => $entry['sched_date'], 'time' => $entry['sched_time'], 'delay' => $entry['delay'], 'cancel' => $entry['is_cancelled'], 'name' => $stop, 'info' => $entry['info']);
+					foreach ($alldata['lines'] as $key => $value) {
+						if (($entry['key'] == $value['key']) && ($entry['lineref']['identifier'] == $value['identifier']) && ($entry['line'] == $value['line'])) {
+							$ibuf = sizeof($alldata['lines'][$key]['stops']);
+							while ($buf != null) {
+								if ($ibuf < 1 || strcmp($buf['time'], $alldata['lines'][$key]['stops'][$ibuf - 1]['time']) > 0) {
+									$alldata['lines'][$key]['stops'][$ibuf] = $buf;
+									$buf = null;
+								} else {
+									$alldata['lines'][$key]['stops'][$ibuf] = $alldata['lines'][$key]['stops'][$ibuf - 1];
+									$ibuf--;
+								}
+							}
+							break;
+						}
+					}
+					if ($buf != null) {
+						$dbuf = array('line' => $entry['line'], 'destination' => $entry['destination'], 'type' => $entry['type'], 'key' => $entry['key'], 'identifier' => $entry['lineref']['identifier'], 'stops' => array($buf));
+						$alldata['lines'][] = $dbuf;
+					}
+				}
+			}
+		
+		}
+			
+		// sort
+		usort($alldata['lines'], function($a, $b) {
+			// atime
+			$scheddate = explode(".",$a['stops'][0]['date']);
+			$schedtime = explode(":",$a['stops'][0]['time']);
+			$atime = $scheddate[2].$scheddate[1].$scheddate[0].$schedtime[0].$schedtime[1];
+			// btime
+			$scheddate = explode(".",$b['stops'][0]['date']);
+			$schedtime = explode(":",$b['stops'][0]['time']);
+			$btime = $scheddate[2].$scheddate[1].$scheddate[0].$schedtime[0].$schedtime[1];
+			// compare
+			return intval($atime) - intval($btime);
+		});
+		
+		// clean
+		foreach ($alldata['lines'] as $id => $line) {
+			unset($alldata['lines'][$id]['key']);
+			unset($alldata['lines'][$id]['identifier']);
+		}
+		
+		// save cache
+		file_put_contents($cachefile,json_encode($alldata));
+	}
+	
+	echo json_encode($alldata);
+?>
diff --git a/panels/departure/script.js b/panels/departure/script.js
index e17e8f72f6d331290d0b11ec461aa01eeb77ded7..d453dab98b3ddd5a09bc145e125b8d184532d534 100755
--- a/panels/departure/script.js
+++ b/panels/departure/script.js
@@ -65,7 +65,7 @@ this.loaded = function(panel, config) {
 
 var update = function(config, fields) {
 	console.log("departures: get data [" + config + "]");
-	$.get("panels/departure/test.json", function(decodedData) {
+	$.get("panels/departure/departures.php?config=config/panel/departure/" + config + ".json", function(decodedData) {
 		
 		console.log("departures: check vrrf errors [" + config + "]");
 		if (!!decodedData.errors) {
diff --git a/panels/departure/test.json b/panels/departure/test.json
deleted file mode 100755
index 0dd676eb3df1b83b63b814274e2cfa73e8d33a89..0000000000000000000000000000000000000000
--- a/panels/departure/test.json
+++ /dev/null
@@ -1 +0,0 @@
-{"version":"0.1","vrrf_version":"0.07-41-gaf10544","used_vrrf_version":"0.07","errors":{"Dortmund - Josef-von-Fraunhover Stra\u00dfe":null,"Dortmund - Meitnerweg":null,"Dortmund - Universit\u00e4t S":null},"lines":[{"line":"462","destination":"Dortmund Marten S\u00fcd S","type":"Bus","stops":[{"date":"12.06.2015","time":"20:10","delay":"0","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""},{"date":"12.06.2015","time":"20:12","delay":"0","cancel":0,"name":"Dortmund - Meitnerweg","info":""},{"date":"12.06.2015","time":"20:13","delay":"0","cancel":0,"name":"Dortmund - Josef-von-Fraunhover Stra\u00dfe","info":""}]},{"line":"465","destination":"Dortmund Oespel S","type":"Bus","stops":[{"date":"12.06.2015","time":"20:11","delay":"0","cancel":0,"name":"Dortmund - Josef-von-Fraunhover Stra\u00dfe","info":""}]},{"line":"462","destination":"Barop","type":"Bus","stops":[{"date":"12.06.2015","time":"20:16","delay":"0","cancel":0,"name":"Dortmund - Josef-von-Fraunhover Stra\u00dfe","info":""},{"date":"12.06.2015","time":"20:17","delay":"0","cancel":0,"name":"Dortmund - Meitnerweg","info":""},{"date":"12.06.2015","time":"20:22","delay":"0","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"447","destination":"Dortmund Revierpark","type":"Bus","stops":[{"date":"12.06.2015","time":"20:18","delay":"0","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"S1","destination":"Dortmund Hbf","type":"S-Bahn","stops":[{"date":"12.06.2015","time":"20:29","delay":"5","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"S1","destination":"Solingen Hbf","type":"S-Bahn","stops":[{"date":"12.06.2015","time":"20:30","delay":"0","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"465","destination":"Dortmund Oespel S","type":"Bus","stops":[{"date":"12.06.2015","time":"20:31","delay":"1","cancel":0,"name":"Dortmund - Josef-von-Fraunhover Stra\u00dfe","info":""}]},{"line":"447","destination":"Dortmund Hacheney","type":"Bus","stops":[{"date":"12.06.2015","time":"20:34","delay":"0","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"447","destination":"Dortmund Bandelstra\u00dfe","type":"Bus","stops":[{"date":"12.06.2015","time":"20:38","delay":"0","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"447","destination":"Dortmund Bandelstra\u00dfe","type":"Bus","stops":[{"date":"12.06.2015","time":"20:54","delay":null,"cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"S1","destination":"Dortmund Hbf","type":"S-Bahn","stops":[{"date":"12.06.2015","time":"20:59","delay":"0","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"S1","destination":"Solingen Hbf","type":"S-Bahn","stops":[{"date":"12.06.2015","time":"21:00","delay":"0","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"447","destination":"Dortmund Hacheney","type":"Bus","stops":[{"date":"12.06.2015","time":"21:04","delay":null,"cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"465","destination":"Dortmund Bandelstra\u00dfe","type":"Bus","stops":[{"date":"12.06.2015","time":"21:24","delay":null,"cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"S1","destination":"Dortmund Hbf","type":"S-Bahn","stops":[{"date":"12.06.2015","time":"21:29","delay":"7","cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"S1","destination":"Solingen Hbf","type":"S-Bahn","stops":[{"date":"12.06.2015","time":"21:30","delay":null,"cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"447","destination":"Dortmund Hacheney","type":"Bus","stops":[{"date":"12.06.2015","time":"21:34","delay":null,"cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"465","destination":"Dortmund Revierpark","type":"Bus","stops":[{"date":"12.06.2015","time":"21:54","delay":null,"cancel":0,"name":"Dortmund - Universit\u00e4t S","info":""}]},{"line":"462","destination":"Dortmund Huckarde Bushof","type":"Bus","stops":[{"date":"13.06.2015","time":"08:22","delay":null,"cancel":0,"name":"Dortmund - Meitnerweg","info":""},{"date":"13.06.2015","time":"08:23","delay":null,"cancel":0,"name":"Dortmund - Josef-von-Fraunhover Stra\u00dfe","info":""}]},{"line":"462","destination":"Barop","type":"Bus","stops":[{"date":"13.06.2015","time":"08:36","delay":null,"cancel":0,"name":"Dortmund - Meitnerweg","info":""},{"date":"13.06.2015","time":"08:36","delay":null,"cancel":0,"name":"Dortmund - Josef-von-Fraunhover Stra\u00dfe","info":""}]}]}