├── .gitignore
├── LICENSE
├── README.md
├── boot.py
├── code.py
├── config.py
├── controls.py
├── direct_input.py
├── leds.py
├── lib
├── adafruit_hid
│ ├── __init__.mpy
│ ├── consumer_control.mpy
│ ├── consumer_control_code.mpy
│ ├── keyboard.mpy
│ ├── keyboard_layout_us.mpy
│ ├── keycode.mpy
│ └── mouse.mpy
└── neopixel.mpy
├── misc
└── cover.png
└── ofs.py
/.gitignore:
--------------------------------------------------------------------------------
1 | .pio
2 | .vscode/.browse.c_cpp.db*
3 | .vscode/c_cpp_properties.json
4 | .vscode/launch.json
5 | .vscode/ipch
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Jonathan Barket
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | [![Contributors][contributors-shield]][contributors-url]
7 | [![Forks][forks-shield]][forks-url]
8 | [![Stargazers][stars-shield]][stars-url]
9 | [![Issues][issues-shield]][issues-url]
10 | [![MIT License][license-shield]][license-url]
11 |
12 |
13 | OpenStickFirmware is open source software designed to handle any and all tasks required in a custom Fight Stick. It can handle being the brains of your entire stick, or just handling the bells and whistles while your Brook board talks to your console.
14 |
15 |
16 | Explore the Docs
17 | ·
18 | Report Bug
19 | ·
20 | Request Feature
21 |
22 |
23 |
24 |
25 | ## About The Project
26 |
27 | OSF is built with CircuitPython and intended to run on _any_ board it supports. Not only does this make the customization process friendlier for non-developers, but it also means a significant amount of power can be had for a mere four dollars.
28 |
29 |
30 | ## Getting Started
31 |
32 | Once you're installed CircuitPython on your device of choice, setting up OSF is as simple as downloading the files from this repository and placing them on the drive that appears on your computer.
33 |
34 | There's no need to compile anything or download additional software.
35 |
36 | ### Configuration
37 | Basic configuration is handled in _config.py_. In here, you can configure whether or not your board has WS2812b (or similar) LEDs, which pins each button is connected to and so on.
38 |
39 | Currently, the only _required_ buttons are left, down, right and up. Anything you don't need can just be commented out.
40 |
41 |
42 | ## Roadmap
43 |
44 | See the [open issues](https://github.com/SleepUnit/OpenStickFirmware/issues) for a list of proposed features (and known issues).
45 |
46 |
47 |
48 | ## Contributing
49 |
50 | Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.
51 |
52 | 1. Fork the Project
53 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
54 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
55 | 4. Push to the Branch (`git push origin feature/AmazingFeature`)
56 | 5. Open a Pull Request
57 |
58 |
59 |
60 | ## License
61 |
62 | Distributed under the MIT License. See `LICENSE` for more information.
63 |
64 |
65 |
66 | ## Contact
67 |
68 | Jonathan Barket - jbarket@sleepunit.com
69 |
70 | Project Link: [https://github.com/SleepUnit/OpenStickFirmware](https://github.com/SleepUnit/OpenStickFirmware)
71 |
72 |
73 |
74 |
75 |
76 | [contributors-shield]: https://img.shields.io/github/contributors/SleepUnit/OpenStickFirmware.svg?style=for-the-badge
77 | [contributors-url]: https://github.com/SleepUnit/OpenStickFirmware/graphs/contributors
78 | [forks-shield]: https://img.shields.io/github/forks/SleepUnit/OpenStickFirmware.svg?style=for-the-badge
79 | [forks-url]: https://github.com/SleepUnit/OpenStickFirmware/network/members
80 | [stars-shield]: https://img.shields.io/github/stars/SleepUnit/OpenStickFirmware.svg?style=for-the-badge
81 | [stars-url]: https://github.com/SleepUnit/OpenStickFirmware/stargazers
82 | [issues-shield]: https://img.shields.io/github/issues/SleepUnit/OpenStickFirmware.svg?style=for-the-badge
83 | [issues-url]: https://github.com/SleepUnit/OpenStickFirmware/repo/issues
84 | [license-shield]: https://img.shields.io/github/license/SleepUnit/OpenStickFirmware.svg?style=for-the-badge
85 | [license-url]: https://github.com/SleepUnit/OpenStickFirmware/blob/main/LICENSE.txt
--------------------------------------------------------------------------------
/boot.py:
--------------------------------------------------------------------------------
1 | import board
2 | import usb_hid
3 | from direct_input import DirectInput
4 |
5 | # Add Appropriate HID Descriptor.
6 | direct_input = DirectInput()
7 | devices = [direct_input.device()]
8 | usb_hid.enable(devices)
--------------------------------------------------------------------------------
/code.py:
--------------------------------------------------------------------------------
1 | import board
2 | import usb_hid
3 | import neopixel
4 | from ofs import OpenFightStick
5 | from controls import Button, Directional
6 | from leds import Leds
7 | from config import config
8 |
9 | # Setup LEDs if the appropriate configuration exists. Otherwise,
10 | # make sure we set pixels so that we don't have to do bootleg
11 | # Python undefined checking everywhere.
12 | pixels = None
13 |
14 | if config.get('led_pin') and config.get('led_num'):
15 | pixels = neopixel.NeoPixel(config.get('led_pin'), config.get('led_num'), brightness=0.3, auto_write=False)
16 |
17 | # Add buttons and their corresponding LEDs, if they exist in our config.
18 | def add_button(pin, hid_num, leds):
19 | if config.get(pin):
20 | buttons.append(Button(config.get(pin), hid_num, pixels, config.get(leds, [])))
21 |
22 | buttons = []
23 | add_button('1p_pin', 0, '1p_leds')
24 | add_button('2p_pin', 3, '2p_leds')
25 | add_button('3p_pin', 5, '3p_leds')
26 | add_button('4p_pin', 4, '4p_leds')
27 | add_button('1k_pin', 1, '1k_leds')
28 | add_button('2k_pin', 2, '2k_leds')
29 | add_button('3k_pin', 7, '3k_leds')
30 | add_button('4k_pin', 6, '4k_leds')
31 | add_button('select_pin', 8, 'select_leds')
32 | add_button('start_pin', 9, 'start_leds')
33 | add_button('home_pin', 12, 'home_leds')
34 | add_button('l3_pin', 10, 'l3_leds')
35 | add_button('r3_pin', 11, 'r3_leds')
36 | add_button('touch_pin', 13, 'touch_leds')
37 |
38 | # left, down, right, up
39 | dpad = Directional(config.get('left_pin'), config.get('down_pin'), config.get('right_pin'), config.get('up_pin'), pixels, config.get('left_leds', []), config.get('down_leds', []), config.get('right_leds', []), config.get('up_leds', []))
40 | ofs = OpenFightStick(usb_hid.devices)
41 |
42 | while True:
43 | for button in buttons:
44 | if button.pressed():
45 | ofs.press_button(button)
46 | button.start_animation()
47 | else:
48 | ofs.release_button(button)
49 | button.animate()
50 |
51 | ofs.move_hat(dpad.dpad())
52 | dpad.animate()
53 | if pixels:
54 | pixels.show()
55 |
--------------------------------------------------------------------------------
/config.py:
--------------------------------------------------------------------------------
1 | import board
2 |
3 | config = {
4 | # Have LEDs? Specify the pin and number of LEDs. These should be WS2812b or
5 | # other NeoPixel style LEDs. Comment these lines out otherwise.
6 | "led_pin": board.GP14,
7 | "led_num": 16,
8 |
9 | # Action Buttons. Specify the pin for any buttons your controller has. Comment out
10 | # any buttons you don't have. If your controller has LEDs for each button, also
11 | # provide the index of those. This may be one or many depending on how much you
12 | # live that RGB life.
13 | "1p_pin": board.GP0,
14 | "1p_leds": [4],
15 | "2p_pin": board.GP1,
16 | "2p_leds": [5],
17 | "3p_pin": board.GP2,
18 | "3p_leds": [6],
19 | "4p_pin": board.GP3,
20 | "4p_leds": [7],
21 |
22 | "1k_pin": board.GP4,
23 | "1k_leds": [8],
24 | "2k_pin": board.GP5,
25 | "2k_leds": [9],
26 | "3k_pin": board.GP6,
27 | "3k_leds": [10],
28 | "4k_pin": board.GP7,
29 | "4k_leds": [11],
30 |
31 | "select_pin": board.GP8,
32 | # "select_leds": [12],
33 | "start_pin": board.GP9,
34 | # "start_leds": [9],
35 | "home_pin": board.GP28,
36 | # "home_leds": [10],
37 | "l3_pin": board.GP17,
38 | # "l3_leds": [11],
39 | "r3_pin": board.GP16,
40 | # "r3_leds": [11],
41 | "touch_pin": board.GP18,
42 | # "touch_leds": [11],
43 |
44 | # Directional Buttons. More of the same. Currently, these pins are required. That's
45 | # probably shortsighted on my part, but it is what it is.
46 | "left_pin": board.GP10,
47 | "left_leds": [0],
48 | "down_pin": board.GP11,
49 | "down_leds": [1],
50 | "right_pin": board.GP12,
51 | "right_leds": [2],
52 | "up_pin": board.GP13,
53 | "up_leds": [3]
54 | }
--------------------------------------------------------------------------------
/controls.py:
--------------------------------------------------------------------------------
1 | import digitalio
2 | import board
3 | from leds import Leds
4 |
5 | class Button:
6 | def __init__(self, pin, num, pixels = None, pixel_group = []):
7 | self.num = num
8 | self.pixels = pixels
9 |
10 | self._button = digitalio.DigitalInOut(pin)
11 | self._button.direction = digitalio.Direction.INPUT
12 | self._button.pull = digitalio.Pull.UP
13 |
14 | self.leds = None
15 |
16 | if pixels and len(pixel_group) > 0:
17 | self.leds = Leds(pixels, pixel_group)
18 |
19 | def pressed(self):
20 | return not self._button.value
21 |
22 | def start_animation(self):
23 | if self.leds:
24 | self.leds.start("Wheel")
25 |
26 | def animate(self):
27 | if self.leds:
28 | self.leds.animate()
29 |
30 |
31 | class Directional:
32 | def __init__(self, left_pin, down_pin, right_pin, up_pin, pixels, left_pixel_group, down_pixel_group, right_pixel_group, up_pixel_group):
33 | self.left_leds = None
34 | self.down_leds = None
35 | self.right_leds = None
36 | self.up_leds = None
37 |
38 | if pixels:
39 | if len(left_pixel_group) > 0:
40 | self.left_leds = Leds(pixels, left_pixel_group)
41 | if len(down_pixel_group) > 0:
42 | self.down_leds = Leds(pixels, down_pixel_group)
43 | if len(right_pixel_group) > 0:
44 | self.right_leds = Leds(pixels, right_pixel_group)
45 | if len(up_pixel_group) > 0:
46 | self.up_leds = Leds(pixels, up_pixel_group)
47 |
48 | self._left = digitalio.DigitalInOut(left_pin)
49 | self._left.direction = digitalio.Direction.INPUT
50 | self._left.pull = digitalio.Pull.UP
51 |
52 | self._down = digitalio.DigitalInOut(down_pin)
53 | self._down.direction = digitalio.Direction.INPUT
54 | self._down.pull = digitalio.Pull.UP
55 |
56 | self._right = digitalio.DigitalInOut(right_pin)
57 | self._right.direction = digitalio.Direction.INPUT
58 | self._right.pull = digitalio.Pull.UP
59 |
60 | self._up = digitalio.DigitalInOut(up_pin)
61 | self._up.direction = digitalio.Direction.INPUT
62 | self._up.pull = digitalio.Pull.UP
63 |
64 |
65 | def animate(self):
66 | if self.left_leds:
67 | self.left_leds.animate()
68 | if self.down_leds:
69 | self.down_leds.animate()
70 | if self.right_leds:
71 | self.right_leds.animate()
72 | if self.up_leds:
73 | self.up_leds.animate()
74 |
75 | def dpad(self):
76 | return self._full_neutral_clean(not self._left.value, not self._down.value, not self._right.value, not self._up.value)
77 |
78 | def _full_neutral_clean(self, left = False, down = False, right = False, up = False):
79 | if up and down:
80 | up = False
81 | down = False
82 |
83 | if left and right:
84 | left = False
85 | right = False
86 |
87 | return self._post_clean(left, down, right, up)
88 |
89 | def _post_clean(self, left = False, down = False, right = False, up = False):
90 | if right and up:
91 | if self.right_leds:
92 | self.right_leds.start("Wheel")
93 | if self.up_leds:
94 | self.up_leds.start("Wheel")
95 | return 1
96 |
97 | if right and down:
98 | if self.right_leds:
99 | self.right_leds.start("Wheel")
100 | if self.down_leds:
101 | self.down_leds.start("Wheel")
102 | return 3
103 |
104 | if right:
105 | if self.right_leds:
106 | self.right_leds.start("Wheel")
107 | return 2
108 |
109 | if left and up:
110 | if self.left_leds:
111 | self.left_leds.start("Wheel")
112 | if self.up_leds:
113 | self.up_leds.start("Wheel")
114 | return 7
115 |
116 | if left and down:
117 | if self.left_leds:
118 | self.left_leds.start("Wheel")
119 | if self.down_leds:
120 | self.down_leds.start("Wheel")
121 | return 5
122 |
123 | if left:
124 | if self.left_leds:
125 | self.left_leds.start("Wheel")
126 | return 6
127 |
128 | if up:
129 | if self.up_leds:
130 | self.up_leds.start("Wheel")
131 | return 0
132 |
133 | if down:
134 | if self.down_leds:
135 | self.down_leds.start("Wheel")
136 | return 4
137 |
138 | return 8
--------------------------------------------------------------------------------
/direct_input.py:
--------------------------------------------------------------------------------
1 | import usb_hid
2 |
3 | class DirectInput:
4 | def __init__(self):
5 | self.report_id = 7
6 |
7 | def descriptor(self):
8 | return bytes((
9 | 0x05, 0x01, # USAGE_PAGE (Generic Desktop)
10 | 0x09, 0x05, # USAGE (Gamepad) - Very important for Switch
11 | 0xa1, 0x01, # COLLECTION (Application)
12 | 0x85, 0xFF, # 7 [SET AT RUNTIME]
13 |
14 | # 16 Buttons
15 | 0x05, 0x09, # USAGE_PAGE (Button)
16 | 0x19, 0x01, # USAGE_MINIMUM (Button 1)
17 | 0x29, 0x10, # USAGE_MAXIMUM (Button 16)
18 | 0x15, 0x00, # LOGICAL_MINIMUM (0)
19 | 0x25, 0x01, # LOGICAL_MAXIMUM (1)
20 | 0x75, 0x01, # REPORT_SIZE (1)
21 | 0x95, 0x10, # REPORT_COUNT (16)
22 | 0x55, 0x00, # UNIT_EXPONENT (0)
23 | 0x65, 0x00, # UNIT (None)
24 | 0x81, 0x02, # INPUT (Data,Var,Abs)
25 |
26 | # One Hat switches (8 Positions)
27 | 0x05, 0x01, # USAGE_PAGE (Generic Desktop)
28 | 0x09, 0x39, # USAGE (Hat switch)
29 | 0x15, 0x00, # LOGICAL_MINIMUM (0)
30 | 0x25, 0x07, # LOGICAL_MAXIMUM (7)
31 | 0x35, 0x00, # PHYSICAL_MINIMUM (0)
32 | 0x46, 0x3B, 0x01, # PHYSICAL_MAXIMUM (315)
33 | 0x65, 0x14, # UNIT (Eng Rot:Angular Pos)
34 | 0x75, 0x04, # REPORT_SIZE (4)
35 | 0x95, 0x01, # REPORT_COUNT (1)
36 | 0x81, 0x02, # INPUT (Data,Var,Abs)
37 |
38 | 0x65, 0x00,
39 | 0x95, 0x01,
40 | 0x81, 0x01,
41 |
42 | # X, Y, and Z Axis
43 | 0x15, 0x00, # LOGICAL_MINIMUM (0)
44 | 0x26, 0xff, 0x00, # LOGICAL_MAXIMUM (255)
45 | 0x75, 0x08, # REPORT_SIZE (8)
46 | 0x09, 0x01, # USAGE (Pointer)
47 | 0xA1, 0x00, # COLLECTION (Physical)
48 | 0x09, 0x30, # USAGE (x)
49 | 0x09, 0x31, # USAGE (y)
50 | 0x09, 0x32, # USAGE (z)
51 | 0x09, 0x35, # USAGE (rz)
52 | 0x95, 0x04, # REPORT_COUNT (4)
53 | 0x81, 0x02, # INPUT (Data,Var,Abs)
54 | 0xc0, # END_COLLECTION
55 | 0xc0 # END_COLLECTION
56 | ))
57 |
58 | def device(self):
59 | return usb_hid.Device(
60 | report_descriptor = self.descriptor(),
61 | usage_page = 0x1,
62 | usage = 0x5,
63 | in_report_length = 7,
64 | out_report_length = 1,
65 | report_id_index = self.report_id,
66 | )
67 |
68 |
69 |
--------------------------------------------------------------------------------
/leds.py:
--------------------------------------------------------------------------------
1 | import time
2 | import board
3 | import neopixel
4 |
5 | RED = (255, 0, 0)
6 | YELLOW = (255, 150, 0)
7 | GREEN = (0, 255, 0)
8 | CYAN = (0, 255, 255)
9 | BLUE = (0, 0, 255)
10 | PURPLE = (180, 0, 255)
11 |
12 | WHEEL_FRAMES = 64
13 | WHEEL_TOTAL_TIME = 1.4
14 | WHEEL_FRAME_TIME = (WHEEL_TOTAL_TIME) / WHEEL_FRAMES
15 |
16 | class Leds:
17 | def __init__(self, pixels, pixel_group):
18 | self.pixels = pixels
19 | self.pixel_group = pixel_group
20 | self.reset()
21 |
22 | def reset(self):
23 | self.current_frame = 0
24 | self.last_frame_time = None
25 | self.animation = None
26 | self.color((0, 0, 0))
27 |
28 | def start(self, animation):
29 | if self.current_frame < 1:
30 | self.current_frame = 0
31 | self.last_frame_time = None
32 | self.animation = animation
33 | else:
34 | self.current_frame = 0
35 |
36 | def color(self, color):
37 | for pixel in self.pixel_group:
38 | self.pixels[pixel] = color
39 |
40 | def animate(self):
41 | if self.animation == "Wheel":
42 | self.wheel()
43 |
44 | def wheel(self):
45 | if self.current_frame > WHEEL_FRAMES:
46 | self.reset()
47 | return
48 |
49 | if self.last_frame_time == None or time.monotonic() - self.last_frame_time > WHEEL_FRAME_TIME:
50 | self.color(self.wheel_position())
51 | self.last_frame_time = time.monotonic()
52 | self.current_frame = self.current_frame + 1
53 |
54 | def wheel_position(self):
55 | conversion = 255 / WHEEL_FRAMES
56 | pos = self.current_frame * conversion
57 |
58 | if pos < 0 or pos > 255:
59 | return (0, 0, 0)
60 | if pos < 85:
61 | return (255 - pos * 3, pos * 3, 0)
62 | if pos < 170:
63 | pos -= 85
64 | return (0, 255 - pos * 3, pos * 3)
65 | pos -= 170
66 | return (pos * 3, 0, 255 - pos * 3)
67 |
68 |
--------------------------------------------------------------------------------
/lib/adafruit_hid/__init__.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SleepUnit/OpenStickFirmware/9a8fcb8543a04dfb85c545fdd276072b56a631f8/lib/adafruit_hid/__init__.mpy
--------------------------------------------------------------------------------
/lib/adafruit_hid/consumer_control.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SleepUnit/OpenStickFirmware/9a8fcb8543a04dfb85c545fdd276072b56a631f8/lib/adafruit_hid/consumer_control.mpy
--------------------------------------------------------------------------------
/lib/adafruit_hid/consumer_control_code.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SleepUnit/OpenStickFirmware/9a8fcb8543a04dfb85c545fdd276072b56a631f8/lib/adafruit_hid/consumer_control_code.mpy
--------------------------------------------------------------------------------
/lib/adafruit_hid/keyboard.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SleepUnit/OpenStickFirmware/9a8fcb8543a04dfb85c545fdd276072b56a631f8/lib/adafruit_hid/keyboard.mpy
--------------------------------------------------------------------------------
/lib/adafruit_hid/keyboard_layout_us.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SleepUnit/OpenStickFirmware/9a8fcb8543a04dfb85c545fdd276072b56a631f8/lib/adafruit_hid/keyboard_layout_us.mpy
--------------------------------------------------------------------------------
/lib/adafruit_hid/keycode.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SleepUnit/OpenStickFirmware/9a8fcb8543a04dfb85c545fdd276072b56a631f8/lib/adafruit_hid/keycode.mpy
--------------------------------------------------------------------------------
/lib/adafruit_hid/mouse.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SleepUnit/OpenStickFirmware/9a8fcb8543a04dfb85c545fdd276072b56a631f8/lib/adafruit_hid/mouse.mpy
--------------------------------------------------------------------------------
/lib/neopixel.mpy:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SleepUnit/OpenStickFirmware/9a8fcb8543a04dfb85c545fdd276072b56a631f8/lib/neopixel.mpy
--------------------------------------------------------------------------------
/misc/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SleepUnit/OpenStickFirmware/9a8fcb8543a04dfb85c545fdd276072b56a631f8/misc/cover.png
--------------------------------------------------------------------------------
/ofs.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2021 Jonathan Barket
2 | # Copyright (c) 2018 Dan Halbert for Adafruit Industries
3 |
4 | # Permission is hereby granted, free of charge, to any person obtaining a copy
5 | # of this software and associated documentation files (the "Software"), to deal
6 | # in the Software without restriction, including without limitation the rights
7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | # copies of the Software, and to permit persons to whom the Software is
9 | # furnished to do so, subject to the following conditions:
10 |
11 | # The above copyright notice and this permission notice shall be included in all
12 | # copies or substantial portions of the Software.
13 |
14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 | # SOFTWARE.
21 |
22 | from adafruit_hid.keyboard import find_device
23 | import usb_hid
24 | import struct
25 | import time
26 |
27 | class OpenFightStick:
28 | def __init__(self, devices):
29 | self._device = find_device(devices, usage_page=0x1, usage=0x05)
30 |
31 | # report[0] buttons 1-8 (LSB is button 1)
32 | # report[1] buttons 9-16
33 | # report[2] hat 0-8
34 | # report[3] joystick 0 x: -127 to 127
35 | # report[4] joystick 0 y: -127 to 127
36 | # report[5] joystick 1 x: -127 to 127
37 | # report[6] joystick 1 y: -127 to 127
38 | self._report = bytearray(7)
39 |
40 | # Remember the last report as well, so we can avoid sending
41 | # duplicate reports.
42 | self._last_report = bytearray(7)
43 |
44 | # Store settings separately before putting into report. Saves code
45 | # especially for buttons.
46 | self._buttons_state = 0
47 | self._hat_position = 8
48 | self._joy_x = 0
49 | self._joy_y = 0
50 | self._joy_z = 0
51 | self._joy_r_z = 0
52 |
53 | # Send an initial report to test if HID device is ready.
54 | # If not, wait a bit and try once more.
55 | try:
56 | self.reset_all()
57 | except OSError:
58 | time.sleep(1)
59 | self.reset_all()
60 |
61 | def press_button(self, button):
62 | self._buttons_state |= 1 << button.num
63 | self._send()
64 |
65 | def release_button(self, button):
66 | self._buttons_state &= ~(1 << button.num)
67 | self._send()
68 |
69 | def move_hat(self, direction):
70 | """ DPad is interpreted as a hat. Values are 0-7 where N is 0, and the
71 | directions move clockwise. 8 is centered. """
72 | self._hat_position = direction
73 | self._send()
74 |
75 | def move_joysticks(self, x=None, y=None, z=None, r_z=None):
76 | if x is not None:
77 | self._joy_x = x
78 | if y is not None:
79 | self._joy_y = y
80 | if z is not None:
81 | self._joy_z = z
82 | if r_z is not None:
83 | self._joy_r_z = r_z
84 | self._send()
85 |
86 | def reset_all(self):
87 | """Return the fightstick to a neutral state"""
88 | self._buttons_state = 0
89 | self._hat_position = 8
90 | self._joy_x = 0
91 | self._joy_y = 0
92 | self._joy_z = 0
93 | self._joy_r_z = 0
94 | self._send(always=True)
95 |
96 | def _send(self, always=False):
97 | """Send a report with all the existing settings.
98 | If ``always`` is ``False`` (the default), send only if there have been changes.
99 | """
100 | struct.pack_into(
101 | "