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.
Here is the source code to the NetworkUtils class:
//
// 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
// // 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
//
// 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