-
Notifications
You must be signed in to change notification settings - Fork 159
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MacOS WIFI Driver broken on >= Sonoma 14.x because airport
command is retired
#506
Comments
So One (not so straightforward) alternative could be to conditionally add pyobjc dependency if system is darwin (can rely on sys_platform marker https://python-poetry.org/docs/dependency-specification/#using-environment-markers) and make use of the native ObjC
Then something like:
|
airport
command is retired
This seems like a good solution; however, on my system, the SSID is If you have further input, please feel free to offer it! 😬 |
I do not own a Macintosh, and MacOS Ventura is the latest I can virtualize atm.
Dependencies:
I tested on MacOS 14.4.1 with Python 3.11 installed via brew and pyobjc 10.2. |
This works toward solving gopro#506
pyobjc-framework-corewlan will be replacing airport -s pyobjc-framework-corelocation is used to provide the permission to read scanned SSID This works toward solving gopro#506
Running macOS Sonoma 14.3.1 (23D60) and using the code below, I'm getting python3 -m venv /tmp/scantest
/tmp/scantest/bin/pip3 install pyobjc
...
/tmp/scantest/bin/python3
Python 3.10.13 (main, Sep 13 2023, 13:03:08) [Clang 14.0.3 (clang-1403.0.22.14.1)] on darwin >>> import CoreLocation
>>> from CoreWLAN import CWInterface, CWWiFiClient
>>> wifi_client: CWWiFiClient = CWWiFiClient.sharedWiFiClient()
>>> interface = wifi_client.interface()
>>> location_manager = CoreLocation.CLLocationManager.alloc().init()
>>> location_manager.startUpdatingLocation()
>>> networks, error = interface.scanForNetworksWithName_error_( None, None )
>>> print(networks)
{(
<CWNetwork: 0x600003afc4a0> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-27, channel=<CWChannel: 0x600003ad4040> [channelNumber=1(2GHz), channelWidth={20MHz}], ibss=0],
<CWNetwork: 0x600003afc500> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-84, channel=<CWChannel: 0x600003ad8800> [channelNumber=136(5GHz), channelWidth={80MHz}], ibss=0],
<CWNetwork: 0x600003afc580> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-77, channel=<CWChannel: 0x600003ad87d0> [channelNumber=11(2GHz), channelWidth={20MHz}], ibss=0],
<CWNetwork: 0x600003afc4f0> [ssid=(null), bssid=(null), security=WPA2 Personal, rssi=-38, channel=<CWChannel: 0x600003ad8830> [channelNumber=149(5GHz), channelWidth={80MHz}], ibss=0],
<CWNetwork: 0x600003afc670> [ssid=(null), bssid=(null), security=WPA/WPA2 Personal, rssi=-74, channel=<CWChannel: 0x600003ad8860> [channelNumber=10(2GHz), channelWidth={20MHz}], ibss=0]
)} |
weird...
I do not have any 14.3 systems at hand but working on virtualizing Sonoma. |
This works toward solving: gopro#506
I also am getting null SSID's on Sonoma 14.4.1. |
airport
command is retiredairport
command is retired
At least on the macbook someone landed me However this inconsistency seems to corroborate others people findings as mentioned in: I am not familiar with Apple's operating system and hardware but if, indeed, their low level tooling is unable to provide consistent results another option could be to adopt the same approach as for Windows and just try to connect until it either succeed or the maximum amount of attempts is reached. (This may even be a simpler option as it wouldn't require Location Service permission and dependency) |
I do think the finite retries combined with your check for location authorization is the only path forward right now. |
I am very interested in a solution to this too. It seems weird to me that special permissions are now required to access information that the logged in user can already access using the GUI. Also weird that Apple would choose to remove functionality from one utility without providing it in another one. I'm the author of a Ruby command line utility that reports and manages wifi state (https://github.com/keithrbennett/wifiwand) and this is a huge problem for my users and me too. In addition to removing the ability to display all available networks, the removal of the
|
FYI, I have been able to restore wifi functionality by using Swift scripts, shelling out to them and then ingesting and parsing their output. However, these Swift scripts use CoreWLAN and, I believe, will not work unless XCode is installed. Here is the script that lists the names of available networks: import Foundation
import CoreWLAN
class NetworkScanner {
var currentInterface: CWInterface
init?() {
// Initialize with the default Wi-Fi interface
guard let defaultInterface = CWWiFiClient.shared().interface(),
defaultInterface.interfaceName != nil else {
return nil
}
self.currentInterface = defaultInterface
self.scanForNetworks()
}
func scanForNetworks() {
do {
let networks = try currentInterface.scanForNetworks(withName: nil)
for network in networks {
print("\(network.ssid ?? "Unknown")")
}
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
}
NetworkScanner() You can put this code in a file with '.swift' extension, e.g. |
Thank you for the update above. I installed XCode to get the Swift interpreter but am having several issues: Issue 1When I run
Issue 2If I copy-paste the code into the swift interpreter (i.e. run Issue 3I tried compiling the code into a binary/app and that worked but the output showed "Unknown" for all the SSIDs (i.e. missing Location Services permission and running the application did not cause any popups) swiftc -framework CoreWLAN -o AvailableWifiNetworkListener AvailableWifiNetworkListener.swift Issue 4I tried making a minimal standalone app in the hopes of causing a Location Services popup, updating the swift code to write the SSIDs to a temporary file; however, the values are still "Unknown": tree AvailableWifiNetworkListener.app/
AvailableWifiNetworkListener.app/
└── Contents
├── Info.plist
└── MacOS
└── AvailableWifiNetworkListener
3 directories, 2 files cat AvailableWifiNetworkListener.app/Contents/Info.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>AvailableWifiNetworkListener</string>
<key>CFBundleIdentifier</key>
<string>com.example.mywifiapp</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app needs access to location for scanning Wi-Fi networks.</string>
</dict>
</plist> import Foundation
import CoreWLAN
class NetworkScanner {
var currentInterface: CWInterface
init?() {
// Initialize with the default Wi-Fi interface
guard let defaultInterface = CWWiFiClient.shared().interface(),
defaultInterface.interfaceName != nil else {
return nil
}
self.currentInterface = defaultInterface
self.scanForNetworks()
}
func scanForNetworks() {
let fileManager = FileManager.default
let fileURL = fileManager.temporaryDirectory.appendingPathComponent("wifiScanResults.txt")
do {
let networks = try currentInterface.scanForNetworks(withName: nil)
var results = [String]()
for network in networks {
results.append(network.ssid ?? "Unknown")
}
let content = results.joined(separator: "\n")
try content.write(to: fileURL, atomically: true, encoding: .utf8)
print("File written to \(fileURL.path)")
} catch {
print("Failed to scan networks or write to file: \(error)")
}
}
}
_ = NetworkScanner() cat /var/folders/tp/nb0kgz6935g59xrhs_rwsps40000gn/T/wifiScanResults.txt
Unknown
Unknown
Unknown
Unknown Note: I am not a macOS/Swift developer. All of the above stuff was kludged together with fingers crossed 🫠 |
@sfoley-gpqa You may just need to install XCode's command line tools: |
My team has had that installed for years 😬 xcode-select --install
xcode-select: note: Command line tools are already installed. Use "Software Update" in System Settings or the softwareupdate command line interface to install updates |
Issue 1 What version do you get when you run When you run I did some brief research and this error occurred in prior versions of Swift. I'm hoping that your version is not up to date, and an update will fix it? Issue 2 When running Issues 3 & 4 I had the same result. Github Copilot had the information and advice pasted below. This is more trouble than it's worth for me at the moment, since for my purposes it's ok not to compile the script: The issue might be related to the permissions required to access WiFi information on macOS. When you run a Swift script using the Swift interpreter (i.e., However, when you compile a Swift program into a binary and run it, it's not sandboxed in the same way. Therefore, it might not have the necessary permissions to access the WiFi information. To resolve this issue, you can try to add the necessary entitlements to your Swift program and then codesign it. This process involves creating an entitlements file, compiling your Swift program into a binary, and then signing that binary with the entitlements file. Here's a step-by-step guide:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.wifi.scan</key>
<true/>
</dict>
</plist>
swiftc -o networkscanner AvailableWifiNetworkLister.swift
codesign --entitlements entitlements.plist -s - networkscanner Please note that this process requires you to have a valid Developer ID Application certificate in your keychain. If you don't have one, you can create one in the Apple Developer portal. Also, keep in mind that this process might not work if your app is distributed outside of the Mac App Store, as the necessary entitlements might not be granted. |
Re: Issue 1pkgutil --pkg-info=com.apple.pkg.CLTools_Executables
package-id: com.apple.pkg.CLTools_Executables
version: 15.1.0.0.1.1700200546
volume: /
location: /
install-time: 1707601182
swift --version
Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
Target: arm64-apple-darwin23.3.0 This is very odd. I just installed Xcode yesterday in order to get Swift. Xcode says the version is I verified that I only have one version of Xcode installed several different ways so there doesn't seem to be a versioning conflict. Re: Issue 2Sorry for being unclear. I am more familiar with Python and its interpreter (i.e. Run swift
Welcome to Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5).
Type :help for assistance.
1> import Foundation
2> print("Hello, world!")
Hello, world! Re: Issues 3 & 4Thanks for taking the time to compare results. In order to scan for the camera's SSID after enable AP Mode, our teams need to have an automated way (SDK, API, CLI tool) to scan for SSIDs without having to do any manual steps like pasting Swift code into the interpreter (or, ideally, without having to install Swift at all. Hmmm....) I totally understand that you may not have time to dig much deeper into this. I appreciate the time you've put into it already! 😀 Re: CopilotI tried the steps without having the Developer ID Application certificate in my keychain as a litmus test but when I ran the compiled application, something Thanks again for your time and feedback! |
@keithrbennett Here's a strange half-baked idea. Since you're already managing this in the WifiWand package, I wonder if it would be feasible for me to consume WifiWand in my Python SDK here. I guess this would be something like:
Are you aware of any obvious pitfalls to this idea? Specifically:
For the sake of simplicity, let's assume that XCode is always available on the host machine. |
Re: Issue 1It seems that those tools were installed in Februrary, does that sound right? This is your installation timestamp converted to date in Ruby's :001 > Time.at(1707601182).utc
=> 2024-02-10 21:39:42 UTC Is it possible that you Re: Issue 2I'm surprised that you get that prompt when you run
Re: Issues 3 & 4I used Swift because it was simpler to run the Swift interpreter on a (text) source code file than to build a Mac executable with all the ceremony that entails. Your use case might justify using Objective C instead; it is lower level than Swift and may require fewer setup steps for your users. Also, for either language, if you built an application (rather than interpreting a script), then you would only need all the setup on a single dev machine to build the app. I realize that Swift is not working for built applications -- it's possible that Objective C would work ok, I don't know. Re: CopilotI tried building and running the application, and for me too, when I ran it, it immediately exited. I suspect that the OS knew that it was not a legit app and killed it. Fixing it may be as simple as getting a developer registration. I'm not thrilled about having to do that either. ;) Re: Calling WifiWand from Python@tcamise-gpsw, although you could certainly call a Ruby (or any other) executable from Python, there is no way that I know of to compile a Ruby application to binary. Also, you would still need to install all the Ruby infrastucture and gems (wifiwand plus its dependencies). It wouldn't help with building Ruby into a binary, but in other ways, it's possible that JRuby would work better for you, I don't know. JRuby is Ruby that is run by a Ruby interpreter written in Java and running on the JVM. From the point of view of the system, the Ruby app is really a Java app, so the configuration issues might be easier to resolve, since Java use is widespread. If your users' machines already have Java installed, and/or your organization uses Java applications, this might be worth looking into. However, it takes a couple of seconds for a JVM to start up, and that may be a dealbreaker for you. My gut feeling is that your best bet is to write something in Python yourselves. The wifi reporting and management part of wifiwand (as opposed to its UI) is quite simple and consists mostly of running Mac OS external applications. If you extract that functionality and tailor it for your specific use case, then I believe it would not be complex or overly time consuming. Regarding the Swift part, it's possible that Objective-C might turn out to be a better option if, for example, the CoreWLAN functionality can be statically linked into the distributed application, eliminating the need for it to be found on the user's system at runtime. I am available to help on a consulting basis if that would be helpful. |
Ok thank you for the information. I agree this doesn't seem worth following up on since there is no easy way to "compile" Ruby gems. To summarize this entire thread specifically in regards to the
So the simplest solution here is to (for MacOS >= 14):
I propose this as the immediate fix here and will start implementing / testing it soon. There can be further investigation to figure out:
FYI I have been contemplating splitting this WiFi "driver" functionality out of the Open GoPro Python SDK into its own package and will probably do this at some point. Then just consume it from the Python SDK. |
I was looking for a solution to a different problem, but maybe this will help with a solution. Just execute in the console: system_profiler SPAirPortDataType |
Vladislav -
Very interesting! And using system_profiler's `-json` option, the output is machine readable.
Using my `rexe` gem (https://github.com/keithrbennett/rexe), which does format conversions, can pull in the JSON as a hash, and can execute Ruby commands on a single command line , one can output just the network information, in pretty JSON mode, with the command:
system_profiler -json SPAirPortDataType | rexe -ij -mb -oJ 'self["SPAirPortDataType"].first["spairport_airport_interfaces"].first["spairport_airport_other_local_wireless_networks"]'
[
{
"_name": "dlink-204C-5GHz",
"spairport_network_channel": "149 (5GHz, 80MHz)",
"spairport_network_phymode": "802.11",
"spairport_network_type": "spairport_network_type_station",
"spairport_security_mode": "spairport_security_mode_wpa2_personal_mixed",
"spairport_signal_noise": "-93 dBm / -96 dBm"
},
...
]
(Although this works, it assumes a specific order of the hash content, so a more robust implementation is really needed; but this illustrates the concept.)
- Keith
… On Jul 25, 2024, at 22:37, VladislavGatsenko ***@***.***> wrote:
I was looking for a solution to a different problem, but maybe this will help with a solution. Just execute in the console:
system_profiler SPAirPortDataType
—
Reply to this email directly, view it on GitHub <#506 (comment)>, or unsubscribe <https://github.com/notifications/unsubscribe-auth/AAAG56TTUKIKZZMWE5F5AVLZOEERXAVCNFSM6AAAAABFYFDZ56VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENJQGQ4TEMZQHA>.
You are receiving this because you were mentioned.
|
That's interesting; I just found the same thing last week and recently integrated it into an internal SDK. I was going to post about it here presently but it seems you beat me to the punch! And Keith: Hahaha, I spent several hours implementing dual |
If it's useful, here is my def scan_for_ssid(ssid: str, timeout_sec: float = 30.0, invert: bool = False) -> bool:
"""
Determine if the local host can find a specific SSID by name
:param ssid: The name of the SSID to scan for
:param timeout_sec: How long to scan (seconds) before giving up (plus duration of one scan, which can take few sec)
:param invert: If True, verify that the SSID is _not_ found within the timeout
:return: True if the SSID was found (unless invert==True); False otherwise
"""
if timeout_sec is None:
timeout_sec = 10.0
path_system_profiler = Path('/usr/sbin/system_profiler')
current_platform = platform.system()
if current_platform != 'Darwin':
raise EnvironmentError(f'Platform not supported (yet): {current_platform}')
if not path_system_profiler.exists():
raise EnvironmentError(f'{path_system_profiler} not found. Unable to scan for SSIDs')
ssids: list[str] = []
print(f'Scanning for SSID: "{ssid}" (timeout: {timeout_sec:.2f} seconds)')
start_time = time.time()
while time.time() - start_time <= timeout_sec:
output: bytes = subprocess.check_output([str(path_system_profiler), 'SPAirPortDataType'])
text: str = output.decode()
regex = re.compile(r'\n\s+([\x20-\x7E]{1,32}):\n\s+PHY Mode:') # 0x20...0x7E --> ASCII for printable characters
for _ssid in sorted(list(set(regex.findall(text)))):
if _ssid not in ssids:
ssids.append(_ssid)
if not invert and ssid in ssids:
return True
#print(f'Scanning... ssids found: {sorted(ssids)}') # debug
return invert |
This is good to hear. I'm currently doing some general SDK maintenance and regression testing on MacOS so will bring this in. |
@sfoley-gpqa thank you, this works for me on 14.6.1 👍 |
I've merged this into the Python SDK. I'm fairly close to extracting the Wifi "Driver" out into its own package at which point the Python SDK will import it. But this won't happen for a few weeks at least. |
Apple has officially retired the airport command-line utility on macOS Sonoma 14.4
Component
Python SDK
Description
SSID Scan does not work anymore on recent Mac OS X version.
To Reproduce
Try connecting to a GoPro using Mac OS Sonoma 14.4
Expected behavior
Python SDK should migrate to wdutil as per Apple recomandation.
Screenshots
The text was updated successfully, but these errors were encountered: