import os import sys import time import spidev import threading import subprocess import RPi.GPIO as GPIO from usb4vc_oled import oled_display_queue """ sudo apt install stm32flash OPTIONAL git clone https://git.code.sf.net/p/stm32flash/code stm32flash-code cd stm32flash-code sudo make install HAVE TO ASSERT BOOT0 THE WHOLE TIME /usr/local/bin/stm32flash -r hhh -a 0x3b /dev/i2c-1 """ SLAVE_REQ_PIN = 16 GPIO.setmode(GPIO.BCM) GPIO.setup(SLAVE_REQ_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN) GPIO.add_event_detect(SLAVE_REQ_PIN, GPIO.RISING) pcard_spi = spidev.SpiDev(0, 0) pcard_spi.max_speed_hz = 2000000 keyboard_opened_device_dict = {} mouse_opened_device_dict = {} gamepad_opened_device_dict = {} led_device_path = '/sys/class/leds' input_device_path = '/dev/input/by-path/' """ https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/input-event-codes.h#L38 xbox gamepad: EVENT TYPE: EV_ABS dpad: ABS_HAT0X ABS_HAT1X buttons: https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/input-event-codes.h#L385 sticks: ABS_X, ABS_Y, ABS_RX, ABS_RY """ # https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h EV_SYN = 0 EV_KEY = 1 EV_REL = 2 EV_ABS = 3 SYN_REPORT = 0 REL_X = 0x00 REL_Y = 0x01 REL_WHEEL = 0x08 SPI_BUF_INDEX_MAGIC = 0 SPI_BUF_INDEX_SEQNUM = 1 SPI_BUF_INDEX_MSG_TYPE = 2 SPI_MOSI_MSG_TYPE_NOP = 0 SPI_MOSI_MSG_TYPE_INFO_REQUEST = 1 SPI_MOSI_MSG_TYPE_SET_PROTOCOL = 2 SPI_MOSI_MSG_TYPE_REQ_ACK = 3 SPI_MOSI_MSG_TYPE_KEYBOARD_EVENT = 8 SPI_MOSI_MSG_TYPE_MOUSE_EVENT = 9 SPI_MOSI_MSG_TYPE_GAMEPAD_EVENT_RAW = 10 SPI_MOSI_MSG_TYPE_GAMEPAD_EVENT_MAPPED = 11 SPI_MISO_MSG_TYPE_NOP = 0 SPI_MISO_MSG_TYPE_INFO_REQUEST = 128 SPI_MISO_MSG_TYPE_KB_LED_REQUEST = 129 SPI_MOSI_MAGIC = 0xde SPI_MISO_MAGIC = 0xcd GP_BTN_SOUTH = 0x130 GP_BTN_EAST = 0x131 GP_BTN_C = 0x132 GP_BTN_NORTH = 0x133 GP_BTN_WEST = 0x134 GP_BTN_Z = 0x135 GP_BTN_TL = 0x136 GP_BTN_TR = 0x137 GP_BTN_TL2 = 0x138 GP_BTN_TR2 = 0x139 GP_BTN_SELECT = 0x13a GP_BTN_START = 0x13b GP_BTN_MODE = 0x13c GP_BTN_THUMBL = 0x13d GP_BTN_THUMBR = 0x13e nop_spi_msg_template = [SPI_MOSI_MAGIC] + [0]*31 info_request_spi_msg_template = [SPI_MOSI_MAGIC, 0, SPI_MOSI_MSG_TYPE_INFO_REQUEST] + [0]*29 set_protocl_spi_msg_template = [SPI_MOSI_MAGIC, 0, SPI_MOSI_MSG_TYPE_SET_PROTOCOL] + [0]*29 keyboard_event_spi_msg_template = [SPI_MOSI_MAGIC, 0, SPI_MOSI_MSG_TYPE_KEYBOARD_EVENT] + [0]*29 mouse_event_spi_msg_template = [SPI_MOSI_MAGIC, 0, SPI_MOSI_MSG_TYPE_MOUSE_EVENT] + [0]*29 gamepad_event_mapped_spi_msg_template = [SPI_MOSI_MAGIC, 0, SPI_MOSI_MSG_TYPE_GAMEPAD_EVENT_MAPPED] + [0]*29 def make_spi_msg_ack(): return [SPI_MOSI_MAGIC, 0, SPI_MOSI_MSG_TYPE_REQ_ACK] + [0]*29 def make_keyboard_spi_packet(input_data, kbd_id): result = list(keyboard_event_spi_msg_template) result[3] = kbd_id result[4] = input_data[2] result[5] = input_data[3] result[6] = input_data[4] return result def make_gamepad_spi_packet(gp_status_dict, gp_id, mode): result = list(gamepad_event_mapped_spi_msg_template) def change_kb_led(scrolllock, numlock, capslock): led_file_list = os.listdir(led_device_path) capslock_list = [os.path.join(led_device_path, x) for x in led_file_list if 'capslock' in x] numlock_list = [os.path.join(led_device_path, x) for x in led_file_list if 'numlock' in x] scrolllock_list = [os.path.join(led_device_path, x) for x in led_file_list if 'scrolllock' in x] for item in scrolllock_list: with open(os.path.join(item, 'brightness'), 'w') as led_file: led_file.write(str(scrolllock)) for item in numlock_list: with open(os.path.join(item, 'brightness'), 'w') as led_file: led_file.write(str(numlock)) for item in capslock_list: with open(os.path.join(item, 'brightness'), 'w') as led_file: led_file.write(str(capslock)) def raw_input_event_worker(): mouse_status_dict = {} mouse_button_state_list = [0] * 5 gamepad_status_dict = {} print("raw_input_event_worker started") while 1: # ----------------- KEYBOARD PARSING ----------------- for key in list(keyboard_opened_device_dict): try: data = keyboard_opened_device_dict[key][0].read(16) except OSError: keyboard_opened_device_dict[key][0].close() del keyboard_opened_device_dict[key] print("keyboard disappeared:", key) continue if data is None: continue data = list(data[8:]) if data[0] == EV_KEY: pcard_spi.xfer(make_keyboard_spi_packet(data, keyboard_opened_device_dict[key][1])) # ----------------- MOUSE PARSING ----------------- for key in list(mouse_opened_device_dict): try: data = mouse_opened_device_dict[key][0].read(16) except OSError: mouse_opened_device_dict[key][0].close() del mouse_opened_device_dict[key] print("mouse disappeared:", key) continue if data is None: continue """ 0 - 1 event_type 2 - 3 key code 4 - 7 key status """ data = list(data[8:]) # mouse movement and scrolling # buffer those values until a SYNC event if data[0] == EV_REL: if data[2] == REL_X: mouse_status_dict["x"] = data[4:6] if data[2] == REL_Y: mouse_status_dict["y"] = data[4:6] if data[2] == REL_WHEEL: mouse_status_dict["scroll"] = data[4:6] # mouse button pressed, send it out immediately if data[0] == EV_KEY: key_code = data[3] * 256 + data[2] if 0x110 <= key_code <= 0x117: mouse_button_state_list[data[2]-16] = data[4] to_transfer = list(mouse_event_spi_msg_template) to_transfer[10:13] = data[2:5] to_transfer[13:18] = mouse_button_state_list[:] to_transfer[3] = mouse_opened_device_dict[key][1] pcard_spi.xfer(to_transfer) """ Logitech unifying receiver identifies itself as a mouse, so need to handle keyboard presses here too for compatibility """ if 0x1 <= key_code <= 127: pcard_spi.xfer(make_keyboard_spi_packet(data, mouse_opened_device_dict[key][1])) # SYNC event happened, send out an update if data[0] == EV_SYN and data[2] == SYN_REPORT and len(mouse_status_dict) > 0: to_transfer = list(mouse_event_spi_msg_template) if 'x' in mouse_status_dict: to_transfer[4:6] = mouse_status_dict['x'] if 'y' in mouse_status_dict: to_transfer[6:8] = mouse_status_dict['y'] if 'scroll' in mouse_status_dict: to_transfer[8:10] = mouse_status_dict['scroll'] to_transfer[13:18] = mouse_button_state_list[:] to_transfer[3] = mouse_opened_device_dict[key][1] mouse_status_dict.clear() pcard_spi.xfer(to_transfer) # ----------------- GAMEPAD PARSING ----------------- for key in list(gamepad_opened_device_dict): try: data = gamepad_opened_device_dict[key][0].read(16) except OSError: gamepad_opened_device_dict[key][0].close() del gamepad_opened_device_dict[key] print("gamepad disappeared:", key) continue if data is None: continue data = list(data[8:]) gamepad_id = gamepad_opened_device_dict[key][1] if gamepad_id not in gamepad_status_dict: gamepad_status_dict[gamepad_id] = {} # gamepad button presses, send out immediately if data[0] == EV_KEY: key_code = data[3] * 256 + data[2] if not (GP_BTN_SOUTH <= key_code <= GP_BTN_THUMBR): continue gamepad_status_dict[gamepad_id][key_code] = data[4] # joystick / analogue trigger movements if data[0] == EV_ABS: abs_axes = data[3] * 256 + data[2] abs_value = int.from_bytes(data[4:8], byteorder='little', signed=True) # print(data) # print(hex(abs_axes), abs_value) # print() gamepad_status_dict[gamepad_id][abs_axes] = abs_value print(gamepad_status_dict) print(gamepad_opened_device_dict[key][2]) if GPIO.event_detected(SLAVE_REQ_PIN): slave_result = None for x in range(2): slave_result = pcard_spi.xfer(make_spi_msg_ack()) print(int(time.time()), slave_result) if slave_result[SPI_BUF_INDEX_MAGIC] == SPI_MISO_MAGIC and slave_result[SPI_BUF_INDEX_MSG_TYPE] == SPI_MISO_MSG_TYPE_KB_LED_REQUEST: change_kb_led(slave_result[3], slave_result[4], slave_result[5]) change_kb_led(slave_result[3], slave_result[4], slave_result[5]) def usb_device_scan_worker(): print("usb_device_scan_worker started") while 1: time.sleep(0.75) try: device_file_list = os.listdir(input_device_path) except FileNotFoundError: print("No input devices found") oled_display_queue.put((0, 'no input')) continue except Exception as e: print('list input device exception:', e) continue mouse_list = [os.path.join(input_device_path, x) for x in device_file_list if 'event-mouse' in x] keyboard_list = [os.path.join(input_device_path, x) for x in device_file_list if 'event-kbd' in x] gamepad_list = [os.path.join(input_device_path, x) for x in device_file_list if 'event-joystick' in x] for item in keyboard_list: if item not in keyboard_opened_device_dict: try: this_file = open(item, "rb") os.set_blocking(this_file.fileno(), False) device_index = 0 try: device_index = sum([int(x) for x in item.split(':')[1].split('.')][:2]) except: pass keyboard_opened_device_dict[item] = (this_file, device_index) print("opened keyboard", keyboard_opened_device_dict[item][1], ':' , item) except Exception as e: print("keyboard open exception:", e) continue for item in mouse_list: if item not in mouse_opened_device_dict: try: this_file = open(item, "rb") os.set_blocking(this_file.fileno(), False) device_index = 0 try: device_index = sum([int(x) for x in item.split(':')[1].split('.')][:2]) except: pass mouse_opened_device_dict[item] = (this_file, device_index) print("opened mouse", mouse_opened_device_dict[item][1], ':' , item) except Exception as e: print("mouse open exception:", e) continue for item in gamepad_list: if item not in gamepad_opened_device_dict: try: this_file = open(item, "rb") os.set_blocking(this_file.fileno(), False) device_index = 0 try: device_index = sum([int(x) for x in item.split(':')[1].split('.')][:2]) except: pass gamepad_info = subprocess.getoutput("timeout 0.5 evtest " + str(item)) gamepad_opened_device_dict[item] = (this_file, device_index, parse_evtest_info(gamepad_info)) print("opened gamepad", gamepad_opened_device_dict[item][1], ':' , item) except Exception as e: print("gamepad open exception:", e) continue def parse_evtest_info(input_str): input_str = input_str.replace('\r', '').split('Event type 3 (EV_ABS)\n')[-1].split('Event type')[0] current_axis_code = None info_dict = {} for line in input_str.split('\n'): line = line.strip().replace('\t', '') if " (ABS_" in line: current_axis_code = int(line.split('Event code ')[1].split(' (ABS_')[0]) info_dict[current_axis_code] = {} continue if current_axis_code is None or len(line) < 3: continue line_split = line.split(' ') info_dict[current_axis_code][line_split[0].lower()] = int(line_split[-1]) return info_dict raw_input_event_parser_thread = threading.Thread(target=raw_input_event_worker, daemon=True) usb_device_scan_thread = threading.Thread(target=usb_device_scan_worker, daemon=True) # raw_input_event_parser_thread.start() # usb_device_scan_thread.start() def get_pboard_info(): # send request pcard_spi.xfer(list(info_request_spi_msg_template)) time.sleep(0.01) # send an empty message to allow response to be shifted into RPi response = pcard_spi.xfer(list(nop_spi_msg_template)) time.sleep(0.01) response = pcard_spi.xfer(list(nop_spi_msg_template)) print(response) def set_protocol(): this_msg = list(set_protocl_spi_msg_template) this_msg[3] = 1 | 0x80 this_msg[4] = 4 | 0x80 pcard_spi.xfer(this_msg)