Advertisement
IBNobody

Python Client

Mar 12th, 2019
105
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 12.53 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. import argparse
  3. import serial
  4. import select
  5. import struct
  6. import sys
  7. import time
  8. import math
  9.  
  10. parser = argparse.ArgumentParser('Client for sending controller commands to a controller emulator')
  11. parser.add_argument('port')
  12. args = parser.parse_args()
  13.  
  14. STATE_OUT_OF_SYNC   = 0
  15. STATE_SYNC_START    = 1
  16. STATE_SYNC_1        = 2
  17. STATE_SYNC_2        = 3
  18. STATE_SYNC_OK       = 4
  19.  
  20. # Actual Switch DPAD Values
  21. A_DPAD_CENTER    = 0x08
  22. A_DPAD_U         = 0x00
  23. A_DPAD_U_R       = 0x01
  24. A_DPAD_R         = 0x02
  25. A_DPAD_D_R       = 0x03
  26. A_DPAD_D         = 0x04
  27. A_DPAD_D_L       = 0x05
  28. A_DPAD_L         = 0x06
  29. A_DPAD_U_L       = 0x07
  30.  
  31. # Enum DIR Values
  32. DIR_CENTER    = 0x00
  33. DIR_U         = 0x01
  34. DIR_R         = 0x02
  35. DIR_D         = 0x04
  36. DIR_L         = 0x08
  37. DIR_U_R       = DIR_U + DIR_R
  38. DIR_D_R       = DIR_D + DIR_R
  39. DIR_U_L       = DIR_U + DIR_L
  40. DIR_D_L       = DIR_D + DIR_L
  41.  
  42. BTN_NONE         = 0x0000000000000000
  43. BTN_Y            = 0x0000000000000001
  44. BTN_B            = 0x0000000000000002
  45. BTN_A            = 0x0000000000000004
  46. BTN_X            = 0x0000000000000008
  47. BTN_L            = 0x0000000000000010
  48. BTN_R            = 0x0000000000000020
  49. BTN_ZL           = 0x0000000000000040
  50. BTN_ZR           = 0x0000000000000080
  51. BTN_MINUS        = 0x0000000000000100
  52. BTN_PLUS         = 0x0000000000000200
  53. BTN_LCLICK       = 0x0000000000000400
  54. BTN_RCLICK       = 0x0000000000000800
  55. BTN_HOME         = 0x0000000000001000
  56. BTN_CAPTURE      = 0x0000000000002000
  57.  
  58. DPAD_CENTER      = 0x0000000000000000
  59. DPAD_U           = 0x0000000000010000
  60. DPAD_R           = 0x0000000000020000
  61. DPAD_D           = 0x0000000000040000
  62. DPAD_L           = 0x0000000000080000
  63. DPAD_U_R         = DPAD_U + DPAD_R
  64. DPAD_D_R         = DPAD_D + DPAD_R
  65. DPAD_U_L         = DPAD_U + DPAD_L
  66. DPAD_D_L         = DPAD_D + DPAD_L
  67.  
  68. LSTICK_CENTER    = 0x0000000000000000
  69. LSTICK_R         = 0x00000000FF000000 #   0 (000)
  70. LSTICK_U_R       = 0x0000002DFF000000 #  45 (02D)
  71. LSTICK_U         = 0x0000005AFF000000 #  90 (05A)
  72. LSTICK_U_L       = 0x00000087FF000000 # 135 (087)
  73. LSTICK_L         = 0x000000B4FF000000 # 180 (0B4)
  74. LSTICK_D_L       = 0x000000E1FF000000 # 225 (0E1)
  75. LSTICK_D         = 0x0000010EFF000000 # 270 (10E)
  76. LSTICK_D_R       = 0x0000013BFF000000 # 315 (13B)
  77.  
  78. RSTICK_CENTER    = 0x0000000000000000
  79. RSTICK_R         = 0x000FF00000000000 #   0 (000)
  80. RSTICK_U_R       = 0x02DFF00000000000 #  45 (02D)
  81. RSTICK_U         = 0x05AFF00000000000 #  90 (05A)
  82. RSTICK_U_L       = 0x087FF00000000000 # 135 (087)
  83. RSTICK_L         = 0x0B4FF00000000000 # 180 (0B4)
  84. RSTICK_D_L       = 0x0E1FF00000000000 # 225 (0E1)
  85. RSTICK_D         = 0x10EFF00000000000 # 270 (10E)
  86. RSTICK_D_R       = 0x13BFF00000000000 # 315 (13B)
  87.  
  88. NO_INPUT       = BTN_NONE + DPAD_CENTER + LSTICK_CENTER + RSTICK_CENTER
  89.  
  90. # Commands to send to MCU
  91. COMMAND_NOP        = 0x00
  92. COMMAND_SYNC_1     = 0x33
  93. COMMAND_SYNC_2     = 0xCC
  94. COMMAND_SYNC_START = 0xFF
  95.  
  96. # Responses from MCU
  97. RESP_USB_ACK       = 0x90
  98. RESP_UPDATE_ACK    = 0x91
  99. RESP_UPDATE_NACK   = 0x92
  100. RESP_SYNC_START    = 0xFF
  101. RESP_SYNC_1        = 0xCC
  102. RESP_SYNC_OK       = 0x33
  103.  
  104. def angle(angle, intensity):
  105.     # y is negative because on the Y input, UP = 0 and DOWN = 255
  106.     x =  int((math.cos(math.radians(angle)) * 0x7F) * intensity / 0xFF) + 0x80
  107.     y = -int((math.sin(math.radians(angle)) * 0x7F) * intensity / 0xFF) + 0x80
  108.     return x, y
  109.  
  110. def lstick_angle(angle, intensity):
  111.     return (intensity + (angle << 8)) << 24
  112.  
  113. def rstick_angle(angle, intensity):
  114.     return (intensity + (angle << 8)) << 44
  115.  
  116. def p_wait(waitTime):
  117.     t0 = time.perf_counter()
  118.     t1 = t0
  119.     while (t1 - t0 < waitTime):
  120.         t1 = time.perf_counter()
  121.  
  122. # Wait for data to be available on the serial port
  123. def wait_for_data(timeout = 1.0, sleepTime = 0.1):
  124.     t0 = time.perf_counter()
  125.     t1 = t0
  126.     inWaiting = ser.in_waiting
  127.     while ((t1 - t0 < sleepTime) or (inWaiting == 0)):
  128.         time.sleep(sleepTime)
  129.         inWaiting = ser.in_waiting
  130.         t1 = time.perf_counter()
  131.  
  132. # Read X bytes from the serial port (returns list)
  133. def read_bytes(size):
  134.     bytes_in = ser.read(size)
  135.     return list(bytes_in)
  136.  
  137. # Read 1 byte from the serial port (returns int)
  138. def read_byte():
  139.     bytes_in = read_bytes(1)
  140.     if len(bytes_in) != 0:
  141.         byte_in = bytes_in[0]
  142.     else:
  143.         byte_in = 0
  144.     return byte_in
  145.  
  146. # Discard all incoming bytes and read the last (latest) (returns int)
  147. def read_byte_latest():
  148.     inWaiting = ser.in_waiting
  149.     if inWaiting == 0:
  150.         inWaiting = 1
  151.     bytes_in = read_bytes(inWaiting)
  152.     if len(bytes_in) != 0:
  153.         byte_in = bytes_in[0]
  154.     else:
  155.         byte_in = 0
  156.     return byte_in
  157.  
  158. # Write bytes to the serial port
  159. def write_bytes(bytes_out):
  160.     ser.write(bytearray(bytes_out))
  161.     return
  162.  
  163. # Write byte to the serial port
  164. def write_byte(byte_out):
  165.     write_bytes([byte_out])
  166.     return
  167.  
  168. # Compute CRC8
  169. # https://www.microchip.com/webdoc/AVRLibcReferenceManual/group__util__crc_1gab27eaaef6d7fd096bd7d57bf3f9ba083.html
  170. def crc8_ccitt(old_crc, new_data):
  171.     data = old_crc ^ new_data
  172.  
  173.     for i in range(8):
  174.         if (data & 0x80) != 0:
  175.             data = data << 1
  176.             data = data ^ 0x07
  177.         else:
  178.             data = data << 1
  179.         data = data & 0xff
  180.     return data
  181.  
  182. # Send a raw packet and wait for a response (CRC will be added automatically)
  183. def send_packet(packet=[0x00,0x00,0x08,0x80,0x80,0x80,0x80,0x00], debug=False):
  184.     if not debug:
  185.         bytes_out = []
  186.         bytes_out.extend(packet)
  187.  
  188.         # Compute CRC
  189.         crc = 0
  190.         for d in packet:
  191.             crc = crc8_ccitt(crc, d)
  192.         bytes_out.append(crc)
  193.         ser.write(bytes_out)
  194.         # print(bytes_out)
  195.  
  196.         # Wait for USB ACK or UPDATE NACK
  197.         byte_in = read_byte()
  198.         commandSuccess = (byte_in == RESP_USB_ACK)
  199.     else:
  200.         commandSuccess = True
  201.     return commandSuccess
  202.  
  203. # Convert DPAD value to actual DPAD value used by Switch
  204. def decrypt_dpad(dpad):
  205.     if dpad == DIR_U:
  206.         dpadDecrypt = A_DPAD_U
  207.     elif dpad == DIR_R:
  208.         dpadDecrypt = A_DPAD_R
  209.     elif dpad == DIR_D:
  210.         dpadDecrypt = A_DPAD_D
  211.     elif dpad == DIR_L:
  212.         dpadDecrypt = A_DPAD_L
  213.     elif dpad == DIR_U_R:
  214.         dpadDecrypt = A_DPAD_U_R
  215.     elif dpad == DIR_U_L:
  216.         dpadDecrypt = A_DPAD_U_L
  217.     elif dpad == DIR_D_R:
  218.         dpadDecrypt = A_DPAD_D_R
  219.     elif dpad == DIR_D_L:
  220.         dpadDecrypt = A_DPAD_D_L
  221.     else:
  222.         dpadDecrypt = A_DPAD_CENTER
  223.     return dpadDecrypt
  224.  
  225. # Convert CMD to a packet
  226. def cmd_to_packet(command):
  227.     cmdCopy = command
  228.     low              =  (cmdCopy & 0xFF)  ; cmdCopy = cmdCopy >>  8
  229.     high             =  (cmdCopy & 0xFF)  ; cmdCopy = cmdCopy >>  8
  230.     dpad             =  (cmdCopy & 0xFF)  ; cmdCopy = cmdCopy >>  8
  231.     lstick_intensity =  (cmdCopy & 0xFF)  ; cmdCopy = cmdCopy >>  8
  232.     lstick_angle     =  (cmdCopy & 0xFFF) ; cmdCopy = cmdCopy >> 12
  233.     rstick_intensity =  (cmdCopy & 0xFF)  ; cmdCopy = cmdCopy >>  8
  234.     rstick_angle     =  (cmdCopy & 0xFFF)
  235.     dpad = decrypt_dpad(dpad)
  236.     left_x, left_y   = angle(lstick_angle, lstick_intensity)
  237.     right_x, right_y = angle(rstick_angle, rstick_intensity)
  238.  
  239.     packet = [high, low, dpad, left_x, left_y, right_x, right_y, 0x00]
  240.     # print (hex(command), packet, lstick_angle, lstick_intensity, rstick_angle, rstick_intensity)
  241.     return packet
  242.  
  243. # Send a formatted controller command to the MCU
  244. def send_cmd(command=NO_INPUT):
  245.     commandSuccess = send_packet(cmd_to_packet(command))
  246.     return commandSuccess
  247.  
  248. #Test all buttons except for home and capture
  249. def testbench_btn():
  250.     send_cmd(BTN_A) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  251.     send_cmd(BTN_B) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  252.     send_cmd(BTN_X) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  253.     send_cmd(BTN_Y) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  254.     send_cmd(BTN_PLUS) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  255.     send_cmd(BTN_MINUS) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  256.     send_cmd(BTN_LCLICK) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  257.     send_cmd(BTN_RCLICK) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  258.  
  259. # Test DPAD U / R / D / L
  260. def testbench_dpad():
  261.     send_cmd(DPAD_U) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  262.     send_cmd(DPAD_R) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  263.     send_cmd(DPAD_D) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  264.     send_cmd(DPAD_L) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  265.  
  266. # Test DPAD Diagonals - Does not register on switch due to dpad buttons
  267. def testbench_dpad_diag():
  268.     send_cmd(DPAD_U_R) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  269.     send_cmd(DPAD_D_R) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  270.     send_cmd(DPAD_D_L) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  271.     send_cmd(DPAD_U_L) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  272.  
  273. # Test Left Analog Stick
  274. def testbench_lstick():
  275.     #Test U/R/D/L
  276.     send_cmd(BTN_LCLICK) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  277.     send_cmd(LSTICK_U) ; p_wait(0.5)
  278.     send_cmd(LSTICK_R) ; p_wait(0.5)
  279.     send_cmd(LSTICK_D) ; p_wait(0.5)
  280.     send_cmd(LSTICK_L) ; p_wait(0.5)
  281.     send_cmd(LSTICK_U) ; p_wait(0.5)
  282.     send_cmd(LSTICK_CENTER) ; p_wait(0.5)
  283.  
  284.     # 360 Circle @ Full Intensity
  285.     for i in range(0,721):
  286.         cmd = lstick_angle(i + 90, 0xFF)
  287.         send_cmd(cmd)
  288.         p_wait(0.001)
  289.     send_cmd(LSTICK_CENTER) ; p_wait(0.5)
  290.  
  291.     # 360 Circle @ Partial Intensity
  292.     for i in range(0,721):
  293.         cmd = lstick_angle(i + 90, 0x80)
  294.         send_cmd(cmd)
  295.         p_wait(0.001)
  296.     send_cmd(LSTICK_CENTER) ; p_wait(0.5)
  297.  
  298. # Test Right Analog Stick
  299. def testbench_rstick():
  300.     #Test U/R/D/L
  301.     send_cmd(BTN_RCLICK) ; p_wait(0.5) ; send_cmd() ; p_wait(0.001)
  302.     send_cmd(RSTICK_U) ; p_wait(0.5)
  303.     send_cmd(RSTICK_R) ; p_wait(0.5)
  304.     send_cmd(RSTICK_D) ; p_wait(0.5)
  305.     send_cmd(RSTICK_L) ; p_wait(0.5)
  306.     send_cmd(RSTICK_U) ; p_wait(0.5)
  307.     send_cmd(RSTICK_CENTER) ; p_wait(0.5)
  308.  
  309.     # 360 Circle @ Full Intensity
  310.     for i in range(0,721):
  311.         cmd = RStick_angle(i + 90, 0xFF)
  312.         send_cmd(cmd)
  313.         p_wait(0.001)
  314.     send_cmd(RSTICK_CENTER) ; p_wait(0.5)
  315.  
  316.     # 360 Circle @ Partial Intensity
  317.     for i in range(0,721):
  318.         cmd = RStick_angle(i + 90, 0x80)
  319.         send_cmd(cmd)
  320.         p_wait(0.001)
  321.     send_cmd(RSTICK_CENTER) ; p_wait(0.5)
  322.  
  323. # Test Packet Speed
  324. def testbench_packet_speed(count=100, debug=False):
  325.     sum = 0
  326.     min = 999
  327.     max = 0
  328.     avg = 0
  329.     err = 0
  330.    
  331.     for i in range(0, count + 1):
  332.  
  333.         # Send packet and check time
  334.         t0 = time.perf_counter()
  335.         status = send_packet(debug=True)
  336.         t1 = time.perf_counter()
  337.        
  338.         # Count errors
  339.         if not status:
  340.             err += 1
  341.             print('Packet Error!')
  342.        
  343.         # Compute times
  344.         delta = t1 - t0
  345.         if delta < min:
  346.             min = delta
  347.         if delta > max:
  348.             max = delta
  349.         sum = sum + (t1 - t0)
  350.        
  351.     avg = sum / i
  352.     print('Min =', '{:.3f}'.format(min), 'Max =', '{:.3}'.format(max), 'Avg =', '{:.3f}'.format(avg), 'Errors =', err)
  353.    
  354. # Force MCU to sync
  355. def force_sync():
  356.     # Send 9x 0xFF's to fully flush out buffer on device
  357.     # Device will send back 0xFF (RESP_SYNC_START) when it is ready to sync
  358.     write_bytes([0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF])
  359.  
  360.     # Wait for serial data and read the last byte sent
  361.     wait_for_data()
  362.     byte_in = read_byte_latest()
  363.  
  364.     # Begin sync...
  365.     inSync = False
  366.     if byte_in == RESP_SYNC_START:
  367.         write_byte(COMMAND_SYNC_1)
  368.         byte_in = read_byte()
  369.         if byte_in == RESP_SYNC_1:
  370.             write_byte(COMMAND_SYNC_2)
  371.             byte_in = read_byte()
  372.             if byte_in == RESP_SYNC_OK:
  373.                 inSync = True
  374.     return inSync
  375.  
  376. # Start MCU syncing process
  377. def sync():
  378.     inSync = False
  379.  
  380.     # Try sending a packet
  381.     inSync = send_packet()
  382.     if not inSync:
  383.         # Not in sync: force resync and send a packet
  384.         inSync = force_sync()
  385.         if inSync:
  386.             inSync = send_packet()
  387.     return inSync
  388.  
  389. # Main Program
  390. def main():
  391.     ser = serial.Serial(port=args.port, baudrate=19200,timeout=1)
  392.     # ser = serial.Serial(port=args.port, baudrate=28800,timeout=1)
  393.     # ser = serial.Serial(port=args.port, baudrate=38400,timeout=1)
  394.  
  395.     Attempt to sync with the MCU
  396.     if not sync():
  397.         print('Could not sync!')
  398.  
  399.     if not send_cmd(BTN_A + DPAD_U_R + LSTICK_U + RSTICK_D_L):
  400.         print('Packet Error!')
  401.  
  402.     p_wait(0.05)
  403.  
  404.     if not send_cmd():
  405.         print('Packet Error!')
  406.  
  407.     ser.close
  408.  
  409. main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement