HealthKit for iOS8: Part 7

iOS 8 HealthKit Santiapps Marcio Valenzuela

iOS 8 HealthKit

4. CoreData for other non-health stats

You made it to the end!  Ok, so we are basically going to be adding another store to our app and reading and writing data to THAT store as well.

First let’s add a new tab and make it a UITableViewController as well.  It will have dynamically populated cells.

HealthKit for iOS8

HealthKit for iOS8

Now embed it!

HealthKit for iOS8

HealthKit for iOS8

 

Your final storyboard should look like this:

HealthKit for iOS8

HealthKit for iOS8

 

Add a new Swift class called Swimming Data and set that new UITableViewController scene to its class.  Make that class file look like this:

import Foundation

import UIKit

class SwimmingData: UITableViewController {

}

Now we must add CoreData.  To do this we need to create a CoreData stack and a xcdatamodeld file.  First thing is first, let’s add the xcdatamodeld file by New->File->CoreData->DataModel.  Name it SwimModel.  Create an entity called Swim and add the following attributes:

  • pace : Int16
  • date : Date
  • laps : Int16
  • meters : Double
  • totalTime : Double

Now with the xcdatamodeld file selected, go to Editor and select Create NSManagedObject subclass:

HealthKit for iOS8

HealthKit for iOS8

 

Make sure SwimModel is selected, click Next, make sure to select the Swim entity, click Next and you should get a Swim.swift class like this:

import Foundation
import CoreData
class Swim: NSManagedObject {

@NSManaged var date: NSDate
@NSManaged var laps: NSNumber
@NSManaged var totalTime: NSNumber
@NSManaged var meters: NSNumber
@NSManaged var pace: NSNumber
}

Perfect!  All you need now is your stack!  To do this, again create a New->File->Source->Swift File-> and name it CoreDataStack.  Now replace everything in there with this:

import CoreData

class CoreDataStack {

let context:NSManagedObjectContext
let psc:NSPersistentStoreCoordinator
let model:NSManagedObjectModel
let store:NSPersistentStore?

 

init() {

//1

let bundle = NSBundle.mainBundle()

let modelURL = bundle.URLForResource("SwimModel", withExtension:"momd")

model = NSManagedObjectModel(contentsOfURL: modelURL!)!

 

//2

psc = NSPersistentStoreCoordinator(managedObjectModel:model)

 

//3

context = NSManagedObjectContext()

context.persistentStoreCoordinator = psc

 

//4

let documentsURL = applicationDocumentsDirectory()

let storeURL = documentsURL.URLByAppendingPathComponent("SwimFit4")

 

let options = [NSMigratePersistentStoresAutomaticallyOption: true]

 

var error: NSError? = nil

store = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error:&error)

 

if store == nil {

println("Error adding persistent store: \(error)")

abort()

}

}

 

func saveContext() {

var error: NSError? = nil

if context.hasChanges && !context.save(&error) {

println("Could not save: \(error), \(error?.userInfo)")

}

}

 

func applicationDocumentsDirectory() -> NSURL {

let fileManager = NSFileManager.defaultManager()

 

let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) as [NSURL]

return urls[0]

}

}

Now since this is not a CoreData tutorial, I will not go into the details, but every CoreData project needs a MOM, MOC and PSC.  That is what we initialize here.

Now we can begin writing to our MOC and PSC.  To test run it, let’s hardcode a value.  Go to the SwimmingData Class and first give it an import CoreData at the top.  Now declare a property for your stack inside your class of course:

lazy var coreDataStack = CoreDataStack()

var workouts = NSMutableArray()

We are creating a CoreDataStack instance and we are creating a mutable array.

Then give it a viewDidLoad method like this:

 

