Automatically discover and connect to devices on a network
Tags: ios, swift, python, tutorial
Published: 2020-07-26 21:20:12

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](https://cocoapods.org/pods/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](https://developer.apple.com/documentation/foundation/bonjour) provides some nice built in tools for discovering Bonjour services
2. [*Network* framework](https://developer.apple.com/documentation/network) 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](https://gist.github.com/benmechen/9f4c1ee1f6dfb21618e5c4e84ed67eaf), which we will be using to test.

Download the [Xcode started project](https://github.com/benmechen/Discoverable-Client), which contains the basic storyboard layout for the app, with Discoverable already installed.

(The completed source code for this project can be found [here](https://github.com/benmechen/Discoverable-Client/tree/completed))

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](https://dev-to-uploads.s3.amazonaws.com/i/ba562u11rhuyc8kv5336.png)

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.

```swift
@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](https://dev-to-uploads.s3.amazonaws.com/i/i3pw3ka4zagzz1oybyej.png)

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