Sample code for the Pappito Remote Controller iPhone App

Jon Richards - JDRichards Home

I developed an iPhone application for Ecofront, a company in Japan. This app was a remote control for the home automation controller the company produced. Using this remote control app you could control the TV, DVD/CD player/recorder, Satellite TV, Webcams, and control devices in your home.

Developing this app involved a lot of iOS TCP/IP programming.

I encompassed all the network related code into a single class called NetworkUtils. As a result, the rest of the code was actually trivial. For example see TVControllerView.

In the interface Builder I assigned the titles of the buttons in disabled state (they never are in disabled state) to be the code that is sent to the controlleras a result of the keypress. As a result, the rest of the code is pretty trivial, even though a lot of capability is acheived.

Screenshots:

(back to top...)

Here is the source code to the NetworkUtils class:


The implementation file:

(back to top...)


//
//  NetworkUtils.m
//  ArgyleIR2
//
//  Created by jrichards on 2/2/11.
//  Copyright 2011 Argyle Home Tech/Ecofront. All rights reserved.
//

#import "NetworkUtils.h"

/**
 * The class implementation. This class handles ALL network activity
 * for this application. Only one instance of this class should exist
 * in this application. A reference to this class instance should be
 * obtained by calling the sharedInstance method.
 */
@implementation NetworkUtils

/**/
@synthesize ipAddress;
@synthesize ipPort;
@synthesize password;
@synthesize isLoggedIn;
@synthesize sessionId;
@synthesize loggedInStateString;
@synthesize rootController;
@synthesize commandList;
@synthesize repeatingTimer;

static NetworkUtils *_sharedInstance = nil;

/**
 * Initialze this instance.
 * @returns this instance.
 */
- (id) init
{
	if ((self = [super init]))
	{
		// custom initialization
		ipAddress = nil;
		ipPort    = 0;	
		password  = nil;
		busy      = FALSE;
		isLoggedIn = FALSE;
		loggedInStateString = @"Not Logged In";
		commandList = [[[NSMutableArray alloc] init] retain];
		[self retrieveSettings];
		
		//Start the timer
		repeatingTimer = [[NSTimer scheduledTimerWithTimeInterval:0.5
			target:self selector:@selector(commandTimer:)
			userInfo:nil repeats:YES] retain];
		
		srand(time(0));

	}
	return self;
}


/**
 * Static function to get the instance pointer. A reference to this 
 * class instance should be btained by calling this method.
 */
+ (NetworkUtils *) sharedInstance
{
    //If not created yet - allocate it.
	if (!_sharedInstance)
	{
		_sharedInstance = [[NetworkUtils alloc] init];
	}
	
	return _sharedInstance;
}

/**
 * Get the data path for persistent storage.
 * @returns the path to the persistent storage.
 */
-(NSString *)dataFilePath 
 {
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	NSString *documentsDirectory = [paths objectAtIndex:0];
	return [documentsDirectory stringByAppendingPathComponent:kFilename];
}

/**
 * Retrieve settings from persistent storage.
 * Returns TRUE if data was retrieved. FALSE if it already has been retrieved.
 */
-(BOOL) retrieveSettings
{
	if(dataRetrieved == TRUE) return FALSE;
	
	
	NSString *filePath = [self dataFilePath];
	
	if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) 
	{
		NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
		self.ipAddress = [array objectAtIndex:0];
		self.ipPort    = [array objectAtIndex:1];
		self.password  = [array objectAtIndex:2];
		[array release];
	}
	
	dataRetrieved = TRUE;
	return TRUE;
	
}

/**
 * Save settings to persistent storage.
 * @returns TRUE always.
 */
-(BOOL) saveSettings
{
	NSMutableArray *array = [[NSMutableArray alloc] init];
	[array addObject:ipAddress];
	[array addObject:ipPort];
	[array addObject:password];
	[array writeToFile:[self dataFilePath] atomically:YES];
	[array release];
	
	dataRetrieved = TRUE;
	return TRUE;
}


/**
 * Store a key press into the keypress list.
 * @params keypress the string representing the key pressed.
 */
-(void) addKeyPress:(NSString *)keypress
{
	[commandList addObject:keypress];
        NSLog(@"++++Adding key %@", keypress);
	
}

/**
 * Timer method than periodically handles key presses stored in
 * the commandList array.
 * @params theTimer the timer causing this method entry.
 */
