Smartphones, Tablets, and Mobile Devices Documents

cancel
Showing results for 
Search instead for 
Did you mean: 

Building Native iPhone Applications for Single-Channel, Multi-point Data Acquisition

Introduction

This example shows you how to build a native iPhone application for performing multiple-point data acquisition from a single channel.

TimeGraph.pngFFTGraph.pngSettings.png


Application Architecture

system-remotedaq-iphone.jpg

1.  NI-DAQmx Task - Performs multiple-point data acquisition on a single channel.

2.  LabVIEW Web Service - Starts the NI-DAQmx task when called and publishes the data as an HTTP response.

3.  iPhone Application - Calls the web service on demand and plots the data from the server.

Download Example Code

Single-Channel Multi-Point Data Acquition (LabVIEW Project).zip

Single-Channel Multi-Point Data Acquition (iPhone Application).zip

Requirements

  • Windows Vista or later (to run DAQmx within a web service, as opposed to only polling a variable in the web service)
  • LabVIEW 2009 or later
  • iOS code tested with iOS 4.2

Tutorial

Part 1: NI-DAQmx Task and LabVIEW Web Service

The NI-DAQmx task and web service are combined into a single VI. In this VI, there are three primary sections. First, there is a Setup section which specifies which device the DAQmx task will read from. Also, this section reads the parameters from the web service's HTTP Request, and pulls them out into variables that can be used to create the DAQmx task. These variables are the following:

    • Mode - this specifies whether the service will be returning Time values, FFT values, or both (interweaved together)
    • Number of Samples
    • Sample Rate

setupSection.png

Next, we have the DAQmx section. This section takes in the parameters that were read in the Setup section and creates a DAQmx task. Once the task is created, a DAQmx read collects the data from the hardware.

DAQsection.png

Lastly, once the data from the device is in LabVIEW, the final section prepares and sends the data back to the phone. First, the FFT of the data is computed, then, the arrays of data are reordered so that they can be easily processed on the phone. Next, the Data Interleave VI creates a formatted string representing the binary values of the data. (We are able to cut down on the amount of data that needs to be sent over the network by sending a binary representation of our data, rather than an ASCII representation.) Lastly, once the output has been formatted as a binary string, the HTTP Response is written.

XMLsection.png

After you create  your web service VI, you must deploy it within the LabVIEW project.

Because the web service depends on some additional HTTP Headers to be set before the request is sent, this service cannot be tested through the browser like the more simple examples can be tested.

Part 2: iPhone Application

Getting Started with iPhone

Download the SDK

Hello World Tutorial

Developer's Guide

Setting Up the Project

In our sample code, we have used the ASIHTTPRequest classes to simplify our calls to the Web Service. Take a look at Using ASIHTTPRequest in an iOS Project to see how to add these classes to your project and add the necessary frameworks.

We have also used the Core Plot framework to simplify plotting in iOS. Take a look at How to Add Core Plot to an iOS Project.

Understanding the Code

This code snippet contains the core functionality and is a part of the View Controller for the application's primary view. When the user presses the Refresh button to fetch new data from the server, we first clear the previous data, then we fetch new data. The comments in the fetchStream method walk through what is happening. It is worth mentioning that it is not generally a good idea to fetch data synchronously like this. The way this is written, the UI will appear frozen to the user from the time that the button is pressed until the data is returned from the server. This could be improved by fetching the data asynchronously. Take a look at the ASIHTTPRequest Documentation to see how to make this modification.

Not shown here, we use a modal view controller to give the user the ability to edit the parameters of the data acquisition. This view controller is defined in the ModalConfigurationViewController class, and is displayed to the user when the editButtonPressed function is called.

-(IBAction)refreshButtonPressed:(id)sender{
    //remove any previous data
    [appDelegate.data clearSamplesForVirtualChannel:channelName];
    [appDelegate.data clearSamplesForVirtualChannel:channelName2];
   
    //fetch new data from the web service
    [self fetchStream];
}

