Skip to content

Instantly share code, notes, and snippets.

@drscotthawley
Last active December 2, 2023 13:49
Show Gist options
  • Save drscotthawley/63369e77796f2a24cefbbe0245a467cf to your computer and use it in GitHub Desktop.
Save drscotthawley/63369e77796f2a24cefbbe0245a467cf to your computer and use it in GitHub Desktop.

Revisions

  1. drscotthawley revised this gist Dec 2, 2023. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,6 @@
    import numpy as np

    pygame.font.init()
    GAME_FONT = pygame.font.Font(pygame.font.get_default_font(), 36)
    GAME_FONT = pygame.font.SysFont(None, 48)

    def stickchange(stick, center=[128,128],
  2. drscotthawley revised this gist Dec 2, 2023. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -10,6 +10,7 @@
    # 4. Run this script!

    # working on making the mic work. will require a usb connection.
    # No idea how to do the touchpad yet.

    import os
    import pygame
  3. drscotthawley revised this gist Dec 2, 2023. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -5,8 +5,8 @@

    # Instructions:
    # 1. Pair the DualSense controller with your computer
    # 2. Install hidapi system binary, e.g. on Mac: brew install hidapi numpy
    # 3. Install Python packages: pip install hidapi pygame
    # 2. Install hidapi system binary, e.g. on Mac: brew install hidapi
    # 3. Install Python packages: pip install hidapi pygame numpy
    # 4. Run this script!

    # working on making the mic work. will require a usb connection.
  4. drscotthawley revised this gist Dec 2, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@

    # Instructions:
    # 1. Pair the DualSense controller with your computer
    # 2. Install hidapi system binary, e.g. on Mac: brew install hidapi
    # 2. Install hidapi system binary, e.g. on Mac: brew install hidapi numpy
    # 3. Install Python packages: pip install hidapi pygame
    # 4. Run this script!

  5. drscotthawley revised this gist Dec 2, 2023. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@
    # Instructions:
    # 1. Pair the DualSense controller with your computer
    # 2. Install hidapi system binary, e.g. on Mac: brew install hidapi
    # 3. Install hidapi Python package: pip install hidapi
    # 3. Install Python packages: pip install hidapi pygame
    # 4. Run this script!

    # working on making the mic work. will require a usb connection.
  6. drscotthawley revised this gist Dec 2, 2023. 1 changed file with 31 additions and 30 deletions.
    61 changes: 31 additions & 30 deletions dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -1,8 +1,20 @@
    #! /usr/bin/env python

    # OS-Agnostic PS5 DualSense Controller Listener
    # Author: Scott H. Hawley

    # Instructions:
    # 1. Pair the DualSense controller with your computer
    # 2. Install hidapi system binary, e.g. on Mac: brew install hidapi
    # 3. Install hidapi Python package: pip install hidapi
    # 4. Run this script!

    # working on making the mic work. will require a usb connection.

    import os
    import pygame
    import hid
    import numpy as np
    import pygame # used for the ugly gui ;-)


    pygame.font.init()
    GAME_FONT = pygame.font.Font(pygame.font.get_default_font(), 36)
    @@ -27,20 +39,25 @@ def get_controller_ids():


    def get_controller_state(report, debug=False):
    connection = "bluetooth"
    if len(report) == 64:
    connection = "usb"
    elif len(report) != 8:
    assert False, "Unknown connection type"

    report = np.array(report)

    lstick, rstick = report[1:3], report[3:5]
    #lstick = stickchange(lstick)
    #rstick = stickchange(rstick)
    lstick, rstick = report[1:3], report[3:5]

    dpad_state = report[5] & 0x0F
    if debug: print("dpad_state =",dpad_state)
    button_pad_ind = 8 if 'usb'==connection else 5
    dpad_state = report[button_pad_ind] & 0x0F
    [dpadu, dpadr, dpadd, dpadl] = [dpad_state==y for y in [0,2,4,6]]
    shapes = report[5]
    shapes = report[button_pad_ind]
    [cross, circle, square, triangle] = [(shapes & (1<<y))!=0 for y in [5,6,4,7]]

    LRbuttons = report[6]
    [l1,r1,l2,r2,l3,r3] = [LRbuttons & y for y in [1,2,4,8,64,128]] # 16 & 32 go where?
    button_id = 9 if 'usb'==connection else 6
    [l1,r1,l2,r2,l3,r3] = [report[button_id] & y for y in [1,2,4,8,64,128]] # 16 & 32 go where?

    state = {'lstick':lstick, 'rstick':rstick,
    'dpadu':dpadu, 'dpadr':dpadr, 'dpadd':dpadd, 'dpadl':dpadl,
    'cross':cross, 'circle':circle, 'square':square, 'triangle':triangle,
    @@ -60,11 +77,8 @@ def get_controller_state(report, debug=False):


    def draw_buttons_and_joysticks(window, state):
    #pygame.font.init() # you have to call this at the start,
    # if you want to use this module.
    #my_font = pygame.font.SysFont('Comic Sans MS', 30)
    # Draw buttons (X, Circle, Square, Triangle)

    # Draw buttons (X, Circle, Square, Triangle)
    img = GAME_FONT.render("L1", True, 'red' if state['l1'] else 'grey')
    window.blit(img, (185, 60))
    img = GAME_FONT.render("L2", True, 'red' if state['l2'] else 'grey')
    @@ -109,11 +123,7 @@ def draw_buttons_and_joysticks(window, state):


    def draw_controller(window):
    pass
    #pygame.draw.rect(window, 'darkgrey', (250, 150, 500, 300)) # White rectangle
    #font = pygame.font.SysFont(None, 24)
    #img = GAME_FONT.render('hello', True, 'blue')
    #window.blit(img, (20, 20))
    pass # eh skip it for now


    def main():
    @@ -142,18 +152,8 @@ def main():
    report = gamepad.read(64)
    if report:
    report = np.array(report)
    if default is None: default = report # save initial state as defeault
    if not np.array_equal( report[:7] , default[:7]): # if anything changed

    state = get_controller_state(report)

    #for k, v in state.items():
    # if (v is not None) and (k not in ['lstick','rstick']) and (v):
    # if v!=False: print(f"{k}={v} ",end="", flush=False)
    # elif (k in ['lstick','rstick']) and (v is not None):
    # print(f"{k}={v}")
    #print("")

    state = get_controller_state(report)

    window.fill((0, 0, 0)) # Fill the screen with black
    draw_controller(window) # Draw the controller
    @@ -164,3 +164,4 @@ def main():

    if __name__ == "__main__":
    main()

  7. drscotthawley revised this gist Dec 2, 2023. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -17,7 +17,6 @@ def stickchange(stick, center=[128,128],
    return None

    def get_controller_ids():
    # print whichever device is the DualSense Controller
    vendor_id, product_id = None, None
    for device in hid.enumerate():
    if "DualSense" in device['product_string']:
  8. drscotthawley revised this gist Dec 2, 2023. 1 changed file with 132 additions and 50 deletions.
    182 changes: 132 additions & 50 deletions dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -1,20 +1,12 @@
    #! /usr/bin/env python

    # OS-Agnostic PS5 DualSense Controller Listener
    # Author: Scott H. Hawley

    # Instructions:
    # 1. Pair the DualSense controller with your computer
    # 2. Install hidapi system binary, e.g. on Mac: brew install hidapi
    # 3. Install hidapi Python package: pip install hidapi
    # 4. Run this script!

    # NB: This currently doesn't make use of the microphone but I'm working on that.
    # The mic doesn't work over bluetooth, only via a wired connection to the machine,
    # after which it just appears as a normal system audio input device.

    import os
    import hid
    import numpy as np
    import pygame # used for the ugly gui ;-)


    pygame.font.init()
    GAME_FONT = pygame.font.Font(pygame.font.get_default_font(), 36)
    GAME_FONT = pygame.font.SysFont(None, 48)

    def stickchange(stick, center=[128,128],
    tol=5, # tolerance in "pixels" b/c sticks sometimes get stuck a bit off-center
    @@ -35,51 +27,141 @@ def get_controller_ids():
    return vendor_id, product_id


    if __name__ == '__main__':
    def get_controller_state(report, debug=False):
    report = np.array(report)

    lstick, rstick = report[1:3], report[3:5]
    #lstick = stickchange(lstick)
    #rstick = stickchange(rstick)

    dpad_state = report[5] & 0x0F
    if debug: print("dpad_state =",dpad_state)
    [dpadu, dpadr, dpadd, dpadl] = [dpad_state==y for y in [0,2,4,6]]
    shapes = report[5]
    [cross, circle, square, triangle] = [(shapes & (1<<y))!=0 for y in [5,6,4,7]]

    LRbuttons = report[6]
    [l1,r1,l2,r2,l3,r3] = [LRbuttons & y for y in [1,2,4,8,64,128]] # 16 & 32 go where?
    state = {'lstick':lstick, 'rstick':rstick,
    'dpadu':dpadu, 'dpadr':dpadr, 'dpadd':dpadd, 'dpadl':dpadl,
    'cross':cross, 'circle':circle, 'square':square, 'triangle':triangle,
    'l1':l1, 'r1':r1, 'l2':l2, 'r2':r2, 'l3':l3,'r3':r3,}

    if debug:
    if lstick is not None:
    print(f"lstick = {lstick}, ",end="",flush=True)
    if rstick is not None:
    print(f"rstick = {rstick}, ",end="",flush=True)
    buttons = [dpadu, dpadr, dpadd, dpadl, cross, circle, square, triangle,l1,r1,l2,r2,l3,r3]
    for button, label in zip(buttons,
    ["DPadU","DPadR","DpadD","DPadL","Cross","Circle","Square","Triangle","L1","R1","L2","R2","L3","R3"]):
    if button: print(f"{label}, ",end="",flush=True)

    return state


    def draw_buttons_and_joysticks(window, state):
    #pygame.font.init() # you have to call this at the start,
    # if you want to use this module.
    #my_font = pygame.font.SysFont('Comic Sans MS', 30)
    # Draw buttons (X, Circle, Square, Triangle)

    img = GAME_FONT.render("L1", True, 'red' if state['l1'] else 'grey')
    window.blit(img, (185, 60))
    img = GAME_FONT.render("L2", True, 'red' if state['l2'] else 'grey')
    window.blit(img, (185, 10))
    img = GAME_FONT.render("R1", True, 'red' if state['r1'] else 'grey')
    window.blit(img, (585, 60))
    img = GAME_FONT.render("R2", True, 'red' if state['r2'] else 'grey')
    window.blit(img, (585, 10))

    img = GAME_FONT.render("Δ", True, 'red' if state['triangle'] else 'grey')
    window.blit(img, (600, 150))
    #img = GAME_FONT.render("[]", True, 'red' if state['square'] else 'grey')
    #window.blit(img, (550, 200))
    pygame.draw.rect(window, 'red' if state['square'] else 'grey', (550, 205, 20, 20))

    img = GAME_FONT.render("O", True, 'red' if state['circle'] else 'grey')
    window.blit(img, (650, 200))
    img = GAME_FONT.render("X", True, 'red' if state['cross'] else 'grey')
    window.blit(img, (600, 250))

    img = GAME_FONT.render("^", True, 'red' if state['dpadu'] else 'grey')
    window.blit(img, (200, 150))
    img = GAME_FONT.render("<-", True, 'red' if state['dpadl'] else 'grey')
    window.blit(img, (150, 200))
    img = GAME_FONT.render("->", True, 'red' if state['dpadr'] else 'grey')
    window.blit(img, (250, 200))
    img = GAME_FONT.render("V", True, 'red' if state['dpadd'] else 'grey')
    window.blit(img, (200, 250))


    # Draw joysticks
    lcenter, rcenter = np.array((300,400)), np.array((500, 400))
    scale = 0.5
    lstickpos = lcenter + scale*(np.array(state['lstick']) - np.array((128,128)))
    pygame.draw.circle(window, 'grey', lcenter, 5) # left center dot
    pygame.draw.circle(window, 'red' if state['l3'] else 'blue', lstickpos, 20) # left active dot

    rstickpos = rcenter + scale*(np.array(state['rstick']) - np.array((128,128)))
    pygame.draw.circle(window, 'grey', rcenter, 5) # right center dot
    pygame.draw.circle(window, 'red' if state['r3'] else 'blue', rstickpos, 20) # left active dot



    def draw_controller(window):
    pass
    #pygame.draw.rect(window, 'darkgrey', (250, 150, 500, 300)) # White rectangle
    #font = pygame.font.SysFont(None, 24)
    #img = GAME_FONT.render('hello', True, 'blue')
    #window.blit(img, (20, 20))


    def main():

    vendor_id, product_id = get_controller_ids()
    assert (vendor_id is not None) and (product_id is not None), "No DualSense controller found."

    gamepad = hid.device()
    gamepad.open(vendor_id, product_id)
    gamepad.set_nonblocking(True)

    default = None # safe the non-active state of the controller
    print("Listening...")
    while True:
    report = gamepad.read(64) # get the tate of the controller

    pygame.init()

    width, height = 800, 500
    window = pygame.display.set_mode((width, height))
    pygame.display.set_caption("PS5 Controller GUI")

    running = True
    default, state = None, None # save the non-active state of the controller
    while running:
    for event in pygame.event.get():
    if event.type == pygame.QUIT:
    running = False

    report = gamepad.read(64)
    if report:
    report = np.array(report)

    if default is None: default = report # save initial state as defeault
    if not np.array_equal( report[:7] , default[:7]): # if anything changed

    ##-----0 get values of sticks and buttons
    lstick, rstick = report[1:3], report[3:5]
    lstick = stickchange(lstick)

    dpad_state = report[5] & 0x0F
    [dpadu, dpadr, dpadd, dpadl] = [dpad_state==y for y in [0,2,4,6]]
    shapes = report[5]
    [cross, circle, square, triangle] = [(shapes & (1<<y))!=0 for y in [5,6,4,7]]

    LRbuttons = report[6]
    [l1,r1,l2,r2,l3,r3] = [LRbuttons & y for y in [1,2,4,8,64,128]] # 16 & 32 go where?


    ##------- print out our values for sticks and buttons

    # not sure what bits for 16 and 32 do so print them out...
    if (LRbuttons & 16) or (LRbuttons & 32): print("report[6] =",report[6])

    if lstick is not None:
    print(f"lstick = {lstick}, ",end="",flush=True)
    rstick = stickchange(rstick)
    if rstick is not None:
    print(f"rstick = {rstick}, ",end="",flush=True)
    buttons = [dpadu, dpadr, dpadd, dpadl, cross, circle, square, triangle,l1,r1,l2,r2,l3,r3]
    for button, label in zip(buttons,
    ["DPadU","DPadR","DpadD","DPadL","Cross","Circle","Square","Triangle","L1","R1","L2","R2","L3","R3"]):
    if button: print(f"{label}, ",end="",flush=True)
    state = get_controller_state(report)

    #for k, v in state.items():
    # if (v is not None) and (k not in ['lstick','rstick']) and (v):
    # if v!=False: print(f"{k}={v} ",end="", flush=False)
    # elif (k in ['lstick','rstick']) and (v is not None):
    # print(f"{k}={v}")
    #print("")


    window.fill((0, 0, 0)) # Fill the screen with black
    draw_controller(window) # Draw the controller
    if state: draw_buttons_and_joysticks(window, state)
    pygame.display.flip() # Update the screen with what we've drawn.

    pygame.quit()

    if any(y != False for y in buttons+[lstick is not None,rstick is not None]):
    print("") # newline
    if __name__ == "__main__":
    main()
  9. drscotthawley revised this gist Dec 1, 2023. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -9,6 +9,10 @@
    # 3. Install hidapi Python package: pip install hidapi
    # 4. Run this script!

    # NB: This currently doesn't make use of the microphone but I'm working on that.
    # The mic doesn't work over bluetooth, only via a wired connection to the machine,
    # after which it just appears as a normal system audio input device.

    import hid
    import numpy as np

  10. drscotthawley created this gist Dec 1, 2023.
    81 changes: 81 additions & 0 deletions dualsense_listener.py
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,81 @@
    #! /usr/bin/env python

    # OS-Agnostic PS5 DualSense Controller Listener
    # Author: Scott H. Hawley

    # Instructions:
    # 1. Pair the DualSense controller with your computer
    # 2. Install hidapi system binary, e.g. on Mac: brew install hidapi
    # 3. Install hidapi Python package: pip install hidapi
    # 4. Run this script!

    import hid
    import numpy as np

    def stickchange(stick, center=[128,128],
    tol=5, # tolerance in "pixels" b/c sticks sometimes get stuck a bit off-center
    ):
    for ax in [0,1]: # x ad y axes
    if stick[ax] < center[ax]-tol or stick[ax] > center[ax]+tol:
    return stick
    return None

    def get_controller_ids():
    # print whichever device is the DualSense Controller
    vendor_id, product_id = None, None
    for device in hid.enumerate():
    if "DualSense" in device['product_string']:
    vendor_id, product_id = device['vendor_id'], device['product_id']
    print(f"Found: {device['product_string']} at 0x{device['vendor_id']:04x}:0x{device['product_id']:04x}")
    break
    return vendor_id, product_id


    if __name__ == '__main__':
    vendor_id, product_id = get_controller_ids()
    assert (vendor_id is not None) and (product_id is not None), "No DualSense controller found."

    gamepad = hid.device()
    gamepad.open(vendor_id, product_id)
    gamepad.set_nonblocking(True)

    default = None # safe the non-active state of the controller
    print("Listening...")
    while True:
    report = gamepad.read(64) # get the tate of the controller
    if report:
    report = np.array(report)

    if default is None: default = report # save initial state as defeault
    if not np.array_equal( report[:7] , default[:7]): # if anything changed

    ##-----0 get values of sticks and buttons
    lstick, rstick = report[1:3], report[3:5]
    lstick = stickchange(lstick)

    dpad_state = report[5] & 0x0F
    [dpadu, dpadr, dpadd, dpadl] = [dpad_state==y for y in [0,2,4,6]]
    shapes = report[5]
    [cross, circle, square, triangle] = [(shapes & (1<<y))!=0 for y in [5,6,4,7]]

    LRbuttons = report[6]
    [l1,r1,l2,r2,l3,r3] = [LRbuttons & y for y in [1,2,4,8,64,128]] # 16 & 32 go where?


    ##------- print out our values for sticks and buttons

    # not sure what bits for 16 and 32 do so print them out...
    if (LRbuttons & 16) or (LRbuttons & 32): print("report[6] =",report[6])

    if lstick is not None:
    print(f"lstick = {lstick}, ",end="",flush=True)
    rstick = stickchange(rstick)
    if rstick is not None:
    print(f"rstick = {rstick}, ",end="",flush=True)
    buttons = [dpadu, dpadr, dpadd, dpadl, cross, circle, square, triangle,l1,r1,l2,r2,l3,r3]
    for button, label in zip(buttons,
    ["DPadU","DPadR","DpadD","DPadL","Cross","Circle","Square","Triangle","L1","R1","L2","R2","L3","R3"]):
    if button: print(f"{label}, ",end="",flush=True)

    if any(y != False for y in buttons+[lstick is not None,rstick is not None]):
    print("") # newline