Annika und ich haben auf dem diesjährigen Barcamp die iPhone Anwendung iLevate entwickelt, mit der man die Höhe eines geworfenen iPhones messen und vergleichen kann. Das Spaß-Projekt hat es immerhin auf den zweiten Platz der umgesetzen Vorschläge geschafft.
Grundsätzlich ging es bei dem Vorhaben auch um das Testen des
Zusammenspiels einer Webapplikation mit dem Mobiltelefon und um die
Frage, inwieweit sich die iPhone Hardware aus einer Webseite heraus
verwenden lässt. Zunächst hat iLevate deshalb auch auf dem Open Source
Framework PhoneGap aufgebaut, welches den Fotoapparat, GPS,
Beschleunigungssensoren, Sounds, Vibration und das Adressbuch in
iPhones, Blackberrys sowie Android Handys gleichermaßen anspricht (Vgl. PhoneGap Roadmap).
Wegen der langen Ladezeit der Anwendung mit PhoneGap, den zu
erwartenden Schwierigkeiten mit Apple beim Einstellen in den App Store
und aufgrund der Tatsache, dass sich die Beschleunigungssensoren in
JavaScript in Echtzeit nur bis 20 Mal pro Sekunde abfragen lassen,
verzichteten wir schlussendlich doch fast gänzlich auf das PhoneGap Framework
und programierten die Anwendung neu als Objective-C/HTML/JavaScript/PHP/MySQL Mischung.
Wie funktioniert iLevate? Der Benutzer startet die Anwendung und sieht zunächst die Rekordlisten mit den höchsten Würfen in seiner Gegend. Der nicht unter Drogen oder Alkoholeinfluss stehende User wirft nun sein in einer Schutzhülle befindliches iPhone auf weichem Untergrund (z.B. Wiese) in die Höhe. Nicht mit voller Kraft versteht sich.
Während sich das iPhone in der Luft befindet, messen die Beschleunigungssensoren im Gerät prinzipiell in keine Richtung irgendeine Beschleunigung. Die Zeit dieses Zustandes wird gestoppt und
aus der so gemessenen Flugzeit lässt sich die Höhe errechnen. Weil sich die Sensoren nicht genau in der Mitte des Gerätes befinden, treten Rotationskräfte auf; dafür wurde ein Toleranzbereich von 2 m/s² festgelegt. Dreht sich das iPhone im Flug zu stark, schlägt die Messung zwar weiterhin fehl, ein zu stark rotierendes iPhone lässt sich aber ohnehin schlecht fangen. Wie man aus der Beschleunigungsgrafik für ein flach fallendes iPhone erkennen kann, werden außerdem ab einer Sekunde Flugzeit schon fast 1 m/s² Beschleunigung durch den Luftwiderstand verursacht. Für höhere Würfe wird der Toleranzbereich deshalb in mehreren Schritten großzügig heraufgesetzt. Seine maximale Fallgeschwindigkeit würde das iPhone theoretisch flach nach 15 Sekunden mit 195 km/h erreichen und die Beschleunigungssensoren würden dann wieder 1 g messen – genauso viel wie bei einem ruhig in der Hand liegenden iPhone.
<font face="courier new,courier,monospace"><font size="2">/*</font></font> <font face="courier new,courier,monospace"><font size="2"> * accelerometer - Sends Accel Data back to the Device. */ - (void) accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)</font></font> <font face="courier new,courier,monospace"><font size="2"> acceleration { NSString * jsCallBack = nil; float height; if (throwing) { newFlightTime = acceleration.timestamp-flightStart; float a; // we accept higher acceleration values when the air resistance </font></font> <font face="courier new,courier,monospace"><font size="2"> // rises at higher velocity if (newFlightTime > 0.5) { a = -0.027; } else if (newFlightTime > 1) { a = -0.040; } else if (newFlightTime > 2.5) { a = -0.050; } else if (newFlightTime > 4) { a = -0.060; } a = a + sqrt(acceleration.x*acceleration.x+acceleration.y*acceleration.y+acceleration.z*acceleration.z);</font></font><font face="courier new,courier,monospace"><font size="2"> if (a < kMaxFlightAcceleration && ![[UIDevice currentDevice] proximityState]) { </font></font> <font face="courier new,courier,monospace"><font size="2"> // now the iPhone is in the air and nothing is near the light sensor if (!flying) { flying = true; flightStart = acceleration.timestamp; //NSLog(@"Flight Start%d",acceleration.timestamp); } else if (!screaming && (newFlightTime > 1)) { // scream screaming = true; NSString *file = @"screamingwoman"; NSString *ext = @"wav"; //NSLog(file); NSBundle *mainBundle = [NSBundle mainBundle]; sound = [[Sound alloc] initWithContentsOfFile:[mainBundle pathForResource:file ofType:ext]]; [sound play]; } } else if(flying) { flying = false; if (newFlightTime < 0) { newFlightTime = newFlightTime + 3074.445; } if (newFlightTime > flightTime) { flightTime = newFlightTime; height = 100*(flightTime/2)*(flightTime/2)*9.81/2; [self addTextToLog:[NSString stringWithFormat:@"Height: %.2f cm",height]]; jsCallBack = [NSString stringWithFormat:@"setHeight(%f);",height]; [webView stringByEvaluatingJavaScriptFromString:jsCallBack]; } }</font></font> } }>
Sobald man mit seiner Wurfhöhe zufrieden ist, drückt man auf den Absenden-Button, um die Daten an dem Server zu senden. Dort erfolgt die Rückwärtsgeokodierung des Standortes, und es werden die Bestwerte für die jeweilige Stadt, Region, Land, Kontinent und der Welt verglichen. Hat man in einer der geographischen Regionen einen neuen Rekord aufgestellt, ertönt eine Fanfare. Die Audio-Datei wird dabei nicht ans iPhone übertragen, sondern liegt bereits in der Applikation und wird von JavaScript aus gestartet.
<font face="courier new,courier,monospace"><font size="2"> </font></font><font size="2" face="courier new,courier,monospace">$sql = "SELECT count(*) FROM throw WHERE height > $height"; $rank_world = mysql_result(mysql_query($sql), 0, 0) + 1;</font> <font size="2" face="courier new,courier,monospace"> <font size="3"><span style="font-family: monospace;"><?php if ($rank_world == 1 || $rank_continent == 1 || $rank_country == 1 || $rank_region == 1 || $rank_city == 1): ?> <script type="text/javascript"> function fanfare() { document.location = "ilevate://sound/fanfare.aif"; } window.onload = fanfare; </script></span></font></font>
iLevate wertet die aufgerufene URL aus und extrahiert den Befehlt:
<p><font size="2" face="courier new,courier,monospace">- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType; { </font> </p>
<p><font size="2" face="courier new,courier,monospace"> NSURL *requestURL =[ [ request URL ] retain ];</font> </p>
<p><font size="2" face="courier new,courier,monospace"> if ([ [ requestURL scheme ] isEqualToString: @"ilevate"]) { NSString * command = [requestURL host]; if([command isEqualToString:@"sound"]){ NSArray *soundFile = [options componentsSeparatedByString:@"."];</font> <font size="2" face="courier new,courier,monospace"> NSString *file = (NSString *)[soundFile objectAtIndex:0];</font> </p>
<p><font size="2" face="courier new,courier,monospace"> NSString *ext = (NSString *)[soundFile objectAtIndex:1]; sound = [[Sound alloc] initWithContentsOfFile:[mainBundle pathForResource:file ofType:ext]]; [sound play];</font> </p>
<img src="http://www.php10.de/iThrow/images/screen_accel_send.png" alt="Submit" align="right">
<p> <font size="2" face="courier new,courier,monospace"> } return NO; } // Auto release</font> </p>
<p><font size="2" face="courier new,courier,monospace"> [ requestURL release ]; return YES; }</font> </p>
iLevate hat es jedoch bisher nicht in den App Store geschafft, Apple hat die Anwendung zunächst leider abgelehnt:
Thank you for submitting iLevate 0.9 to the App Store. We’ve reviewed
iLevate 0.9 and determined that we cannot post this version of your
iPhone application to the App Store at this time because it encourages
a physical activity that could result in a customer damaging their
iPhone. We have chosen to not publish this type of application to the
App Store.
If you believe you can make the necessary modifications to bring
your application in compliance with iPhone Software License Agreement,
we encourage you to do so and resubmit it for review.
Regards,
iPhone Developer Program
Naja, Schade. Vielleicht beim nächsten Mal.
Schreibe einen Kommentar