loading...
Cover image for Automatically discover and connect to devices on a network

Automatically discover and connect to devices on a network

benmechen profile image Ben Mechen ・4 min read

Recently, in a university project focused on finding a better way for disabled people to interact with video games, we needed to connect an iOS app to PC over a local network. Usually this would be a simple task using IP addresses, possibly through a matching server. However, due to the constraints of the project we were not able to create a server to handle this, and no user wants to be inputting IP addresses.

We needed another way.

Initially we looked at using broadcast/multicast, however this was problematic on complex networks, such as university WiFi. We also looked at using QR codes, sounds, and many other methods, but none matched the brief; simplicity for the user.

Zeroconf and Bonjour

Zeroconf is a group of technologies aimed at simplifying the discovery of services on a local network - Bonjour, a technology released in 2007 by Apple, is one of these technologies.

Bonjour is used to locate services advertised on a network using mDNS service records, and is often used in printers (think printers - when you connect a printer to a network, any nearby device can then use it with minimal setup).

 Enter Discoverable

Bonjour was just what we were looking for, so how do we actually use it to talk to another device?

While to the user's it seems like one, seamless process, behind the scenes two of Apple's iOS frameworks are used:

  1. Foundation framework provides some nice built in tools for discovering Bonjour services
  2. Network framework gives direct access to protocols like TLS, TCP, and UDP, on top of which we can build our custom networking protocol.

We use these frameworks for the Discoverable library, built by a number of us in the project to abstract away the networking aspect for the project. We decided to open source the project, mainly because we thought it was pretty cool ¯_(ツ)_/¯.

How do I actually use this?

Okay, enough rambling. This tutorial is going to focus on the client (iOS) side, but you'll still need something to connect to. I've put together a simple Python server here, which we will be using to test.

Download the Xcode started project, which contains the basic storyboard layout for the app, with Discoverable already installed.

(The completed source code for this project can be found here)

Now, if you open ViewController.swift, import Discoverable and assign a variable to a new instance of Discoverable, and another to hold a boolean value representing the connection state.
Create a new Discoverable object

Create a new TouchUpInside IBAction for the Connect button in ViewController.swift, and add the following code to toggle the connection. If disconnected, this code tells Discoverable to begin searching for a service on the local network with the given name, and connect on the given port.

@IBAction func toggleConnection(_ sender: Any) {
    if (!isConnected) {
        connectionService.discover(type: "_discoverable._udp", on: 1024)
    } else {
        connectionService.close()
    }
}

And that's all it takes to find a server on the network and automatically connect to it. If you run the server locally:

  1. cd into the folder containing server.py
  2. Make sure you have python 3 and pip installed
  3. Run pip install zeroconf
  4. Open server.py in an editor and set the `HOST' variable to your computer's IP address
  5. Run sudo python3 server.py to start the server

Then run the iOS app (either in the simulator or on a real device) and hit connect. Now, nothing will have changed on the iOS app (as we haven't updated the UI yet), but if you check the Xcode console or the server.py output, you'll see a bunch of logs involving messages being sent and received between the two.

Now, let's hook into Discoverable's state publishing system, so that we can update the UI to show we've opened a new connection. Back in ViewController.swift, add the following code to make the ViewController conform to the DiscoverableDelegate protocol.
`swift
extension ViewController: DiscoverableDelegate {
func connectionState(state: Discoverable.State) {

}

func connectionStrength(strength: Float) {

}

}
`
Inside `connectionState(state: Discoverable.State)`, add the following switch statement.
`
swift
switch state {
case .connected:
isConnected = true
default:
isConnected = false
}
`
There is much more you can do with the connection state - such as showing a connecting indicator while the connection is starting, or showing error messages on connection failure - but for this example, this is all we need.

Finally, to subscribe to updates we need to set the connectionService's delegate to the ViewController, so that it knows where to send updates. In viewDidLoad, add the following line.
swift
connectionService.delegate = self

Now we can connect and disconnect to the server, but let's update the UI to tell the user that. Update connectionState(state: Discoverable.State) with the following code.
swift
func connectionState(state: Discoverable.State) {
DispatchQueue.main.async {
switch state {
case .connected:
self.isConnected = true
self.connectButton.setTitle("Disconnect", for: .normal)
self.sendButton.isEnabled = true
default:
self.isConnected = false
self.connectButton.setTitle("Connect", for: .normal)
self.sendButton.isEnabled = false
}
}
}

The connect button will now update to show the connection status. Now, let's actually send some data to the server. Add an IBAction on TouchUpInside for the send button, which gets the content of the text field and sends it to the server:
`swift
@IBAction func send(_ sender: Any) {
guard (textField.text != nil) else {
return
}

connectionService.send(textField.text!)

}
`

And that's it! If you enter some text into the field, and check the server's logs, you'll see that your input was received by the server (if everything worked correctly).

Check the server logs to make sure the input was sent correctly

This is a very basic example, but shows the main principles of opening and using a Discoverable connection.

Posted on by:

Discussion

markdown guide