Raspberry Pi Temperature Sensor Web Server – Part 4 (iOS Swift App) — May 28, 2015

Raspberry Pi Temperature Sensor Web Server – Part 4 (iOS Swift App)

So we have our Raspberry Pi web server which gives us a lovely set of json temperature data. What would be great though is an app that could display this data whenever we want. So lets do just that. Once again all of the source code can be found in my Github Repo.

Create a new single view project in Xcode, selecting Swift as the language. Open the storyboard and add some labels to the ViewController for the temperature output and date ( I used two for the date, one just as a title). Mine is below. I disabled size classes for this.

Screen Shot 2015-05-21 at 20.37.40

Using assistant editor, ctrl drag the temp and date labels into ViewController file to create IBOutlets for UIlabels. I called mine currentTempLabel and lastUpdateLabel.

Although we could put all the code we need for this project in the VIewController it would probably be better to abstract away all of the code we will use to get the json data from the Raspberry Pi. So make a new Swift file that subclasses NSObject calledTemperatureWebService. Copy the below code into that new file, remembering to swap the IP address for yours!

import Foundation
import UIKit

protocol TemperatureWebServiceDelegate
	
{
	func temperatureReceived(temperature: String, date: String)
}

class TemperatureWebService: NSObject, NSURLConnectionDelegate
{
	
	var delegate: TemperatureWebServiceDelegate?
	
	var data = NSMutableData()
	var jsonResult: NSArray = []

	func startConnection()
	{
		let urlPath: String = "http://192.168.0.11/temperaturejson.php"
		var url: NSURL = NSURL(string: urlPath)!
		var request: NSURLRequest = NSURLRequest(URL: url)
		
		var connection: NSURLConnection = NSURLConnection(request: request, delegate: self, startImmediately: true)!
	}
	
	func connection(connection: NSURLConnection!, didReceiveData data: NSData!)
	{
		self.data.appendData(data)
	}
	
	func connectionDidFinishLoading(connection: NSURLConnection!)
	{
		var err: NSError
		jsonResult = NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions.MutableContainers, error: nil) as! NSArray
		
		getLatestTempReading()
	}
	
	func getLatestTempReading()
	{
		var dictionary: NSDictionary = jsonResult.lastObject as! NSDictionary
		var tempValue = dictionary.objectForKey("Temp") as! String
		var dateValue = dictionary.objectForKey("Date") as! String
		
		
		if (delegate != nil)
		{
			delegate?.temperatureReceived(tempValue, date: dateValue)
		}
	}
}

This class is using NSURLConnection to get the data from our web service. We provide a function startConnection() that we can call from other classes that will do as it says. It will start the NSURLConnection for our IP address. We implement the NSURLConnectionDelegate and its delegate functions – using connection to append data to our NSMutableData object and connectionDidFinishLoading to know when we have all the data and to serialise our data into JSON.

When we have the data we call getLatestTempReading() to put the last temp reading from the JSON into a dictionary and call the corresponding objects for keys for temperature and date.

I have then opted to use a delegate method to give the data back to the object that owns the TemperatureWebService object as two String objects.

Now copy the below code into your ViewController class.

class ViewController: UIViewController, TemperatureWebServiceDelegate
{
	@IBOutlet weak var currentTempLabel: UILabel!
	@IBOutlet weak var lastUpdateLabel: UILabel!
	
	override func viewWillAppear(animated: Bool)
	{
		super.viewWillAppear(animated)
		
		var webService = TemperatureWebService()
		webService.delegate = self
		webService.startConnection()
}
	
	func temperatureReceived(temperature: String, date: String)
	{
		currentTempLabel.text = "\(temperature) °C"
		lastUpdateLabel.text = "\(date)"
	}
}

As all of the hard work is done in the web service class our ViewController is incredibly simple. We firstly have two IBOutlets for our labels and then we override viewWillAppear and create a TemperatureWebService object, which we call startConnection() on. Then we implement the TemperatureWebServiceDelegate and its associated method which will be called when the web service has received data, serialised it and found the last temperature reading. We then simply set our labels to show the two strings given to us by the web service.

NOTE: If you are using iOS9 changes in App Security stop you accessing http web addresses. To fix this do what is detailed here.

