Search code examples
macospyobjccorewlan

Assistance in extracting fields and formatting using Pyobjc and CoreWLAN


Apple used to provide a binary called airport at the following location -

/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport

This was able to scan for and produce a list of available SSIDs which would look like the following.

                            SSID BSSID             RSSI CHANNEL HT CC SECURITY (auth/unicast/group)
                  vodafone20645C                   -89  6       Y  -- RSN(PSK/AES/AES) 
                  vodafone20645C                   -89  60      Y  -- RSN(PSK/AES/AES) 
                        ALCL-HGU                   -88  1       Y  -- RSN(PSK/AES/AES) 
                        SKY69QVQ                   -84  11      Y  -- RSN(PSK/AES/AES)

Unfortunately as of macOS Sonoma 14.4 this tool is now deprecated and if executed only displays the following error message.

WARNING: The airport command line tool is deprecated and will be removed in a future release. For diagnosing Wi-Fi related issues, use the Wireless Diagnostics app or wdutil command line tool.

It goes without saying that neither of the referred to tools is a suitable substitute.

I have found a very helpful article on stackoverflow -

How to enable location services for CoreWLAN pyobjc wrapper to get bssid?

Using this as a starting point and by adding the following to it -

    for i in networks:
        if i.ssid() is None:
            continue
        print({'SSID_STR': i.ssid(), 'BSSID': i.bssid(), 'RSSI': i.rssiValue(), 'CHANNEL': i.channel()})

I have already been able to get a result like the following

{'SSID_STR': 'vodafone20645C', 'BSSID': None, 'RSSI': -89, 'CHANNEL': 6}
{'SSID_STR': 'vodafone20645C', 'BSSID': None, 'RSSI': -89, 'CHANNEL': 60}
{'SSID_STR': 'ALCL-HGU', 'BSSID': None, 'RSSI': -88, 'CHANNEL': 1}
{'SSID_STR': 'SKY69QVQ', 'BSSID': None, 'RSSI': -84, 'CHANNEL': 11}

I am however whilst an experienced shell script author very much a novice for Python and was hoping for help in getting this Python output to look exactly like the output from the Apple airport command. The format of the output from the airport command is used by many other existing tools. This would then allow the intended python script to be used as a replacement for the airport tool.

This will involve - if possible adding additional fields e.g. relating to the security settings, and in particular formatting the output to be the same layout as that formerly produced by the airport tool. You will note the righthand alignment of the SSID values in the airport output.

I could I feel already take the above and feed it back to a shell script and use awk, sed, etc. to extract the fields for my own requirement, however in the interest of generating a solution that would be directly usable by anyone (like me) who has previously relied on the airport command, it would be more useful to produce an identical format output.


Solution

  • Here is a quick and dirty example using f-strings:

      import CoreWLAN
      import re
    
      print(f"{'SSID' : >32} {'BSSID' : <17} RSSI CHANNEL HT CC SECURITY")
    
      wifi_interface = CoreWLAN.CWInterface.interface()
      networks, error = wifi_interface.scanForNetworksWithName_error_(None, None)
    
      for i in networks:
            supportsHT = 'Y' if i.fastestSupportedPHYMode() >= CoreWLAN.kCWPHYMode11n else 'N'
            security = re.search('security=(.*?),', str(i)).group(1)
            print(f"{i.ssid() : >32} {i.bssid() or '' : <17} {i.rssiValue() : <4} {i.channel() : <6}  {supportsHT : <2} {i.countryCode() or '--' : <2} {security}")
    

    Note the f-strings require Python 3.6 or greater.

    See https://www.geeksforgeeks.org/string-alignment-in-python-f-string/ for examples on how to use f strings for alignment.

    The above uses the same spacing as the airport command and should be safe to use those hardcoded values as SSIDs cannot exceed 32 characters and BSSID is always 17 characters.

    For more complex tables I would recommend using either pandas or prettytable.

    If you need any of the other data it will be more complicated to get. A starting point would be to check the developer documentation: https://developer.apple.com/documentation/corewlan?language=objc

    Also if you want the BSSID you need to grant location permissions to Python. I posted a solution to get that working in the post that you referenced: https://stackoverflow.com/a/75843844/648788

    Edit: I have added HT, country, and security. This is a simplified version of security that does not match the output of the airport command but is probably what most people want anyways. To make it match the previous output would require a lot of mapping and knowing all the possible outputs of the command.

    Example output with all identifying information changed:

                            SSID BSSID             RSSI CHANNEL HT CC SECURITY
                        example1 XX:XX:XX:XX:XX:XX -34  153     Y  -- WPA2 Personal
                        example2 XX:XX:XX:XX:XX:XX -56  44      Y  US WPA2 Personal
                        example3 XX:XX:XX:XX:XX:XX -48  6       Y  US WPA2 Personal
                        example4 XX:XX:XX:XX:XX:XX -74  149     Y  US WPA2 Personal
                        example5 XX:XX:XX:XX:XX:XX -84  36      Y  US WPA2 Personal
                        example6 XX:XX:XX:XX:XX:XX -65  6       Y  US WPA2 Enterprise
                        example7 XX:XX:XX:XX:XX:XX -32  1       Y  US WPA2/WPA3 Personal