Uploading to FlashAir

Latest update: December 2013

In this tutorial, we'll show you how to upload a file to your FlashAir with upload.cgi.
This tutorial builds off of Android Tutorial 4: Displaying Image Thumbnails.

Overview

We're going to sort images on your FlashAir by title, and by date.

Warning: If this operation is used incorrectly, the FlashAir file system may become corrupted and you may lose your data. Host devices, like a PC, can cache the contents of the SD card (FAT), but will not see changes made by upload.cgi. Therefore, a FAT file system conflict may occur if changes are made by the host device and upload.cgi at the same time. After using upload.cgi, re-insert the SD card and the host device will be able to see your changes.

Creating the Screen Layout

Here's the screen layour of our app:

(Layout)

We will add screen3:"Date list", and screen4:"Operation Screen" to iOS Tutorial 4: Displaying Image Thumbnails.

We'll be adding a Date List button to Screen 1 (Content list). A list, sorted by date, will be displayed when it's tapped.

When the user taps on an item, Screen 4 (the Operation Screen) will be displayed.

Writing the Code!

Screen1:Creating the Content List

First lets add the "Date List" button to iOS Tutorial 4: Displaying Image Thumbnails. Next, add a sort condition so we only see image files.
See the sample code for more details!

Screen2:Creating the Image Viewer

This file will be identical to the copy in iOS Tutorial 3: Downloading Content.
Please refer to that tutorial for an explanation of the implementation.

Screen3:Creating the Date List

The "Date List" will display images in the selected folder (from the Content List), as well as a saved date and title. Notes and title information will be saved in the same folder as the image, stored in a timestamped file (YYYYMMDD.txt). The title will be saved as the top line, and notes will be on the second. This file will be saved by screen 5 (the "Opertaion Screen"). See below for details.

First, lets get the date information for the images.

FSDatelistViewController.m (1)

NSMutableDictionary *ret = nil;

// Find unique dates of files
NSMutableSet *dates = [NSMutableSet set];
for (NSString *fileInfo in self.files) {
    NSArray *tokens = [fileInfo componentsSeparatedByString:@","];

    // Skip if it is the signature line or an empty line or not image file.
    NSString *dir = [tokens objectAtIndex:0];
    NSString *filename = [tokens objectAtIndex:1];
    NSString *ext = [[filename pathExtension] lowercaseString];
    if([dir isEqualToString:@""] || [dir isEqualToString:@"WLANSD_FILELIST\r"] ||
       !([ext isEqualToString:@"jpg"] || [ext isEqualToString:@"jpeg"] ||
        [ext isEqualToString:@"png"] || [ext isEqualToString:@"jpe"])){
        continue;
    }

    // Add date.
    [dates addObject:[tokens objectAtIndex:4]];
}

Next, the title!

FSDatelistViewController.m (2)

// Load existing memo.
ret = [NSMutableDictionary dictionary];
NSDateFormatter *toFormatter = [[NSDateFormatter alloc] init];
[toFormatter setDateFormat:@"yyyyMMdd"];        //for filename
NSDateFormatter *tostrFormatter = [[NSDateFormatter alloc] init];
[tostrFormatter setDateFormat:@"yyyy/MM/dd"];   //for show display
for (NSString *date in dates) {
    NSError *error = nil;

    // Convert date number to string.
    NSString *sDate= [toFormatter stringFromDate:[self dateAsDate:date]];
    NSString *strDate= [tostrFormatter stringFromDate:[self dateAsDate:date]];

    // Make up URL to the memo file.
    NSURL *url=[NSURL URLWithString:[NSString stringWithFormat:@"http://flashair%@/%@.txt",
                                                                            self.path, sDate]];

    // Get memo contents from the FlashAir.
    NSString *title = @"";
    NSString *body = @"";
    NSString *memoData =[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding 
                                                                                 error:&error];
    if ( ![error.domain isEqualToString:NSCocoaErrorDomain] && [memoData rangeOfString:@"DOCTYPE" 
                                         options:NSCaseInsensitiveSearch].location == NSNotFound) {
        // Success to get existing memo.
        NSArray *data = [memoData componentsSeparatedByString:@"\n"];
        title = [data objectAtIndex:0];
        body = [data objectAtIndex:1];
    }

    // Add this memo to the list.
    NSDictionary *memo = [NSDictionary dictionaryWithObjectsAndKeys:
                          title, @"title",
                          body, @"body",
                          strDate, @"date",
                          nil];
   [ret setObject:memo forKey:sDate];
}
  • In lines 38-49 We create a file path based on the date that we get, and then download the file.

Screen4:Creating the Operation Screen

This screen will edit and save the YYYYMMDD.txt file (which holds the title and notes of each date).

Uploading

