Skip to content

Commit 89768e5

Browse files
committed
Added joycontrol-pluginloader
1 parent c9c6879 commit 89768e5

6 files changed

Lines changed: 205 additions & 0 deletions

File tree

JoycontrolPlugin/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from JoycontrolPlugin.commands import JoycontrolCommands
2+
from JoycontrolPlugin.plugin import JoycontrolPlugin
3+
from JoycontrolPlugin.loader import load_plugin

JoycontrolPlugin/commands.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import math
2+
import asyncio
3+
import time
4+
import logging
5+
6+
from joycontrol.controller_state import ControllerState
7+
from joycontrol.command_line_interface import ControllerCLI
8+
9+
logger = logging.getLogger(__name__)
10+
11+
MAX_STICK_POWER = 0xFFF/2
12+
13+
class JoycontrolCommands:
14+
def __init__(self, controller_state):
15+
self.cli = ControllerCLI(controller_state)
16+
self.controller_state = controller_state
17+
self.max_stick_power = MAX_STICK_POWER
18+
19+
def __calc_stick_position(self, angle, power):
20+
angle = (angle + 180) % 360 * -1
21+
22+
rad = math.radians(angle)
23+
x = power * math.cos(rad)
24+
y = power * math.sin(rad)
25+
26+
# Adjust the position of the circle.
27+
stick_radius = MAX_STICK_POWER
28+
h_val = x + stick_radius
29+
v_val = y + stick_radius
30+
return int(h_val), int(v_val)
31+
32+
async def stick(self, stick, direction=None, angle=None, power=MAX_STICK_POWER):
33+
if direction == 'center':
34+
angle = 0
35+
power = 0
36+
elif direction == 'left':
37+
angle = 0
38+
elif direction == 'up':
39+
angle = 90
40+
elif direction == 'right':
41+
angle = 180
42+
elif direction == 'down':
43+
angle = 270
44+
45+
if angle is None:
46+
raise ValueError('Missing angle')
47+
48+
logger.debug(f'Stick: {stick}, {angle}, {power}')
49+
h_val, v_val = self.__calc_stick_position(angle, power)
50+
await self.cli.cmd_stick(stick, 'horizontal', h_val)
51+
await self.cli.cmd_stick(stick, 'vertical', v_val)
52+
53+
async def left_stick(self, direction=None, angle=None, power=MAX_STICK_POWER):
54+
await self.stick('left', direction, angle, power)
55+
56+
async def right_stick(self, direction=None, angle=None, power=MAX_STICK_POWER):
57+
await self.stick('right', direction, angle, power)
58+
59+
async def button_press(self, *buttons):
60+
if not buttons:
61+
raise ValueError('No Buttons were given.')
62+
63+
logger.debug('Press {}'.format(', '.join(buttons)))
64+
button_state = self.controller_state.button_state
65+
for button in buttons:
66+
button_state.set_button(button)
67+
68+
# send report
69+
await self.controller_state.send()
70+
71+
async def button_release(self, *buttons):
72+
if not buttons:
73+
raise ValueError('No Buttons were given.')
74+
75+
logger.debug('Release {}'.format(', '.join(buttons)))
76+
button_state = self.controller_state.button_state
77+
for button in buttons:
78+
button_state.set_button(button, pushed=False)
79+
80+
# send report
81+
await self.controller_state.send()
82+
83+
async def button_push(self, *buttons, press_time_sec=0.1):
84+
await self.button_press(*buttons)
85+
await self.wait(press_time_sec)
86+
await self.button_release(*buttons)
87+
88+
async def wait(self, sec):
89+
logger.debug(f'Wait: {sec}')
90+
await asyncio.sleep(sec)

JoycontrolPlugin/loader.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import os
2+
import logging
3+
4+
from importlib.machinery import SourceFileLoader
5+
6+
logger = logging.getLogger(__name__)
7+
8+
def load_plugin(plugin, controller_state, *plugin_options):
9+
logger.info(f'Loading: {plugin}')
10+
module = SourceFileLoader(plugin, plugin).load_module()
11+
plugin_name = os.path.splitext(os.path.basename(plugin))[0]
12+
joycontrol_plugin = module.__dict__[plugin_name](controller_state, *plugin_options)
13+
return joycontrol_plugin

