5

I'm currently trying to build a proof-of-concept iOS app to check if we are able to implement some sort of indoor positioning capability without deploying beacons or any other hardware.

What we have

There is a database containing all registered access points in our building including their X- and Y-coordinates. The coordinates are mapped to a custom-built grid that spans the whole building.

The app will be released using our Enterprise distribution, so there are no constraints concerning any Apple Store requirements. The app will be running exclusively on devices that automatically connect to the proper WiFi using a certificate.

What we'd like to build

In order to improve the usability of the app, we'd like to show the user his current position. Using Apples native CLLocation services is not accurate enough because we are operating inside a building. The basic idea is to fetch all nearby access points including their BSSID and signal strength and calculate a more or less accurate position using both signal strength and the location database for our access points (see above).

What i've tried so far

Using SystemConfiguration.CaptiveNetwork to get the BSSID

import SystemConfiguration.CaptiveNetwork

func getCurrentBSSID() -> String {
    guard let currentInterfaces = CNCopySupportedInterfaces() as? [String] else { return "" }
    for interface in currentInterfaces {
        print("Looking up BSSID info for \(interface)") // en0
        let SSIDDict = CNCopyCurrentNetworkInfo(interface as CFString) as! [String : AnyObject]
        return SSIDDict[kCNNetworkInfoKeyBSSID as String] as! String
    }
    return ""
}

This solution works (after setting the proper entitlements), but i'm only able to read the BSSID of the CURRENTLY CONNECTED access point.

Using UIStatusBarDataNetworkItemView to read signal strength

private func wifiStrength() -> Int? {
    let app = UIApplication.shared
    var rssi: Int?
    guard let statusBar = app.value(forKey: "statusBar") as? UIView, let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else {
        return rssi
    }
    for view in foregroundView.subviews {
        if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view .isKind(of: statusBarDataNetworkItemView) {
            if let val = view.value(forKey: "wifiStrengthRaw") as? Int {
                rssi = val
                break
            }
        }
    }
    return rssi
}

This one is kind of obvious, it only reads the signal strength for the connected WiFi network, not the access point specific one.

QUESTION

Is there any way to read a list of available access points (not WiFi networks) including their BSSID and signal strength? We cannot jailbreak the devices since they are under device management.

Maybe there is some way to do it using MobileWiFi.framework (see this link), but i couldn't wrap my head around doing it in Swift (kind of a beginner when it comes to iOS development).

schaermu
  • 13,478
  • 2
  • 39
  • 32
  • What brand of WiFi network equipment do you have? Many systems can handle this on the network side; e.g. CMX or DNA-Spaces from Cisco. Meraki location services and so on – Paulw11 Jul 15 '19 at 12:40
  • Not entirely certain what we are running at that specific location. But since it's an enterprise environment i'd assume we run the same everywhere: Cisco. I'm certain though that things like Meraki location services are not being used/activated due to privacy concerns. – schaermu Jul 15 '19 at 12:50
  • You will get much better results using Cisco CMX (or its cloud-based replacement DNA-Spaces) than trying to write your own, especially with iOS devices since the network stack on iOS provides additional information to Cisco Access Points regarding signal strength and visible APs. In my opinion you would be better off addressing privacy concerns through your employment contract. That's what my employer did. We only expose "fuzzed" staff locations - about 10M accuracy even though the system has 1m accuracy with the new Cisco APs – Paulw11 Jul 15 '19 at 12:59
  • The challenge you have is that iOS won't be monitoring all surrounding BSSIDs when it is connected; It only goes looking for a new AP when the signal strength gets too low or it is forced to by the AP as part of roaming. For best results Cisco APs use a dedicated monitoring radio to track all clients that it can "hear", not just clients that are connected to the AP. – Paulw11 Jul 15 '19 at 13:06
  • I don't think that you'll get a precise result by using wifis' signal strength. You should take into account that you've walls inside a building which also affects to signal strength. – arturdev Jul 30 '19 at 14:23
  • As of September 2016: [Apple does not let you scan for WiFi networks.](http://cennest.com/weblog/2016/09/apple-does-not-let-you-scan-for-wifi-networks/) – Daniel Jul 30 '19 at 21:41
  • Check how to use private API in Swift and try to use MobileWiFi.framework – canister_exister Jul 31 '19 at 21:26

3 Answers3

3

I am afraid it is not possible to implement this on not jailbroken device.

I found some code for this, but it was outdated. I don't think that you will use it on iOS 3/4 devices.

NEHotspotHelper works only when Settings->Wifi page is active. You can get signal strength there, but I unsure how it will work.

MobileWiFi.framework requires entitlement, which can't be set without jailbreak.

Useful links:
Technical Q&A QA1942

Probably iBeacons or QR (AR) is the only options.

Sergey Kuryanov
  • 6,114
  • 30
  • 52
  • Thanks for your explanations, sadly we cannot rely on jailbroken devices due to enterprise device management. – schaermu Jul 30 '19 at 10:05
1

Although many resources say that while using Apple "official" frameworks, you can only get network's SSID that your iPhone is at the moment connected to. Here are workaround:

  1. You can use NEHotspotConfigurationManager class but at first you must to enable the Hotspot Configuration Entitlement (property list key) in Xcode.

  2. You can also use NEHotspotHelper class (although it requires Apple's permission). For this you need to apply for the Network Extension entitlement and then modify your Provisioning Profile plus some additional actions. Look at this SO post for further details.

Here's a code snippet how to use NEHotspotConfigurationManager:

import NetworkExtension

class ViewController: UIViewController {

    let SSID = ""

    @IBAction func connectAction(_ sender: Any) {
        let hotspotConfig = NEHotspotConfiguration(ssid: SSID, passphrase: "", isWEP: false)
        NEHotspotConfigurationManager.shared.apply(hotspotConfig) {[unowned self] (error) in
            if let error = error {
                self.showError(error: error)
            } else {
                self.showSuccess()
            }
        }
    }

    @IBAction func disconnectAction(_ sender: Any) {
        NEHotspotConfigurationManager.shared.removeConfiguration(forSSID: SSID)
    }

    private func showError(error: Error) {
        let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
        let action = UIAlertAction(title: "Darn", style: .default, handler: nil)
        alert.addAction(action)
        present(alert, animated: true, completion: nil)
    }

    private func showSuccess() {
        let alert = UIAlertController(title: "", message: "Connected", preferredStyle: .alert)
        let action = UIAlertAction(title: "Cool", style: .default, handler: nil)
        alert.addAction(action)
        present(alert, animated: true, completion: nil)
    }
}

Here's a code snippet how to use NEHotspotHelper:

import NetworkExtension
import SystemConfiguration.CaptiveNetwork

func getSSID() -> String {

    if #available(iOS 11.0, *) {

        let networkInterfaces = NEHotspotHelper.supportedNetworkInterfaces()
        let wiFi = NEHotspotNetwork()

        let st = "SSID:\(wiFi.SSID), BSSID:\(wiFi.BSSID)"
        return st

        for hotspotNetwork in NEHotspotHelper.supportedNetworkInterfaces() {

            let signalStrength = hotspotNetwork.signalStrength
            print(signalStrength)
        }

    } else {

        let interfaces = CNCopySupportedInterfaces()
        guard interfaces != nil else { 
            return "" 
        }
        let if0: UnsafePointer<Void>? = CFArrayGetValueAtIndex(interfaces, 0)

        guard if0 != nil else { 
            return "" 
        }             
        let interfaceName: CFStringRef = unsafeBitCast(if0!, CFStringRef.self)
        let dictionary = CNCopyCurrentNetworkInfo(interfaceName) as NSDictionary?

        guard dictionary != nil else { 
            return "" 
        } 

    return String(dictionary![String(kCNNetworkInfoKeySSID)])
    }
}
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220
1

You can use transportable Differential GPS reference station inside your building and improve accuracy to about 1-3 cm and then rely on mobile phone built-in GPS.

Yaroslav
  • 2,338
  • 26
  • 37