- (void)commandTimer:(NSTimer*)theTimer
{
	
	@synchronized(self)
	{
		if(busy) return;
		
		if([commandList count] > 0)
		{
                        // If not logged in, remove all pending key presses, display login
                        // view, and return.
			if(isLoggedIn != TRUE)
			{
				//Remove all keypresses
				[commandList removeAllObjects];
						
				[self switchToLogin];
				return;
			}
			else 
			{
                                //Generate a random number as an argument to avoid potential
                                //conflicts with the cache (I saw it happen!).
				int r = arc4random();
                
                                //Create the command part of the URL.
				NSString *url =  [[NSString stringWithFormat:@"rSremote.cgi?v=%@&s=%d&r=%d", [commandList objectAtIndex:0], sessionId, r] retain];
						
				//Remove the keypress from the list.		
				NSLog(@"----Removing key %@", [commandList objectAtIndex:0]);
				[commandList removeObjectAtIndex:0];
					
                //Process the keypress by sending it to the controller.
				[self urlRequest: url];
				[url release];
						
			}
		}
	}
}

/**
 * Switch to the login screen.
 */
- (void)switchToLogin {
	
	//Switch to login view
	self.rootController.selectedIndex = 5;
	
	UIViewController *selectViewController = [rootController.viewControllers objectAtIndex:8];
	[rootController setSelectedViewController:rootController.moreNavigationController];
	[rootController.moreNavigationController popToRootViewControllerAnimated:NO];//make sure we're at the top level More
	[rootController.moreNavigationController pushViewController:selectViewController animated:YES];
	rootController.customizableViewControllers=nil;
	return;
}

/**
 * Perform a login.
 * @returns TRUE if request was sent to the controller.
 */
- (BOOL )login {
	

	isLoggedIn = FALSE;
    loggedInStateString = @"Logging in. Please wait...";

    //Compose the request
	NSString *url =  [[NSString stringWithFormat:@"login.cgi?vPassword=%@", password] retain];
	
	BOOL result = [self urlRequest: url];
	
	[url release];
	
	return result;
	
}


/**
 * Send a request and get a server.
 * See http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/URLLoadingSystem/Tasks/UsingNSURLConnection.html
 * for good information on how to do this.
 * @returns FALSE if the connection can not be made.
 */
- (BOOL)urlRequest:(NSString *)url
{
	
    if(busy == TRUE) return FALSE;
    else busy = TRUE;

	
	//Compose the full URL request
	theURL = [[NSString stringWithFormat:@"http://%@:%@/%@", ipAddress, ipPort, url] retain];
	
	NSLog(@"urlRequest:%@", theURL);
	
	// Create the request.
	NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:theURL]
								cachePolicy:NSURLRequestUseProtocolCachePolicy
                                timeoutInterval:5.0];
    
	// create the connection with the request
	// and start loading the data
	NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
	
	if (theConnection) 
    {
		// Create the NSMutableData to hold the received data.
		// receivedData is an instance variable declared elsewhere.
		receivedData = [[NSMutableData data] retain];
	} 
    else 
    {
		// Inform the user that the connection failed.1
		@synchronized(self)
		{
			busy = FALSE;
			loggedInStateString = @"Connection Failure";
			isLoggedIn = FALSE;
		}

		return FALSE;
		[theURL release];
	}
	 
					 
	return TRUE;
}

/**
 * A response is being received. Clear the receivedData.
 * @params response the response.
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
	// This method is called when the server has determined that it
	// has enough information to create the NSURLResponse.
		
	// It can be called multiple times, for example in the case of a
	// redirect, so each time we reset the data.
		
	// receivedData is an instance variable declared elsewhere.
	NSLog(@"didReceiveResponse");
	[receivedData setLength:0];
}

/**
 * Data Received. For large data there can be multiple calls to this methos
 * for a single response - so append to the receivedData.
 * @params data - the data received.
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
	// Append the new data to receivedData.
	// receivedData is an instance variable declared elsewhere.
	NSLog(@"didReceiveData");
	[receivedData appendData:data];
}
					 
/**
 * Handle a failure. Set the logged in state to FALSE.
 * @params error the error.
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
	// release the connection, and the data object
	[connection release];
	// receivedData is declared as a method instance elsewhere
	[receivedData release];
	
	NSLog(@"didFailWithError");
	
	
    busy = FALSE;
    loggedInStateString = @"Connection Failure";
    isLoggedIn = FALSE;
		
}
	
/**
 * Finished loading. Process the response. 
 * @params connection the connection that is finished loading.
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
	// do something with the data
	// receivedData is declared as a method instance elsewhere
	NSLog(@"Succeeded! Received %d bytes of data",[receivedData length]);
	
	NSString *aStr = [[[NSString alloc] initWithData:receivedData encoding:NSASCIIStringEncoding] retain];
	
	NSLog(@"connectionDidFinishLoading; %@", aStr);
	
	NSArray *arr = [[aStr componentsSeparatedByCharactersInSet:
		[NSCharacterSet characterSetWithCharactersInString:@","]] retain];
	
	if(arr != nil && [arr count] > 1)
	{
		//If this is a login result
		if ([[arr objectAtIndex:0] isEqual:@"LOGIN"] == YES)
		{
			//If this is a login error
			if([[arr objectAtIndex:1] isEqual:@"ERROR"] == YES)
			{
				loggedInStateString = @"Login error. Please check if the controller is on-line and your login parameters are correct.";
                                isLoggedIn = FALSE;
		
			}
			else if([[arr objectAtIndex:1] isEqual:@"FALSE"] == YES)
			{
				loggedInStateString = @"You are not logged in, please log in.";
                                isLoggedIn = FALSE;
			}
			else 
			{
				int sid = [[arr objectAtIndex:1] intValue];
				if(sid > 0)
				{
					loggedInStateString = @"You are logged in.";
					sessionId = sid;
					isLoggedIn = TRUE;
				}
				else 
				{
					loggedInStateString = aStr;
                    isLoggedIn = FALSE;
				}
				

			}
			
		}
		
		if ([[arr objectAtIndex:0] isEqual:@"OK"] == YES)
		{
		}
	}

	

		
	// release the connection, and the data object
	[connection release];
	[receivedData release];
	[arr release];
	[aStr release];
    
	busy = FALSE;

}
					 
/**
 * Cleanup
 */