When working it should look like this.

Screen Shot 2015-05-21 at 22.31.53

Next time we will make a graph to show all of our data that has been collected.

Raspberry Pi Temperature Sensor Web Server – Part 3 (Scheduling temperature readings and PHP script) — May 18, 2015

Raspberry Pi Temperature Sensor Web Server – Part 3 (Scheduling temperature readings and PHP script)

Now wouldn’t it be nice to have our python script work autonomously, putting a temperature reading into the database every 5 minutes. To do this we will use Crontab, which is a handy unix tool to schedule jobs. A good explanation of what crontab is can be found here.

To open Crontab enter the following in your terminal.

crontab -e

Inside the Crontab file enter the following at the bottom. This simply runs our Python script every 5 minutes. If you have used different file and folder names, adjust them accordingly.

*/5 * * * * /home/pi/tempLog/readTempSQL.py

At its current state this will not work though because the readTempSQL.py script isn’t yet executable so the Cronjob will fail. To make the Python script executable, firstly enter the following at the top of the readTempSQL.py file.

#!/usr/bin/env python

This is a ‘shebang’ line, which Wikipedia will tell you “In computing, a shebang (also called a hashbang, hashpling, pound bang, or crunchbang) refers to the characters “#!” when they are the first two characters in an interpreter directive as the first line of a text file. In a Unix-like operating system, the program loader takes the presence of these two characters as an indication that the file is a script, and tries to execute that script using the interpreter specified by the rest of the first line in the file.”

Now we just need to change the file permission to executable.

sudo chmod +x readTempSQL.py

To test that the file is now executable, navigate to your tempLog directory and enter

./readTempSQL.py

The script should run and print an output to the terminal window. Congratulations the file is now executable. Check your database after 10 minutes or so to make sure your Cronjob is working as when the script is run by Crontab the output is not displayed in the terminal window.

Now lets make a php script to return our database data. Change directory to /var/www
and make a new file called temperaturejson.php.

sudo nano temperaturejson.php

Enter the following into that new file.

<?php
 
$username="root";
$password="password";
$database="temp_database";
 
mysql_connect(localhost,$username,$password);
@mysql_select_db($database) or die( "Unable to select database");
 
$query="SELECT * FROM tempLog";
$result=mysql_query($query);
 
$num=mysql_numrows($result);
 
mysql_close();
 
$tempValues = array();
 
$i=0;
while ($i < $num)
{
        $dateAndTemps = array();
        $datetime = mysql_result($result,$i,"datetime");
        $temp = mysql_result($result,$i,"temperature");
 
        $dateAndTemps["Date"] = $datetime;
        $dateAndTemps["Temp"] = $temp;
 
        $tempValues[$i]=$dateAndTemps;
        $i++;
}
 
echo json_encode($tempValues);
 
?>

This new php script gets the data from our database and then loops through the results putting them into an array where the key is either “Date” or “Temp” and the corresponding object is the result from the database.You don’t really need to give the objects keys but I think it makes it much easier when getting the data into the app. These arrays are then added to a larger array holding each reading of Date and Temp, which is then encoded into json and output using echo.

Now test this out. Using any browser go to the following address (substituting my IP for yours of course!). If you don’t get the desired output firstly check you can get to the test page on the Raspberry Pi. If you can try changing the echo to just output a string to test the page. Then check your database to make sure you actually have data to display.

http://192.168.0.11/temperaturejson.php

If all has worked correctly you will get an output similar to this

[{“Date”:”2014-12-28 17:26:20″,”Temp”:”18.90″},{“Date”:”2014-12-28 17:27:05″,”Temp”:”18.90″},{“Date”:”2014-12-28 17:27:52″,”Temp”:”18.90″},{“Date”:”2014-12-28 17:30:39″,”Temp”:”19.00″},{“Date”:”2014-12-28 17:31:02″,”Temp”:”18.90″},{“Date”:”2015-01-04 22:29:24″,”Temp”:”18.60″},{“Date”:”2015-05-14 20:56:07″,”Temp”:”21.80″},{“Date”:”2015-05-17 19:55:05″,”Temp”:”22.90″},{“Date”:”2015-05-17 19:56:17″,”Temp”:”22.90″},{“Date”:”2015-05-17 20:06:18″,”Temp”:”23.00″},{“Date”:”2015-05-17 20:47:03″,”Temp”:”23.20″}]

That is a wrap for the Raspberry Pi stuff. Now we move on to making an iOS app to do something with our data.

Centre UICollectionView Cells Horizontally iOS — May 5, 2015

Centre UICollectionView Cells Horizontally iOS

Or Centre UICollectionView Cells Horizontally iOS for US dudes.

I had to do this for a project recently so thought I would share.

Create a single view project in Objective C and open the Storyboard. For ease of this example turn Auto Layout and Size Classes off using the iPad size for the view. This can be done in File Inspector View on the right.

Screen Shot 2015-04-28 at 21.44.02

In the general settings under Deployment info, un-tick the boxes for portrait and upside down so the app can only be displayed horizontally.

Next, setup the View Controller simulated metrics in the Attribute Inspector as follows.

Screen Shot 2015-04-28 at 21.44.25

Add a UICollectionView to the ViewController and make it 1000 in width and 300 in height. Try to centre it in the middle of the View.

Screen Shot 2015-04-28 at 21.47.50

 

Change the scroll direction to horizontal in the Attribute Inspector for the UICollectionView. Make sure items is set to 1 while you are there so that a prototype cell stays in the collection.

Screen Shot 2015-04-28 at 21.58.06

Now make the CollectionViewCell 200 in width and 250 in height

Screen Shot 2015-04-28 at 21.50.14

I would now add some views and labels to the default prototype cell so that what you are working with is realistic. Here is my collection View and cell.

Screen Shot 2015-04-28 at 22.01.40

With the UICollectionView selected, go into Connections Inspector and connect the Delegate to the View Controller.

Screen Shot 2015-04-28 at 22.04.38

We need to implement the UICollectionViewDelegate. Add this to the ViewController.h

 

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UICollectionViewDelegate>

@end

Now we will add two methods from the UICollectionViewDelegate. These methods will simply tell the UICollectionView how many collection cells we’re displaying and what cells to display. Pretty much the same as a tableView Delegate.

#import "ViewController.h"

@implementation ViewController
{
	NSInteger _numberOfCells;
}

- (void)viewDidLoad
{
	[super viewDidLoad];
	_numberOfCells = 3;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{

	return _numberOfCells;
}

-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{

	UICollectionViewCell *cell = (UICollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];

	return cell;
}

Now build and run the project. You should see something similar to the below. If not, put a breakpoint in the delegate methods and make sure they are called and the delegate is setup correctly.

Screen Shot 2015-05-04 at 20.16.07

Now lets add another method to centre the 3 UICollectionViewCells. You will notice I have hardcoded in all of the width values but that is just to make this more understandable, it should be quite simple to get the values at run time programmatically. So you should now adapt your ViewController to look like the below.

#import "ViewController.h"

#define CELL_WIDTH 200
#define CELL_SPACING 10
#define COLLECTIONVIEW_WIDTH 1000

@implementation ViewController
{
	NSInteger _numberOfCells;
}

- (void)viewDidLoad
{
	[super viewDidLoad];
	_numberOfCells = 3;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{

	return _numberOfCells;
}

-(UICollectionViewCell*)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{

	UICollectionViewCell *cell = (UICollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath];

	return cell;
}

- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout*)collectionViewLayout insetForSectionAtIndex:(NSInteger)section
{
	NSInteger viewWidth = COLLECTIONVIEW_WIDTH;
	NSInteger totalCellWidth = CELL_WIDTH * _numberOfCells;
	NSInteger totalSpacingWidth = CELL_SPACING * (_numberOfCells -1);

	NSInteger leftInset = (viewWidth - (totalCellWidth + totalSpacingWidth)) / 2;
	NSInteger rightInset = leftInset;

	return UIEdgeInsetsMake(0, leftInset, 0, rightInset);
}

@end

There is no reason to use that many lines of code or to have both a left and right inset other than simplicity for this demonstration. That method can be reduced to a few lines at most. Run the project again and you should have a centred UICollectionView like below.

Screen Shot 2015-05-05 at 20.26.22