In order to save our file to the FlashAir, we need to use upload.cgi.

  • To upload, follow these steps:
    1. Write UPLOAD=1 in the CONFIG file
    2. Restart the FlashAir
    3. Connect to the FlashAir via Wireless LAN
    4. Restrict the write ability of host devices using the WRITEPROTECT command
    5. Set the upload directory with the UPDIR command
    6. Set the file creation date using the FTIME command
    7. POST your file to upload.cgi!

Steps 1-3 need to be done manually, and then we'll continue with steps 4-6.

FSMemoEditViewController.m (1)

// Set Write-Protect and upload directory and System-Time
// Make System-Time
NSDate *systemdate = [NSDate date];
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateCompnents;
dateCompnents =[calendar components:NSYearCalendarUnit
                                    | NSMonthCalendarUnit
                                    | NSDayCalendarUnit
                                    | NSHourCalendarUnit
                                    | NSMinuteCalendarUnit
                                    | NSSecondCalendarUnit fromDate:systemdate];

NSInteger year =([dateCompnents year]-1980) << 9;
NSInteger month = ([dateCompnents month]) << 5;
NSInteger day = [dateCompnents day];
NSInteger hour = [dateCompnents hour] << 11;
NSInteger minute = [dateCompnents minute]<< 5;
NSInteger second = floor([dateCompnents second]/2);

NSString *datePart = [@"0x" stringByAppendingString:[NSString stringWithFormat:@"%x%x" ,
                                                    year+month+day,hour+minute+second]];

// Make Filename
NSString *filename=[[self.date stringByReplacingOccurrencesOfString:@"/" withString:@""]
                                                     stringByAppendingString :@".txt"];

// Make url
NSString *urlStr = @"http://flashair/upload.cgi";
urlStr = [urlStr stringByAppendingString:@"?WRITEPROTECT=ON&UPDIR="];
urlStr = [urlStr stringByAppendingString:self.path];
urlStr = [urlStr stringByAppendingString:@"&FTIME="];
urlStr = [urlStr stringByAppendingString:datePart];
NSURL *url = [NSURL URLWithString:urlStr];
// Run cgi
NSError *error;
NSString *rtnStr =[NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:&error];
if ([error.domain isEqualToString:NSCocoaErrorDomain]){
    NSLog(@"upload.cgi %@\n",error);
    return;
}else{
    if(![rtnStr isEqualToString:@"SUCCESS"]){
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:self.title
                                                    message:@"upload.cgi:setup failed" delegate:nil
                                              cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
        [alert show];
        return;
    }
}
  • Lines 3-21 generate a date string from the current date to use as the creation date for our file. It creates our date and timestamp as 16-bit integers (hex values), and then concatenates them to match the parameters of the request.

Next, step 7!.

FSMemoEditViewController.m (2)

// File upload
// Make Data
self.memoTitle = self.textFieldTitle.text;
self.memoBody = self.textViewMemo.text;
NSString *memoData = [[self.memoTitle stringByAppendingString:@"\n"] 
                                                            stringByAppendingString:self.memoBody];
NSData *textData=[memoData dataUsingEncoding:NSUTF8StringEncoding ];

//url
url=[NSURL URLWithString:@"http://flashair/upload.cgi"];

//boundary
CFUUIDRef uuid = CFUUIDCreate(nil);
CFStringRef uuidString = CFUUIDCreateString(nil, uuid);
CFRelease(uuid);
NSString *boundary = [NSString stringWithFormat:@"flashair-%@",uuidString];

//header
NSString *header = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary];

//body
NSMutableData *body=[NSMutableData data];
[body appendData:[[NSString stringWithFormat:@"--%@\r\n",boundary] 
                                                        dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"file\";
                         filename=\"%@\"\r\n",filename] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[[NSString stringWithFormat:@"Content-Type: text/plain\r\n\r\n"]
                                                        dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:textData];
[body appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary]
                                                        dataUsingEncoding:NSUTF8StringEncoding]];


//Request
NSMutableURLRequest *request =[NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request addValue:header forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:body];

NSURLResponse *response;

NSData *result = [NSURLConnection sendSynchronousRequest:request
                                       returningResponse:&response
                                                   error:&error];
rtnStr=[[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];

if ([error.domain isEqualToString:NSCocoaErrorDomain]){
    NSLog(@"upload.cgi %@\n",error);
    return;
}else{
    if([rtnStr rangeOfString:@"Success"].location==NSNotFound){     //v2.0
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:self.title
                                        message:@"upload.cgi: POST failed" delegate:nil
                                            cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
        [alert show];
        return;
    }
}
  • In line 19
    We set the multipart/form-data format.

Result

Let's set a title and save some notes!

Tap the Date List button.

(This image shows the result)

Tap 2013/03/03.

(This image shows the result)

The operation screen for 2013/03/03 is displayed.

(This image shows the result)

Enter a title and some notes, then tap the Done button.

(This image shows the result)

When you return to the previous screen, you'll see your changes have been saved!

(This image shows the result)

Sample Code

ios_tutorial_07.zip (62KB)

All sample code on this page is licensed under BSD 2-Clause License