-(void)fetchStream{
    NSURL *url;
    ASIHTTPRequest *request;
    NSError *error;
       
    // Create an HTTP Request to fetch data from the server
    NSUserDefaults *stdDefaults = [NSUserDefaults standardUserDefaults];
    url = [NSURL URLWithString:[stdDefaults valueForKey:@"networkAddress"]];
    request = [ASIHTTPRequest requestWithURL:url];
   
    // Set the request headers with the variables that were populated from the Settings View
    // Note, the Physical Channel variable should be set for compatibility with the existing web
    // service's API, but it will be ignored by LabVIEW
    [request addRequestHeader:@"PHYSICAL_CHANNEL" value:@"0"];
    [request addRequestHeader:@"MODE" value:[HelperFunctions numberToString:[stdDefaults valueForKey:@"dataMode"]]];
    [request addRequestHeader:@"NUMBER_OF_SAMPLES" value:[HelperFunctions numberToString:[stdDefaults valueForKey:@"numberOfSamples"]]];
    [request addRequestHeader:@"SAMPLE_RATE" value:[HelperFunctions numberToString:[stdDefaults valueForKey:@"sampleRate"]]];
    [request startSynchronous];
   
    // Print out the web request's info for debugging
    NSLog(@"%@", url);
    NSLog(@"numberOfSamples: %@", [HelperFunctions numberToString:[stdDefaults valueForKey:@"numberOfSamples"]]);
    NSLog(@"sampleRate: %@", [HelperFunctions numberToString:[stdDefaults valueForKey:@"sampleRate"]]);
   
    error = [request error];
    if (!error) {
        // If there was no error, let's save the data

        double num;
        NSRange range;
        CFSwappedFloat64 tempNum;
       
        NSData *response = [request responseData];
        NSInteger numElements = [response length]/sizeof(double);
        NSMutableArray *results;
        NSMutableArray *results2;
       
        // Depending on the dataMode (time, fft, both) we'll need different storage to handle the data
        // (This approach isn't very efficient, but hopefully the concepts are clear)
        switch ([[stdDefaults valueForKey:@"dataMode"] intValue]) {
            case 0:
                //time
                results = [[NSMutableArray alloc] initWithCapacity:numElements/2];
                break;
            case 1:
                //FFT
                results2 = [[NSMutableArray alloc] initWithCapacity:numElements/2];
                break;
            case 2:
                //time/FFT
                results = [[NSMutableArray alloc] initWithCapacity:numElements/2];
                results2 = [[NSMutableArray alloc] initWithCapacity:numElements/2];
                break;
            default:
                break;
        }
       
        // Let's loop through all the data that we recevied and add it to the correct
        // storage array.
        for (NSInteger i=0; i<numElements; i++) {
           
            // First, we need to extract the next set of bytes that represent the next data element
            range.location = 8*i;
            range.length = sizeof(double);
            [response getBytes:&tempNum range:range];
           
            // Because LabVIEW output the data using Bin Endian formatting, we need to swap the
            // byte order to Little Endian to be compatible with iOS
            num = CFConvertDoubleSwappedToHost(tempNum);

            // Based on the data mode, add the current number to the correct array
            switch ([[stdDefaults valueForKey:@"dataMode"] intValue]) {
                case 0:
                    //time
                    [results addObject:[NSNumber numberWithDouble:num]];
                    break;
                case 1:
                    //FFT
                    [results2 addObject:[NSNumber numberWithDouble:num]];
                    break;
                case 2:
                    //time/FFT
                    if (i%2 == 0) {
                        // Even idexed points will be Time values
                        [results addObject:[NSNumber numberWithDouble:num]];
                    }else {
                        // Odd indexed points will be FFT values
                        [results2 addObject:[NSNumber numberWithDouble:num]];
                    }
                    break;
                default:
                    break;
            }
        }

        // Based on the data mode, add the array(s) of data to the app's storage
        switch ([[stdDefaults valueForKey:@"dataMode"] intValue]) {
            case 0:
                //time
                [appDelegate.data setSamples:results forVirtualChannel:channelName];
                break;
            case 1:
                //FFT
                [appDelegate.data setSamples:results2 forVirtualChannel:channelName2];
                break;
            case 2:
                //time/FFT
                [appDelegate.data setSamples:results forVirtualChannel:channelName];
                [appDelegate.data setSamples:results2 forVirtualChannel:channelName2];
                break;
            default:
                break;
        }
       
        // Update the UI with the data
        [graphView processNewPoint];
       
    }else {
        // If there was an error retrieving data, log it to the console.
        // In a shipping app, this should certainly be improved to alert the user to the error.
        NSLog(@"error");
        NSLog(@"%i", [request responseStatusCode]);
        NSLog(@"%@", error);
    }  
}
National Instruments
Applications Engineer
Comments
henryjr
Member
Member
on

good Project

Contributors