JoycontrolPlugin/plugin.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from JoycontrolPlugin.commands import JoycontrolCommands
2+
from abc import abstractmethod
3+
4+
class JoycontrolPlugin(JoycontrolCommands):
5+
def __init__(self, controller_state, options):
6+
super().__init__(controller_state)
7+
self.options = options
8+
9+
@abstractmethod
10+
async def run(self):
11+
pass

joycontrol-pluginloader.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/usr/bin/env python3
2+
3+
import argparse
4+
import asyncio
5+
import logging
6+
import os
7+
8+
from aioconsole import ainput
9+
from joycontrol import logging_default as log, utils
10+
from joycontrol.controller import Controller
11+
from joycontrol.controller_state import ControllerState, button_push
12+
from joycontrol.memory import FlashMemory
13+
from joycontrol.protocol import controller_protocol_factory
14+
from joycontrol.server import create_hid_server
15+
from importlib.machinery import SourceFileLoader
16+
from JoycontrolPlugin.loader import load_plugin
17+
18+
logger = logging.getLogger(__name__)
19+
20+
async def _main(args):
21+
# Create memory containing default controller stick calibration
22+
spi_flash = FlashMemory()
23+
24+
# Get controller name to emulate from arguments
25+
controller = Controller.from_arg('PRO_CONTROLLER')
26+
27+
with utils.get_output(path=None, default=None) as capture_file:
28+
factory = controller_protocol_factory(controller, spi_flash=spi_flash)
29+
ctl_psm, itr_psm = 17, 19
30+
transport, protocol = await create_hid_server(factory, reconnect_bt_addr=args.reconnect_bt_addr,
31+
ctl_psm=ctl_psm,
32+
itr_psm=itr_psm, capture_file=capture_file,
33+
device_id=args.device_id)
34+
35+
controller_state = protocol.get_controller_state()
36+
37+
try:
38+
# waits until controller is fully connected
39+
await controller_state.connect()
40+
joycontrol_plugin = load_plugin(args.plugin, controller_state, args.plugin_options)
41+
await joycontrol_plugin.run()
42+
except Exception as e:
43+
logger.error(e)
44+
finally:
45+
logger.info('Stopping communication...')
46+
await transport.close()
47+
48+
if __name__ == '__main__':
49+
# check if root
50+
if not os.geteuid() == 0:
51+
raise PermissionError('Script must be run as root!')
52+
53+
parser = argparse.ArgumentParser()
54+
parser.add_argument('plugin', type=str, help='joycontrol plugin path')
55+
parser.add_argument('plugin_options', nargs='*', help='joycontrol plugin options')
56+
parser.add_argument('-d', '--device_id')
57+
parser.add_argument('-r', '--reconnect_bt_addr', type=str, default=None,
58+
help='The Switch console Bluetooth address, for reconnecting as an already paired controller')
59+
parser.add_argument('-v', '--verbose', action='store_true')
60+
args = parser.parse_args()
61+
62+
if args.verbose:
63+
log.configure()
64+
else:
65+
log.configure(console_level=logging.INFO)
66+
67+
loop = asyncio.get_event_loop()
68+
loop.run_until_complete(
69+
_main(args)
70+
)

setup.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
2+
from setuptools import setup, find_packages
3+
4+
README = open('README.md', 'r').read()
5+
6+
setup(
7+
name='joycontrol-pluginloader',
8+
version='0.1',
9+
long_description=README,
10+
long_description_content_type='text/markdown',
11+
url='https://github.com/almtr/joycontrol-pluginloader',
12+
author='almtr',
13+
description='PluginLoader for mart1nro/joycontrol',
14+
packages=find_packages(),
15+
install_requires=[
16+
'hid', 'aioconsole', 'dbus-python', 'crc8'
17+
],
18+
)

0 commit comments

Comments
 (0)