Monday, January 11, 2016

Connect Mac/iPhone to a Simple Python Socket Server (iOS Part)

This example shows how to use NSStream to connect an iPhone to a remote Python socket server on a Raspberry Pi. A custom port is used instead of the HTTP port 80.

1. Setup a socket server on a Raspberry Pi:
See Connect Mac/iPhone to a Simple Python Socket Server (Raspberry Pi Part)

The above link also shows how to connect to the socket server from a Mac using telnet and local client python file.

2. Edit ViewController.swift as:


import UIKit

class ViewController: UIViewController, NSStreamDelegate {

    //Button
    var buttonConnect : UIButton!
    
    //Label
    var label : UILabel!
    var labelConnection : UILabel!
    
    //Socket server
    let addr = "192.168.xx.xx"
    let port = 9876
    
    //Network variables
    var inStream : NSInputStream?
    var outStream: NSOutputStream?
    
    //Data received
    var buffer = [UInt8](count: 200, repeatedValue: 0)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        ButtonSetup()
        
        LabelSetup()
    }
    
    //Button Functions
    func ButtonSetup() {
        buttonConnect = UIButton(frame: CGRectMake(20, 50, 300, 30))
        buttonConnect.setTitle("Connect to server", forState: UIControlState.Normal)
        buttonConnect.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonConnect.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonConnect.addTarget(self, action: "btnConnectPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(buttonConnect)
        
        let buttoniPhone = UIButton(frame: CGRectMake(20, 100, 300, 30))
        buttoniPhone.setTitle("Send \"This is iPhone\"", forState: UIControlState.Normal)
        buttoniPhone.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttoniPhone.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttoniPhone.addTarget(self, action: "btniPhonePressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(buttoniPhone)
        
        let buttonQuit = UIButton(frame: CGRectMake(20, 150, 300, 30))
        buttonQuit.setTitle("Send \"Quit\"", forState: UIControlState.Normal)
        buttonQuit.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
        buttonQuit.setTitleColor(UIColor.cyanColor(), forState: UIControlState.Highlighted)
        buttonQuit.addTarget(self, action: "btnQuitPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        view.addSubview(buttonQuit)
    }
    
    func btnConnectPressed(sender: UIButton) {
        NetworkEnable()
        
        buttonConnect.alpha = 0.3
        buttonConnect.enabled = false
        buttonConnect.setTitleColor(UIColor.blueColor(), forState: UIControlState.Normal)
    }
    func btniPhonePressed(sender: UIButton) {
        let data : NSData = "This is iPhone".dataUsingEncoding(NSUTF8StringEncoding)!
        outStream?.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)
    }
    func btnQuitPressed(sender: UIButton) {
        let data : NSData = "Quit".dataUsingEncoding(NSUTF8StringEncoding)!
        outStream?.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)
    }
    //Label setup function
    func LabelSetup() {
        label = UILabel(frame: CGRectMake(0,0,300,150))
        label.center = CGPointMake(view.center.x, view.center.y+100)
        label.textAlignment = NSTextAlignment.Center
        label.numberOfLines = 0 //Multi-lines
        label.font = UIFont(name: "Helvetica-Bold", size: 20)
        view.addSubview(label)
        
        labelConnection = UILabel(frame: CGRectMake(0,0,300,30))
        labelConnection.center = view.center
        labelConnection.textAlignment = NSTextAlignment.Center
        labelConnection.text = "Please connect to server"
        view.addSubview(labelConnection)
    }
    
    //Network functions
    func NetworkEnable() {
        
        print("NetworkEnable")
        NSStream.getStreamsToHostWithName(addr, port: port, inputStream: &inStream, outputStream: &outStream)
        
        inStream?.delegate = self
        outStream?.delegate = self
        
        inStream?.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
        outStream?.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
        
        inStream?.open()
        outStream?.open()
        
        buffer = [UInt8](count: 200, repeatedValue: 0)
    }
    
    func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
        
        switch eventCode {
        case NSStreamEvent.EndEncountered:
            print("EndEncountered")
            labelConnection.text = "Connection stopped by server"
            inStream?.close()
            inStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
            outStream?.close()
            print("Stop outStream currentRunLoop")
            outStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
            buttonConnect.alpha = 1
            buttonConnect.enabled = true
        case NSStreamEvent.ErrorOccurred:
            print("ErrorOccurred")

            inStream?.close()
            inStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
            outStream?.close()
            outStream?.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
            labelConnection.text = "Failed to connect to server"
            buttonConnect.alpha = 1
            buttonConnect.enabled = true
            label.text = ""
        case NSStreamEvent.HasBytesAvailable:
            print("HasBytesAvailable")
            
            if aStream == inStream {
                inStream!.read(&buffer, maxLength: buffer.count)
                let bufferStr = NSString(bytes: &buffer, length: buffer.count, encoding: NSUTF8StringEncoding)
                label.text = bufferStr! as String
                print(bufferStr!)
            }
            
        case NSStreamEvent.HasSpaceAvailable:
            print("HasSpaceAvailable")
        case NSStreamEvent.None:
            print("None")
        case NSStreamEvent.OpenCompleted:
            print("OpenCompleted")
            labelConnection.text = "Connected to server"
        default:
            print("Unknown")
        }
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}


3. Enable the socket server on Raspberry Pi and run the iOS simulator on Mac.
Result:




Reference:
Stream Programming Guide
Connect iOS device to HTTP GET/POST PHP service (Raspberry Pi Part) (iOS Part)
Connect iOS device to MySQL database on a server (Raspberry Pi Part) (iOS Part)
Connect Mac/iPhone to a Simple Python Socket Server (Raspberry Pi Part)

Go back to Communication between iOS device (Client) and Raspberry Pi (Server)

5 comments:

  1. Would you know how to implement this using Swift 3? You would be my hero!

    ReplyDelete
  2. Agree - Swift 3 would be ace!

    ReplyDelete
  3. Great Tutorial!!
    Is there code on Swift 3 for this project?

    ReplyDelete
  4. Excuse me, can we have the xcode project?

    ReplyDelete
    Replies
    1. I used the basis of this post to create a similar app using SWIFT 3. I don't want to hijack Enoch's blog but XCODE project is published here:

      https://bitbucket.org/alansommerville/ios-tcp-io-client

      Also some basic notes on this created here:

      Published “TCP IO Client for IOS Demo” https://medium.com/@alan.sommerville/tcp-io-client-for-ios-demo-684dae1ed638

      Delete