override func viewDidLoad() {

super.viewDidLoad()

//Create Sample Swim object

var description = NSEntityDescription.entityForName("Swim", inManagedObjectContext:coreDataStack.context)

var sampleSwim = Swim(entity:description!, insertIntoManagedObjectContext:coreDataStack.context)

sampleSwim.laps = 24

sampleSwim.meters = 50

sampleSwim.totalTime = 40

sampleSwim.pace = 6

sampleSwim.date = NSDate()

coreDataStack.saveContext()

 

//Add object to array

self.workouts.addObject(sampleSwim)

 

//Refresh UI

self.tableView.reloadData()

}

Now add an identifier for the cell like so:

let JournalViewControllerTableViewCellReuseIdentifier: NSString = "Cell"

as a property at the top of the class.

So of course don’t forget to set the identifier in your storyboard scene.  Finally, implement both datasource methods:

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return self.workouts.count

}

override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {

 

let cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath!) as UITableViewCell

 

var myWorkout: Swim = self.workouts[indexPath!.row] as Swim

let dateFormatter = NSDateFormatter()

dateFormatter.dateFormat = "yyyy'-'MM'-'dd HH':'mm':'ss"

let date = dateFormatter.stringFromDate(myWorkout.date as NSDate)

println(date)

cell.textLabel.text = date

 

cell.detailTextLabel!.text = myWorkout.totalTime.stringValue

 

return cell;

}

And don’t forget to set your cell type to Right Detail in your storyboard.  If you Build & Run and switch to the newly created tab, you might get a crash saying:

Unable to load class Swim …

This is because you need to fully qualify the class name in CoreData, so select your xcdatamodeld file and with your Swim entity selected, make sure to append the Class name in the inspector on the right like so:

HealthKit for iOS8

HealthKit for iOS8

Basically you need to ensure that you append your project name to the Class name field.

Now run your app and go over to the Workouts tab and see your hardcoded workout in the tableview.

Before we move on, let’s take a few minutes to work on some details.  While this provides the info required by the user, it would be nice to polish it up a bit.  First, we should add the letters “mins” to the totalTime displayed in the cell.  Second, it would be nice to format the date a little more such that its more human readable.  So go back to your cellForRowAtIndexPath and make the following changes:

override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell {

let cell: UITableViewCell = self.tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath:indexPath!) as UITableViewCell

var myWorkout: Swim = self.workouts[indexPath!.row] as Swim

var formatString = NSDateFormatter.dateFormatFromTemplate("EdMMM", options: 0, locale: NSLocale.currentLocale())

let dateFormatter = NSDateFormatter()

dateFormatter.dateFormat = formatString

let date = dateFormatter.stringFromDate(myWorkout.date as NSDate)

println(date)

cell.textLabel.text = date

cell.detailTextLabel!.text = myWorkout.totalTime.stringValue + " mins"

return cell;

}

There, now the user has a little mode detailed info of the data displayed.  We could go on and modify the cell to hold more data or even be selectable such that it would segue into a detail view controller to display all the info.

WRITING TO CORE DATA

Now all that is left to do is actually, remove that viewDidLoad code that writes to CoreData and instead, write to CoreData from our Workout view controller.  So back in WorkoutViewController, first import CoreData at the top, then add this property:

lazy var coreDataStack = CoreDataStack()

and finally, in the saveMyWorkout method, after we calculate our joules burned, or before, it doesn’t matter, add this code:

//E - Perhaps just store laps and meters per lap = total metes in some extra field within the SwimFit app to display it.

var description = NSEntityDescription.entityForName("Swim", inManagedObjectContext:coreDataStack.context)

var sampleSwim = Swim(entity:description!, insertIntoManagedObjectContext:coreDataStack.context)

var numberFormatter = NSNumberFormatter()

var nolaps:NSNumber? = numberFormatter.numberFromString(numberOfLapsValue!)

if let nolaps = nolaps {

sampleSwim.laps = Int(nolaps)

}

var nometers:NSNumber? = numberFormatter.numberFromString(metersPerLapValue!)

if let nometers = nometers {

sampleSwim.meters = Double(nometers)

}

var totime:NSNumber? = numberFormatter.numberFromString(workoutDurationValue!)

