Last active
January 27, 2023 04:36
-
-
Save Lokno/271f36547530ed33f9a94c9d304cf705 to your computer and use it in GitHub Desktop.
Python 3 Script for automatically connecting a PC to hotspot created by the Nintendo Switch to serve media to a smart device
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Python 3 Script for automatically connecting a PC to the temporary hotspot | |
# created by the Nintendo Switch to serve media to a smartphone | |
# | |
# How to Set Up the hotspot transfer from the Nintendo Switch: | |
# 1. From the HOME Menu select Album, then select a screenshot or video capture. | |
# 2. Select Sharing and Editing, then select Send to Smartphone. | |
# 3. Select Only This One or Send a Batch. | |
# 4. A screen with a QR code will appear with the encoded SSID and password. | |
# This is where the script will take over. | |
# | |
# Media served by Switch will likely be at: http://192.168.0.1/index.html | |
# | |
# Requires Python 3.6+ | |
# Requires opencv-python | |
# You must have a wifi adapter | |
# Supports Windows, Linux, and Mac OS. | |
# Linux requires nmcli and must be run with root privileges | |
from sys import exit,argv,platform | |
import subprocess | |
try: | |
import cv2 | |
except ModuleNotFoundError: | |
print('OpenCV Python Module Not Found') | |
print(' install via pip:') | |
print(' python -m pip install opencv-python') | |
exit(-1) | |
import os | |
from time import sleep | |
import signal | |
import keyboard | |
import webbrowser | |
# OpenCV index of the device capturing your Nintendo Switch | |
# Note that if you have a webcam, that might be assigned to 0 | |
video_capture_device_id = 0 | |
# delay after adding the wifi profile | |
delay_new_profile = 3 # seconds | |
# delay after detecting the QR code encoding the url | |
delay_detect_url = 1 # seconds | |
supported_platforms = ['win32','linux','darwin'] | |
class MacWifi(): | |
def __init__(self): | |
self.SSID = '' | |
self.password = '' | |
wifi_interface = os.popen("networksetup -listallhardwareports | grep -A 1 Wi-Fi | grep Device | cut -d ' ' -f 2").read() | |
self.wifi_interface = wifi_interface.strip() | |
def create_new_connection(self, SSID, password): | |
self.SSID = SSID | |
self.password = password | |
command = f'networksetup -addpreferredwirelessnetworkatindex {self.wifi_interface} "{self.SSID}" 0 WPA2 {self.password}' | |
os.system(command) | |
def connect(self): | |
command = f'networksetup -setairportnetwork {self.wifi_interface} "{self.SSID}"' | |
os.system(command) | |
def delete_profile(self): | |
command = f'networksetup -removepreferredwirelessnetwork {self.wifi_interface} "{self.SSID}"' | |
os.system(command) | |
def disconnect(self): | |
os.system(f"networksetup -setairportpower {self.wifi_interface} off") | |
class LinuxWifi(): | |
def __init__(self): | |
self.SSID = '' | |
self.password = '' | |
user_groups = os.popen("groups").read().strip().split(' ') | |
if 'root' not in user_groups: | |
print('ERROR: User not authorized to control networking') | |
exit(-1) | |
try: | |
subprocess.run(['nmcli', '--version'], check=True) | |
except subprocess.CalledProcessError: | |
print('ERROR: nmcli not found. Please install network-manager') | |
exit(-1) | |
wifi_interface = os.popen("nmcli -t -f device,type device | awk -F: '/wifi/ {print $1; exit}'").read() | |
self.wifi_interface = wifi_interface.strip() | |
def create_new_connection(self, SSID, password): | |
self.SSID = SSID | |
self.password = password | |
def connect(self): | |
command = f'nmcli device wifi connect "{self.SSID}" password "{self.password}" ifname {self.wifi_interface}' | |
os.system(command) | |
def delete_profile(self): | |
command = f'nmcli connection delete "{self.SSID}"' | |
os.system(command) | |
def disconnect(self): | |
os.system(f"nmcli device disconnect {self.wifi_interface}") | |
class WinWifi(): | |
def __init__(self): | |
self.SSID = '' | |
self.password = '' | |
def create_new_connection(self, SSID, password): | |
self.SSID = SSID | |
self.password = password | |
config = """<?xml version=\"1.0\"?> | |
<WLANProfile xmlns="http://www.microsoft.com/networking/WLAN/profile/v1"> | |
<name>"""+self.SSID+"""</name> | |
<SSIDConfig> | |
<SSID> | |
<name>"""+self.SSID+"""</name> | |
</SSID> | |
</SSIDConfig> | |
<connectionType>ESS</connectionType> | |
<connectionMode>auto</connectionMode> | |
<MSM> | |
<security> | |
<authEncryption> | |
<authentication>WPA2PSK</authentication> | |
<encryption>AES</encryption> | |
<useOneX>false</useOneX> | |
</authEncryption> | |
<sharedKey> | |
<keyType>passPhrase</keyType> | |
<protected>false</protected> | |
<keyMaterial>"""+self.password+"""</keyMaterial> | |
</sharedKey> | |
</security> | |
</MSM> | |
</WLANProfile>""" | |
file_path = os.path.expandvars(f'%TEMP%\\{self.SSID:s}.xml') | |
command = f'netsh wlan add profile filename="{file_path:s}" interface=Wi-Fi' | |
with open(file_path, 'w') as file: | |
file.write(config) | |
os.system(command) | |
def connect(self): | |
os.system("netsh wlan connect name=\""+self.SSID+"\" ssid=\""+self.SSID+"\" interface=Wi-Fi") | |
def delete_profile(self): | |
os.system(f'netsh wlan delete profile name="{self.SSID:s}" interface=Wi-Fi') | |
def disconnect(self): | |
os.system("netsh wlan disconnect interface=Wi-Fi") | |
# Expected Format: | |
# WIFI:S:<<ssid>>;T:<<type>>;P:<<pw>>;; | |
def decode_ssid(s): | |
ssid_str = None | |
type_str = None | |
pw_str = None | |
for p in s.split(';'): | |
items = p.split(':') | |
if p.startswith('WIFI') and len(items) == 3: | |
ssid_str = items[2] | |
elif p.startswith('T') and len(items) == 2: | |
type_str = items[1] | |
elif p.startswith('P') and len(items) == 2: | |
pw_str = items[1] | |
return ssid_str,type_str,pw_str | |
loop = True | |
def handler(signum, frame): | |
global loop | |
loop = False | |
if __name__ == "__main__": | |
signal.signal(signal.SIGINT, handler) | |
signal.signal(signal.SIGTERM, handler) | |
script_name = os.path.basename(argv[0]) | |
print(f'Connect to Nintendo Switch Python 3 Script ({script_name:s})\n-----------------\nType ctrl+c to quit\n') | |
if platform not in supported_platforms: | |
print('ERROR: Operating system not supported') | |
exit(-1) | |
# Optionally read video capture device index from arguments | |
if len(argv) > 1 and argv[1].isnumeric(): | |
video_capture_device_id = int(argv[1]) | |
if len(argv) > 2: | |
print(f'Additional arguments ignored (usage: {script_name:s} <device index (integer)>)') | |
elif len(argv) > 1: | |
print(f'Arguments ignored (usage: {script_name:s} <device index (integer)>)') | |
print(f'Opening capture device index {video_capture_device_id:d}...', end = '') | |
vid = cv2.VideoCapture(video_capture_device_id) | |
if not vid.isOpened: | |
print(f'\nCould not open video capture device index {video_capture_device_id:d}') | |
exit(-1) | |
else: | |
print(f' opened!') | |
detect = cv2.QRCodeDetector() | |
last_value = None | |
waiting_on_ssid_pw = True | |
waiting_on_url = False | |
print('Watching video for QR code...') | |
if platform == 'win32': | |
wc = WinWifi() | |
elif platform == "darwin": | |
wc = MacWifi() | |
elif platform == "linux": | |
wc = LinuxWifi() | |
else: | |
raise Exception(f"Unsupported operating system: {platform}") | |
while loop: | |
if waiting_on_ssid_pw or waiting_on_url: | |
ret, frame = vid.read() | |
value, _, _ = detect.detectAndDecode(frame) | |
if value and last_value != value: | |
print('New QR Code Detected!') | |
last_value = value | |
recognized = False | |
if waiting_on_ssid_pw: | |
if value.startswith('WIFI'): | |
print('SSID/PW received!') | |
recognized = True | |
ssid_str,_,pw_str = decode_ssid(value) | |
print(f'Adding profile for ssid {ssid_str:s}') | |
wc.create_new_connection(ssid_str, pw_str) | |
sleep(delay_new_profile) | |
print(f'Requesting connection to ssid {ssid_str:s}') | |
wc.connect() | |
waiting_on_ssid_pw = False | |
waiting_on_url = True | |
print('Waiting for QR code with encoded url...') | |
else: | |
if value.startswith('http'): | |
print('URL received!') | |
recognized = True | |
url_str = value | |
sleep(delay_detect_url) | |
waiting_on_url = False | |
webbrowser.open(url_str) | |
if not recognized: | |
print('Encoded value not recognized, continuing to wait...') | |
if keyboard.is_pressed("q"): | |
loop = False | |
print('Disconnecting and cleaning up...') | |
if not waiting_on_ssid_pw: | |
wc.disconnect() | |
wc.delete_profile() | |
vid.release() | |
cv2.destroyAllWindows() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment