Michael Fuller Michael Fuller - 2 months ago 13
reST (reStructuredText) Question

Cardboard QR Scanning without Unity SDK

I've been trying to figure out how to scan and use the QR codes provided with Cardboard devices without needing to use the Unity API. I've already written SCN-VR for SceneKit based VR for iOS devices with Obj-c and I would want scanning QR codes to also work to make setting up profiles simpler.

I've seen a QR code scan to goo.gl/pdNRON, which leads to a page on how to download the Google Cardboard app, but what HTTP service is the Google Cardboard app going to download the actual profile?

Answer

The QR codes can be parsed with Google's protocol buffers (https://developers.google.com/protocol-buffers/docs/cpptutorial?hl=en0). The shortened URL scanned from the code is redirected to an URL that contains the actual info in the p= query field. For example, your URL (goo.gl/pdNRON) redirects to https://www.google.com/get/cardboard/download/?p=CgxNYXR0ZWwsIEluYy4SGFZJRVctTUFTVEVS4oSiIFZSIFZJRVdFUh0xCCw9JWiRbT0qEAAASEIAAEhCAABIQgAASEJYATUpXA89OggpXA8-SOE6P1AAYAM (which displays as the Cardboard download site in a browser). The string you want is the long one starting with CgxNYXR. The field is base64 encoded.

The protocol buffer definition file is available at https://github.com/google/wwgc/blob/master/www/CardboardDevice.proto. Once you have the Protocol Buffers compiler built (protoc, from the tutorial link above), just run it on CardboardDevice.proto, and include the output .cc and .h files in your project. You can access the info through the DeviceParams type, after sending the decoded data to it.

For building the Protocol Buffers library and including it in your iOS project: https://gist.github.com/BennettSmith/9487468ae3375d0db0cc. Or if you have problems generating/linking the libraries, try this as an alternative: Google protocol buffers on iOS

Here's some code to get you started:

// Retrieve HEAD only (don't want the whole page)
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", myURL]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
                                                       cachePolicy:NSURLRequestReloadIgnoringCacheData
                                                   timeoutInterval:10.0f];
[request setHTTPMethod:@"HEAD"];

// Start the request
[NSURLConnection sendAsynchronousRequest:request
                                   queue:[NSOperationQueue mainQueue]
                       completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
    if (connectionError) {
        NSLog(@"%@", [connectionError localizedDescription]);
    } else {
        // Find the p attribute
        NSArray *comps = [[[response URL] query] componentsSeparatedByString:@"&"];
        for (NSString *comp in comps) {
            NSArray *subComps = [comp componentsSeparatedByString:@"="];
            if ([subComps count] == 2 && [subComps[0] isEqualToString:@"p"]) {
                NSString *base64 = subComps[1];

                // Replace _ with /, - with +, and pad with = to multiple of 4
                base64 = [base64 stringByReplacingOccurrencesOfString:@"_" withString:@"/"];
                base64 = [base64 stringByReplacingOccurrencesOfString:@"-" withString:@"+"];
                base64 = [base64 stringByPaddingToLength:(([base64 length]+3)/4)*4 withString:@"=" startingAtIndex:0];

                // Decode from base 64
                NSData *decodedData = [[NSData alloc] initWithBase64EncodedString:base64
                                                                          options:NSDataBase64DecodingIgnoreUnknownCharacters];

                // Get the device parameters
                DeviceParams deviceParams;
                deviceParams.ParseFromArray([decodedData bytes], (int)[decodedData length]);

                // Do stuff with deviceParams
                // eg deviceParams.inter_lens_distance()
                break;
            }
        }
    }
}];