if let totime = totime {

sampleSwim.meters = Double(totime)

}

sampleSwim.pace = pace

sampleSwim.date = NSDate()

coreDataStack.saveContext()

This will save that other data, which is not HealthKit or health store data, into CoreData for later use.  Now let’s just go modify our SwimmingData view controller to make it fetch.

You already added a CoreDataStack variable to your SwimmingData view controller, so just add a fetchRequest var like this, right below the CoreDataStack var:

var coreDataStack: CoreDataStack!

var fetchRequest: NSFetchRequest!

Now in viewDidLoad add this neat code:

fetchRequest = coreDataStack.model.fetchRequestTemplateForName("FetchRequest")

This is a stored fetch request and to use it you must head over to the xcdatamodeld.file and create a new FetchRequest, leave its name as FetchRequest and now from the editor leave Swim as the selected entity to fetch from.  Now go back to SwimmingData and add this method:

//MARK - Helper methods

func fetchAndReload(){

var error: NSError?

let results = coreDataStack.context.executeFetchRequest(fetchRequest, error: &error) as [Swim]?

if let fetchedResults = results {

workouts = fetchedResults.copy() as NSMutableArray

} else {

println("Could not fetch \(error), \(error!.userInfo)")

}

tableView.reloadData()

}

And now call this method from viewDidLoad.  This will load your fetched data from CoreData into your tableview.

This will fetch the items in the order they were inserted, but you can also add a sort descriptor.

Add this lazy property at the top of your SwimmingData class:

lazy var dateSortDescriptor: NSSortDescriptor = {

var sd = NSSortDescriptor(key: "Swim.laps",

ascending: true) return sd
}()

Then in the viewDidLoad add this as a property of your fetchRequest:

fetchRequest.sortDescriptors =[dateSortDescriptors]

 NSFETCHEDRESULTSCONTROLLER OPTION

Alternatively you can also use NSFetchedResultsController.  NSFRC is a neat object that is created specifically for fetching and manipulating data from a CoreData query.  Its special in many respects but mainly because it works nicely with table views.  It can store information about table structure and can allow for interaction between its data and the tableview at the same time.  So add this property to the top of your class:

var fetchedResultsController : NSFetchedResultsController!

In your viewDidLoad:

//1
let fetchRequest = NSFetchRequest(entityName: "Swim")
let sortDescriptor = NSSortDescriptor(key: "totalTime", ascending: false) fetchRequest.sortDescriptors = [sortDescriptor] 
//2 fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil) 
//3 var error: NSError? = nil 
if (!fetchedResultsController.performFetch(&error)) { 
println("Error: \(error?.localizedDescription)") } 

Now replace your datasource methods with:

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return fetchedResultsController.sections!.count
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let sectionInfo = fetchedResultsController.sections![section] as NSFetchedResultsSectionInfo
return sectionInfo.numberOfObjects
}

Finally, the way you access data to populate your cell in cellForRowAtIndexPath is:

let mySwim = fetchedResultsController.objectAtIndexPath(indexPath) as Swim

cell.textLabel.text = date
cell.detailLabel.text = mySwim.totalTime.stringValue + " mins"

}

where date would be the formatted value of course.

Well it has been a long journey.  I hope you learned enough about Healthkit to feel comfortable enough to start working on your own app.

Have a good one!

HealthKit for iOS8: Part 6

iOS 8 HealthKit Santiapps Marcio Valenzuela

iOS 8 HealthKit

Here is the start of our WorkOut view controller:

import Foundation
import UIKit
import HealthKit

class WorkoutViewController: UITableViewController, UITextFieldDelegate {

@IBOutlet var numberOfLapsTextField: UITextField!
@IBOutlet var metersPerLapTextField: UITextField!
@IBOutlet var workoutDurationTextField: UITextField!
@IBOutlet var paceTextField: UITextField!
var numberOfLapsValue: NSString?
var metersPerLapValue: NSString?
var workoutDurationValue: NSString?
var userWeight: Double?
var  healthStore:HKHealthStore?
}

We import what we need, we subclass UITableViewController and add the Text Field delegate protocol.  Here I have created 4 labels for:

  • numberOfLaps
  • metersPerLap
  • workoutDuration
  • pace

These labels have an underlying variable for each.  The reason the first 3 are strings is because these are not health kit data per se.  These will be stored in CoreData.  However they really should be NSNumbers because it would be quite nice to store them and take advantage of CoreData’s ability to retrieve ordered data and statistical data in its fetches as well.

Finally we declare our health store property.

First let’s look at our lifecycle methods:

override func viewDidLoad() {

super.viewDidLoad()

self.fetchUsersWeight()

}

func textFieldShouldReturn (textField: UITextField) -> (ObjCBool) {

textField.resignFirstResponder()

if self.numberOfLapsTextField != nil && self.metersPerLapTextField != nil && self.workoutDurationTextField != nil {

}

return true;

}

In viewDidLoad we call a fetchUsersWeight method because we will need that to calculate calories burned.  Then we implement textFieldShouldReturn for each label.  Let’s take a look at that first method called:

func fetchUsersWeight() -> () {

var todayPredicate: NSPredicate = self.predicateForSamplesToday()

var weightType: HKQuantityType = HKObjectType.quantityTypeForIdentifier(HKQuantityTypeIdentifierBodyMass)

self.healthStore?.aapl_mostRecentQuantitySampleOfType(weightType, predicate: todayPredicate, completion: { (weight, error) -> () in

if weight == nil {

NSLog("Sorry, weight is empty...")

return

}

if let someWeight = weight {

var weightUnit: HKUnit = HKUnit.poundUnit()

self.userWeight = weight!.doubleValueForUnit(weightUnit)

}

})

}

Once again we make a fetch using the same extension method and helper predicate method as before.  This exemplifies the use of extensions because we need to include the predicate method in this Workout class, not so the extension.  So go ahead and add the predicate method now:

func predicateForSamplesToday () -> (NSPredicate) {

let calendar: NSCalendar = NSCalendar.currentCalendar()

let now: NSDate = NSDate()

let startDate: NSDate = calendar.startOfDayForDate(now)

let endDate: NSDate = calendar.dateByAddingUnit(.CalendarUnitDay, value:1, toDate:startDate, options:nil)!

return HKQuery.predicateForSamplesWithStartDate(startDate, endDate:endDate, options:HKQueryOptions.StrictStartDate)

}

Ok so the user has 2 options here: Done or Cancel.  Cancel is easy:

@IBAction func cancel(sender: AnyObject) -> () {

self.navigationController?.popViewControllerAnimated(true)

}

Now let’s take a look at Done:

@IBAction func saveMyWorkout(sender: AnyObject) -> () {

//1.  Enter the values for your workout & capture

numberOfLapsValue = numberOfLapsTextField.text

metersPerLapValue = metersPerLapTextField.text

workoutDurationValue = workoutDurationTextField.text

//A - Need to fetch the user's weight in kgs from healthstore

var myWeight: Double = self.userWeight! * 0.453

//B - Need to convert time worked out into hours

//C - Need to select pace from some sort of switch

var pace: Double = (paceTextField!.text as NSString).doubleValue

//D - Throw away numberOfLaps = (numberOfLapsValue! as NSString).doubleValue & (metersPerLapValue! as NSString).doubleValue

var totalCaloriesBurnedByWorkout: Double =  ( (myWeight * pace) ) * ((workoutDurationValue! as NSString).doubleValue)/60

var totalJoulesBurnedByWorkout: Double = totalCaloriesBurnedByWorkout*4.184

//1.5 Set to EnergyVC before saving

let energyVCInstance: EnergyViewController = self.navigationController!.viewControllers[0] as EnergyViewController

energyVCInstance.activeEnergyBurnedValueLabel?.text = NSString(format:"%.2f",totalJoulesBurnedByWorkout)

energyVCInstance.activeEnergyBurned = totalJoulesBurnedByWorkout

energyVCInstance.refreshControl?.endRefreshing()

//2.  Dismiss and set values on EnergyVC such that they can be saved to the healthStore

//Save object to healthstore

// MUST DEFINE HKQUANTITY_TYPE

var quantityType: HKQuantityType = HKQuantityType.quantityTypeForIdentifier(HKQuantityTypeIdentifierActiveEnergyBurned)

//MUST DEFINE HKQUANTITY

var quantity: HKQuantity = HKQuantity(unit: HKUnit.jouleUnit(), doubleValue:totalJoulesBurnedByWorkout)

//DATE & METADATA

var now: NSDate = NSDate()

var metadata: NSDictionary = ["HKMetadataKey":"Swim Session"]

//WORKOUT TYPE?

var workoutType = HKWorkoutActivityType.Swimming

//This creates the object to SAVE

var energyBurnt: HKQuantitySample = HKQuantitySample(type: quantityType, quantity:quantity, startDate:now, endDate:now, metadata:metadata)

NSLog("Before saving to the healthstore...")

self.healthStore?.saveObject(energyBurnt, withCompletion: { (success, error) in

NSLog("Saving to the healthstore...")

if (error != nil) {

NSLog("The error is: \(error)")

}

dispatch_async(dispatch_get_main_queue(), {

if success {

// This was for updating a uitableview

// Alert User

let alertController = UIAlertController(title: "Success!", message: "Data Saved", preferredStyle: UIAlertControllerStyle.Alert)

alertController.addAction(UIAlertAction(title: "Confirm", style: UIAlertActionStyle.Default, handler: {action in

println("confirm was tapped")

//dismiss this vc

self.navigationController?.popViewControllerAnimated(true)

}))

self.presentViewController(alertController, animated: true, completion: nil)

} else {

NSLog("An error occured saving your workout burn. In your app, try to handle this gracefully. The error was: %@.", error)

abort()

}

})

})

}

This time I take you step by step, once again, inside the same method so as to drive the point home.  First we take all necessary data from labels to make our calculations.  We make our calculation and populate totalJoulesBurnedByWorkout.

Then we set the properties in our EnergyViewController from here before we return.

And before returning, we save our data to the health store, creating an identifier, then a quantity, then a date, then some metadata and finally calling saveObject.  In the saveObject method we return an alertController for success and pop our Workout view controller, because remember this is a navigation stack, not a modally presented view controller.  Else we log an error.

In the final part, we will add CoreData to our project and save laps and meters data into CoreData as well.

C ya!

Learning C++ @Udemy

Udemy & Santiapps: Learn C++, Swift, ObjC & Arduino: Marcio Valenzuela

Udemy & Santiapps

C++ is a very widely used & powerful language.  We can see it in our ObjC, Swift and Arduino projects.  So here is some great content from Udemy on C++ for anyone interested in the basics of C++:

Beginning C++ @Udemy Santiapps Marcio Valenzuela

Arduino Communication Technologies (Bluetooth, GSM/GPRS & WiFi)

Arduino Comm Technologies

Im using a Sim900 module I got from SainSmart.  It basically looks like this:

Arduino - SIM900 GSM_GPRS module Santiapps Marcio Valenzuela

Arduino – SIM900 GSM_GPRS Module

Arduino - SIM900 GSM_GPRS module Santiapps Marcio Valenzuela

Arduino – SIM900 GSM_GPRS Module

Its important to understand something in Arduino-world!

Arduino is a brand and as such, it makes branded products.  But there are a bunch of other companies that do similar things.  The same way you can get an arduino-like board from a bunch of other manufacturers, you can also get a bunch of add-on modules or shields from other manufacturers as well.
In my case, I have an original Arduino UNO/MEGA board:
Arduino_MEGA Santiapps Marcio Valenzuela

Arduino_MEGA

But I decided to go with a cheaper, TinySine WiFi shield.  That TinySine WiFi shield has a WiFiBee module on top of it.  That WiFiBee is the name TinySine gave its little module that it plopped on top of a shield.  But the WiFiBee module actually uses a WiFly from Roving Networks.  So you can see how this can get confusing.
It gets specially confusing because Arduino also has its own WiFi shields.  And they have their own examples online.  So you may find yourself trying to use an Arduino sketch or tutorial (on a web site or youtube) on a module on top of a shield that is not an original Arduino shield.
Here is what my WiFi-module shield looks like:
Ardiuno - WiFiBee Module Santiapps Marcio Valenzuela

Arduino – WiFiBee Module

So wait…modules, shields, boards…wth!
Your board is your Arduino or arduino-like board, the main board with the micro-controller chip on it.
A shield is usually a smaller board you plop over your main Arduino board.  It has fixed pins on it that fit nicely into your Arduino board.  The pins must match for these to work.  Thats why its important to make sure that if you are buying a WiFi or BT or GSM shield for an Arduino, to ask or research if that shield fits an Arduino UNO or an Arduino MEGA or anything else.  Otherwise, the nicely made pins wont fit nicely over your board.  It will still work but you will have to get a breadboard or use a bunch of cables to make the jump between fixed pins on the shield and fixed pins on the main board.  But the shield is still just a shield.  Its main purpose is to hardwire connections from a module that performs some task, to your Arduino board, using fixed pins.
A module is usually something that resides on a shield which is responsible for some specific feature, such as WiFi communication.  Those modules usually have their own micro-controller(s) to carry out their own logic and interface with your Arduino board via the shield.
The shield is basically just another board that has some hard-wired cables in a board.  Anybody can make a shield.  You can buy a module and a proto-board and make your own shield.  It’s usually easier to just buy a shield or use a module directly, as we will see in this case.
In the picture of the WiFi module you can actually see it all come together:
Ardiuno - WiFiBee Module Santiapps Marcio Valenzuela

Arduino – WiFiBee Module

The board on the bottom is actually an Arduino UNO board (smaller than the MEGA).  You can see the silvery USB port sticking out on the top left and the black plastic power jack on the bottom left.  The board itself is green colored.

Just over that you can see the TinySine WiFi shield in blue color, right above it.  Its pins stick right into the Arduino UNO board below.

Finally on top of the shield you can see the red colored board which contains the Roving Networks’ WiFly module.  Unfortunately you can’t see it because it is facing down.  We are only seeing the underside with all the solder points.

In my next post Ill talk about my experience with all three comm technologies:

  1. BT using HC-05 module
  2. WiFi using the TinySine (Roving Networks module) shield
  3. GSM/GPRS with the Sim900 module

See ya!

Web Client to post/get data to web with Arduino via SIM900 module

Arduino - SIM900 GSM_GPRS module Santiapps Marcio Valenzuela

Arduino – SIM900 GSM_GPRS Module

This is actually my work log on Arduino & SIM900 GSM/GPRS module.

I’m starting off from here:

https://github.com/MarcoMartines/GSM-GPRS-GPS-Shield/blob/GSMSHIELD/examples/GSM_GPRSLibrary_Client/GSM_GPRSLibrary_Client.ino

But Im getting this error:

status=READY
ATT: ERROR
RIC:
ERROR

DB:STARTING NEW CONNECTION
ATT: SHUT OK
RIC:
SHUT OK

DB:SHUTTED OK
status=ERROR ==> Not connecting to apn for some reason.

DB:NOT CONN
ATT: OK
RIC:
ERROR

DB:NOT CONN

Number of data received:
0

Data received:

This was quickly solved by increasing the timeout value in the inetGSM library file from 500 to 10000 ms.

Now the error is:

GSM Shield testing.
DB:ELSE
DB:CORRECT BR
RIC: ATE0
ATT: +CPMS:
+CPMS: 28,190,28,190,28,190
ATT: SHUT OK
status=READY
DB:STARTING NEW CONNECTION
DB:SHUTTED OK
DB:APN OK
DB:CONNECTION OK
RIC: 10.124.39.139
DB:ASSIGNED AN IP
status=ATTACHED
DB:RECVD CMD
ATT: CONNECT OK
DB:NOT CONN
ATT: OK
RIC: AT+CIPSTART=”TCP”,”www.google.com”,80
ERROR
DB:NOT CONN
ATT: OK
RIC: AT+CIPSTART=”TCP”,”www.google.com”,80
ERROR
DB:NOT CONN
Number of data received:
0
Data received: 

So it seems its losing the connection.  I believe it might be due to a power loss situation where the SIM900 is reported to require a steady 2A power supply to avoid such issues when it requires bursts of energy.

Im currently researching how to solve this issue…

I was about to try a capacitor (22microF) and a 4xAA battery pack thru the Vin when all of a sudden it worked!

I just tried it again another day and it worked again.  Apparently my sim wasn’t properly activated…

GSMesting.
GSM Shield testing.
ATT: OK
RIC:
OK

DB:ELSE
ATT: OK
RIC:
OK

DB:ELSE
ATT: OK
RIC:
OK

DB:ELSE
ATT: OK
RIC:
OK

DB:CORRECT BR
ATT: OK
RIC:
OK

ATT: OK
RIC:
OK

ATT: OK
RIC: ATE0

OK

ATT: OK
RIC:
OK

ATT: OK
RIC:
OK

ATT: OK
RIC:
OK

ATT: OK
RIC:
OK

ATT: +CPMS:
RIC:
+CPMS: 37,190,37,190,37,190

OK

ATT: OK
RIC:
OK

ATT: SHUT OK
RIC:
SHUT OK
status=READY
ATT: ERROR
RIC:
ERROR

DB:STARTING NEW CONNECTION
ATT: SHUT OK
RIC:
SHUT OK

DB:SHUTTED OK
ATT: OK
RIC:
OK

DB:APN OK
ATT: OK
RIC:
OK

DB:CONNECTION OK
ATT: ERROR
RIC:
10.125.69.222

DB:ASSIGNED AN IP
status=ATTACHED

10.125.69.222
ATT: OK
RIC:
OK

DB:RECVD CMD
ATT: CONNECT OK
RIC:
OK

ATT: OK
RIC:
CONNECT OK

DB:OK TCP
ATT: >
RIC:
>
DB:>
ATT: SEND OK
RIC:
SEND OK

DB:SENT

Number of data received:
50

Data received:

HTTP/1.0 302 Found
Location: http://www.google.hn/?gws_rd=cr&ei=rEtyVZzBI9XfsAT9zLvABA
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Set-Cookie: PREF=ID=4bf4186862639d93:FF=0:TM=1433553836:LM=1433553836:S=vDVVuaRrLthWg9Cz; expires=Mon, 05-Jun-2017 01:23:56 GMT; path=/; domain=.google.com
Set-Cookie: NID=68=cj3CJ_MYqgELI9nt9GdFr0DLx8QyH9jRSQ3hWD8XgKpJhzAmKLaw-bY1KDmg5ylOvsj5zDAfkQ29QmOurH9vHxhb44f5wMqaH6AuO5vtHIJBEF1_4U4IqTyVEswuSdmq; expires=Sun, 06-Dec-2015 01:23:56 GMT; path=/; domain=.google.com; HttpOnly
P3P: CP=”This is not a P3P policy! See http://www.google.com/support/accounts/bin/answer.py?hl=en&answer=151657 for more info.”
Date: Sat, 06 Jun 2015 01:23:56 GMT
Server: gws
Content-Length: 258
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Alternate-Protocol: 80:quic,p=0

<HTML><HEAD><meta http-equiv=”content-type” content=”text/html;charset=utf-8″>
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF=”http://www.google.hn/?gws_rd=cr&amp;ei=rEtyVZzBI9XfsAT9zLvABA”>here</A&gt;.
</BODY></HTML>

CLOSED