Closed Dkenobi closed 4 years ago
Hey, sorry for the late reply.
The horizontal and vertical values of the sticks default to 0 in the implementation. That is why your character is moving south east.
To clarify, the memory dump is necessary, because I was yet to lazy to implement some of the required fields. The center and limit positions of the sticks are defined as calibration data in the memory. The switch reads those positions while pairing.
Values of the stick are defined in the l_stick_state and r_stick_state classes which are part of the controller state (controller_state.py). With the stick state class, you can call the set_center function to set the vertical and horizontal values of the stick as defined in the memory. Also you can call set_up, set_down, set_left and set_right to set the the stick to the defined limit positions.
If you want to want to set more precise stick positions, you have to calculate those yourself using the calibration data you can access with get_calibration. Horizontal and vertical values can be set with set_h and set_v respectively. Probably you should calculate them using percentages because the ranges depend on how the stick is calibrated. The API could definitely improve to include this feature.
I'd like to give you an example implementation, but truth is I haven't much used the sticks yet besides of how they are implemented in the command line interface.
Here is an example that I use for bicycling around in circles in pokemon sword, might not be the prettiest implementation but I think it gets the job done
You need to import the time and math libraries
def lerp(a, b, percentage):
return (percentage * a) + ((1 - percentage) * b)
def runInCircles(controller_state):
stick = controller_state.l_stick_state
calibration = stick.get_calibration()
maxUp = calibration.v_center + calibration.v_max_above_center
maxDown = calibration.v_center - calibration.v_max_below_center
maxRight = calibration.h_center + calibration.h_max_above_center
maxLeft = calibration.h_center - calibration.h_max_below_center
lastUpdatedTime = time.time()
while True:
deltaTime = time.time() - lastUpdatedTime
lastUpdatedTime = time.time()
stickAngle += deltaTime * 360 # rate of turning per second (360 degrees for second)
if stickAngle >= 360:
stickAngle -= 360
radians = math.radians(stickAngle)
# Cosine and Sine range between 1 and -1, so we need to offset it to 2 and 0
# and then divide it in half to be between 1 and 0 for the lerp function to work
# alternatively you can make the linear interpolation work between 2 and 0 but I prefer this way
horizontalPercentage = (math.cos(radians) + 1) / 2
verticalPercentage = (math.sin(radians) + 1) / 2
horizontalValue = int(lerp(maxRight, maxLeft, horizontalPercentage))
verticalValue = int(lerp(maxUp, maxDown, verticalPercentage))
stick.set_h(horizontalValue)
stick.set_v(verticalValue)
await asyncio.sleep(0)
Sorry for the very late response.
@mart1nro I see thanks for the guide. will try to implement it when I'm free!
@NullScope Cool! Thanks for sharing your implementation. I'll try it when I'm free! I think it looks good. Just a question where did you implement this? is it in the run_controller_cli.py? Apologies, I'm new to python oop.
What I did for the button pressed implementation is implementing it in the run_controller_cli with reference from the test test_controller_buttons function.
I got an additional question with regards to the Bluetooth disconnecting from my Switch like every hour once I run my button function. It is just me or is anyone facing this issue?
I believe I did use run_controller_cli.py as a starting point yes, here is a full file implementation example
I haven't had problems with disconnecting, but I have had problems where it sometimes skips an input, but that is to be expected sometimes to be fair
import argparse
import asyncio
import logging
import os
import time
import math
from contextlib import contextmanager
from joycontrol import logging_default as log
from joycontrol.controller import Controller
from joycontrol.controller_state import button_push
from joycontrol.memory import FlashMemory
from joycontrol.protocol import controller_protocol_factory
from joycontrol.server import create_hid_server
logger = logging.getLogger(__name__)
def lerp(a, b, percentage):
return (percentage * a) + ((1 - percentage) * b)
async def runInCircles(controller_state):
stick = controller_state.l_stick_state
calibration = stick.get_calibration()
maxUp = calibration.v_center + calibration.v_max_above_center
maxDown = calibration.v_center - calibration.v_max_below_center
maxRight = calibration.h_center + calibration.h_max_above_center
maxLeft = calibration.h_center - calibration.h_max_below_center
lastUpdatedTime = time.time()
while True:
deltaTime = time.time() - lastUpdatedTime
lastUpdatedTime = time.time()
stickAngle += deltaTime * 360 # rate of turning per second (360 degrees for second)
if stickAngle >= 360:
stickAngle -= 360
radians = math.radians(stickAngle)
# Cosine and Sine range between 1 and -1, so we need to offset it to 2 and 0
# and then divide it in half to be between 1 and 0 for the lerp function to work
# alternatively you can make the linear interpolation work between 2 and 0 but I prefer this way
horizontalPercentage = (math.cos(radians) + 1) / 2
verticalPercentage = (math.sin(radians) + 1) / 2
horizontalValue = int(lerp(maxRight, maxLeft, horizontalPercentage))
verticalValue = int(lerp(maxUp, maxDown, verticalPercentage))
stick.set_h(horizontalValue)
stick.set_v(verticalValue)
await asyncio.sleep(0)
async def _main(controller, reconnect_bt_addr=None, capture_file=None, spi_flash=None, device_id=None):
factory = controller_protocol_factory(controller, spi_flash=spi_flash)
ctl_psm, itr_psm = 17, 19
transport, protocol = await create_hid_server(factory, reconnect_bt_addr=reconnect_bt_addr, ctl_psm=ctl_psm,
itr_psm=itr_psm, capture_file=capture_file, device_id=device_id)
controller_state = protocol.get_controller_state()
controller_state.l_stick_state.set_center()
controller_state.r_stick_state.set_center()
await controller_state.connect()
await runInCircles(controller_state)
logger.info('Stopping communication...')
await transport.close()
if __name__ == '__main__':
# check if root
if not os.geteuid() == 0:
raise PermissionError('Script must be run as root!')
# setup logging
#log.configure(console_level=logging.ERROR)
log.configure()
parser = argparse.ArgumentParser()
parser.add_argument('controller', help='JOYCON_R, JOYCON_L or PRO_CONTROLLER')
parser.add_argument('-l', '--log')
parser.add_argument('-d', '--device_id')
parser.add_argument('--spi_flash')
parser.add_argument('-r', '--reconnect_bt_addr', type=str, default=None,
help='The Switch console Bluetooth address, for reconnecting as an already paired controller')
args = parser.parse_args()
if args.controller == 'JOYCON_R':
controller = Controller.JOYCON_R
elif args.controller == 'JOYCON_L':
controller = Controller.JOYCON_L
elif args.controller == 'PRO_CONTROLLER':
controller = Controller.PRO_CONTROLLER
else:
raise ValueError(f'Unknown controller "{args.controller}".')
spi_flash = None
if args.spi_flash:
with open(args.spi_flash, 'rb') as spi_flash_file:
spi_flash = FlashMemory(spi_flash_file.read())
# creates file if arg is given
@contextmanager
def get_output(path=None):
"""
Opens file if path is given
"""
if path is not None:
file = open(path, 'wb')
yield file
file.close()
else:
yield None
with get_output(args.log) as capture_file:
loop = asyncio.get_event_loop()
loop.run_until_complete(
_main(controller,
reconnect_bt_addr=args.reconnect_bt_addr,
capture_file=capture_file,
spi_flash=spi_flash,
device_id=args.device_id
)
)
Thanks for sharing your code! I'll let you know if I have any more questions. I guess I have managed to solve the disconnecting issue too.
Hi
Thanks for creating this cool project.
I am trying to write a function to do some automation task in pokemon sword I managed to automate the buttons but not the left and right stick. Could you point me the right direction as to how to write it?
Thanks.
I have also looked into issue #4 managed to create the memory dump. However once I run the cli with the dump my character was constantly going to the south east direction.