nxbt/nxbt/cli.py
2021-09-07 16:41:15 -07:00

246 lines
7.5 KiB
Python

import argparse
from random import randint
from time import sleep
import os
from .nxbt import Nxbt, PRO_CONTROLLER
from .bluez import find_devices_by_alias
from .tui import InputTUI
parser = argparse.ArgumentParser()
parser.add_argument('command', default=False, choices=[
'webapp', 'demo', 'macro', 'tui', 'remote_tui', 'addresses'
],
help="""Specifies the nxbt command to run:
webapp - Runs web server and allows for controller/macro
input from a web browser.
demo - Runs a demo macro (please ensure that your Switch
is on the main menu's Change Grip/Order menu before running).
macro - Allows for input of a specified macro from the command line
(with the argument -s) or from a file (with the argument -f).
tui/remote_tui - Opens a TUI that allows for direct input from the keyboard
to the Switch.
addresses - Lists the Bluetooth MAC addresses for
all previously connected Nintendo Switches""")
parser.add_argument('-c', '--commands', required=False, default=False,
help="""Used in conjunction with the macro command. Specifies a
macro string or a file location to load a macro string from.""")
parser.add_argument('-r', '--reconnect', required=False, default=False, action='store_true',
help="""Used in conjunction with the macro or tui command. If specified,
nxbt will attmept to reconnect to any previously connected
Nintendo Switch.""")
parser.add_argument('-a', '--address', required=False, default=False,
help="""Used in conjunction with the macro or tui command. If specified,
nxbt will attmept to reconnect to a specific Bluetooth MAC address
of a Nintendo Switch.""")
parser.add_argument('-d', '--debug', required=False, default=False, action='store_true',
help="""Enables debug mode in nxbt.""")
parser.add_argument('-l', '--logfile', required=False, default=False, action='store_true',
help="""Enables logging to a file in the current working directory
instead of stderr.""")
parser.add_argument('-i', '--ip', required=False, default="0.0.0.0", type=str,
help="""Specifies the IP to run the webapp at. Defaults to 0.0.0.0""")
parser.add_argument('-p', '--port', required=False, default=8000, type=int,
help="""Specifies the port to run the webapp at. Defaults to 8000""")
args = parser.parse_args()
MACRO = """
B 0.1s
0.5s
B 0.1s
0.5s
B 0.1s
0.5s
B 0.1s
1.5s
DPAD_RIGHT 0.075s
0.075s
A 0.1s
1.5s
LOOP 12
DPAD_DOWN 0.075s
0.075s
A 0.1s
0.25s
DPAD_DOWN 0.93s
A 0.1s
0.25s
L_STICK_PRESS 0.1s
1.0s
L_STICK@-100+000 0.75s
L_STICK@+000+100 0.75s
L_STICK@+100+000 0.75s
L_STICK@+000-100 0.75s
B 0.1s
0.25s
R_STICK_PRESS 0.1s
1.0s
R_STICK@-100+000 0.75s
R_STICK@+000+100 0.75s
R_STICK@+100+000 0.75s
R_STICK@+000-100 0.75s
B 0.1s
0.1s
B 0.1s
0.1s
B 0.1s
0.1s
B 0.1s
0.4s
DPAD_LEFT 0.1s
0.1s
A 0.1s
1.5s
A 0.1s
5.0s
"""
def random_colour():
return [
randint(0, 255),
randint(0, 255),
randint(0, 255),
]
def check_bluetooth_address(address):
"""Check the validity of a given Bluetooth MAC address
:param address: A Bluetooth MAC address
:type address: str
:raises ValueError: If the Bluetooth address is invalid
"""
address_bytes = len(address.split(":"))
if address_bytes != 6:
raise ValueError("Invalid Bluetooth address")
def get_reconnect_target():
if args.reconnect:
reconnect_target = find_devices_by_alias("Nintendo Switch")
elif args.address:
check_bluetooth_address(args.address)
reconnect_target = args.address
else:
reconnect_target = None
return reconnect_target
def demo():
"""Loops over all available Bluetooth adapters
and creates controllers on each. The last available adapter
is used to run a macro.
"""
nx = Nxbt(debug=args.debug, log_to_file=args.logfile)
adapters = nx.get_available_adapters()
controller_idxs = []
for i in range(0, len(adapters)):
index = nx.create_controller(
PRO_CONTROLLER,
adapters[i],
colour_body=random_colour(),
colour_buttons=random_colour())
controller_idxs.append(index)
# Run a macro on the last controller
print("Running Demo...")
macro_id = nx.macro(controller_idxs[-1], MACRO, block=False)
while macro_id not in nx.state[controller_idxs[-1]]["finished_macros"]:
state = nx.state[controller_idxs[-1]]
if state['state'] == 'crashed':
print("An error occurred while running the demo:")
print(state['errors'])
exit(1)
sleep(1.0)
print("Finished!")
def macro():
"""Runs a macro from the command line.
The macro can be from a specified file, a command line string,
or input from the user in an interactive process.
"""
macro = None
if args.commands:
if os.path.isfile(args.commands):
with open(args.commands, "r") as f:
macro = f.read()
else:
macro = args.commands
else:
print("No macro commands were specified.")
print("Please use the -c argument to specify a macro string or a file location")
print("to load a macro string from.")
return
reconnect_target = get_reconnect_target()
nx = Nxbt(debug=args.debug, log_to_file=args.logfile)
print("Creating controller...")
index = nx.create_controller(
PRO_CONTROLLER,
colour_body=random_colour(),
colour_buttons=random_colour(),
reconnect_address=reconnect_target)
print("Waiting for connection...")
nx.wait_for_connection(index)
print("Connected!")
print("Running macro...")
macro_id = nx.macro(index, macro, block=False)
while (True):
if nx.state[index]["state"] == "crashed":
print("Controller crashed while running macro")
print(nx.state[index]["errors"])
break
if macro_id in nx.state[index]["finished_macros"]:
print("Finished running macro. Exiting...")
break
sleep(1/30)
def list_switch_addresses():
addresses = find_devices_by_alias("Nintendo Switch")
if not addresses or len(addresses) < 1:
print("No Switches have previously connected to this device.")
return
print("---------------------------")
print("| Num | Address |")
print("---------------------------")
for i in range(0, len(addresses)):
address = addresses[i]
print(f"| {i+1} | {address} |")
print("---------------------------")
def main():
if args.command == 'webapp':
from .web import start_web_app
start_web_app(ip=args.ip, port=args.port)
elif args.command == 'demo':
demo()
elif args.command == 'macro':
macro()
elif args.command == 'tui':
reconnect_target = get_reconnect_target()
tui = InputTUI(reconnect_target=reconnect_target)
tui.start()
elif args.command == 'remote_tui':
reconnect_target = get_reconnect_target()
tui = InputTUI(reconnect_target=reconnect_target, force_remote=True)
tui.start()
elif args.command == 'addresses':
list_switch_addresses()