- (void) dealloc
{
    if(ipAddress != nil)
	{
		[ipAddress release];
	}
	if(password != NULL)
	{
		[password release];
	}
    [super dealloc];
}

@end

The interface file:

(back to top...)


//
//  NetworkUtils.h
//  ArgyleIR2
//
//  Created by jrichards on 2/2/11.
//  Copyright 2011 Argyle Home Tech/Ecofront. All rights reserved.
//

#import 

#define NETWORK_ERROR -1
#define NETWORK_RESULT 1

/** The name of the persistent data file. */
#define kFilename @"data2.plist"


@interface NetworkUtils : NSObject 
{
	
	NSString *ipAddress;
	NSString *ipPort;
	NSString *password;
	
	NSMutableData *receivedData;
	NSString *theURL;
	BOOL busy;
	NSMutableArray *commandList;
	BOOL dataRetrieved;
	BOOL isLoggedIn;
	NSString *loggedInStateString;
	
	NSTimer *repeatingTimer;
	int sessionId;
	UITabBarController *rootController;
}

/////////////
// Properties
/////////////
@property (retain) NSString* ipAddress;
@property (retain) NSString* ipPort;
@property (retain) NSString* password;
@property (retain) NSMutableArray* commandList;
@property (retain) NSTimer* repeatingTimer;
@property  BOOL isLoggedIn;
@property (retain) NSString* loggedInStateString;
@property int sessionId;
@property (retain) UITabBarController* rootController;

/////////////////
// Static Methods
/////////////////
+ (NetworkUtils *) sharedInstance;

///////////////////
// Instance Methods
///////////////////
- (id) init;
- (BOOL )login;
- (BOOL)urlRequest:(NSString *)url;
- (BOOL)retrieveSettings;
- (BOOL)saveSettings;
- (void)addKeyPress:(NSString *)keypress;
- (void)commandTimer:(NSTimer*)theTimer;
- (void)switchToLogin;
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;

-(NSString *)dataFilePath;


@end

////////////
// Delegates
////////////
@protocol NetworkUtilsDelegate
- (void)NetworkResult:(int) result string: (NSString *)string;
@end

TVControllerView:

(back to top...)
//
//  TVViewController.m
//  ArgyleIR2
//
//  Created by jrichards on 2/2/11.
//  Copyright 2011 Argyle Home Tech/Ecofront. All rights reserved.
//

#import "TVViewController.h"

@implementation TVViewController


// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad 
{
    nu = [NetworkUtils sharedInstance];
    [super viewDidLoad];
}

- (void)didReceiveMemoryWarning 
{ 
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];
    
    // Release any cached data, images, etc that aren't in use.
}

-(void)viewWillAppear:(BOOL)animated
{
	[nu addKeyPress:@"C2h"]; //C2h tells the controller to put itself into TV control mode
	[super viewWillAppear:animated];
}


- (void)viewDidUnload 
{ 
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}

//Button pressed - send its disabled state title to the controller
- (IBAction)buttonPressed:(id)sender 
{
	
    NSString *hint = [sender titleForState:UIControlStateDisabled];
    [nu addKeyPress:hint]; 
    [hint release];
	
}

- (void)NetworkResult:(int) result string: (NSString *)string
{

}


- (void)dealloc {
    [super dealloc];
}


@end