Skip to content

Instantly share code, notes, and snippets.

@Ultrawipf
Last active January 27, 2023 10:00
Show Gist options
  • Save Ultrawipf/53bc237ce11a1f5f1d86107989f62675 to your computer and use it in GitHub Desktop.
Save Ultrawipf/53bc237ce11a1f5f1d86107989f62675 to your computer and use it in GitHub Desktop.
Simucube and cube controls compatible bluetooth LE steering wheel to keyboard or vjoy using pyvjoy mapper
import asyncio
import platform
from time import time,sleep
from bleak import BleakClient,BleakScanner
VJOYMODE = True # Set true to use vjoy gamepad instead of keyboard
if VJOYMODE:
import pyvjoy
joy = pyvjoy.VJoyDevice(2)
print(joy)
buttonsToKeys = [1,2,3,4,5,6,7,8,9,10]
encoderKeys = [(11,12),(13,14),(15,16),(17,18)]
else:
import keyboard
buttonsToKeys = ["page down","page up","F13","F14","F15","F16","F17","F18","F19","F20"]
#buttonsToKeys = [",",".","F13","F14","F15","F16","F17","F18","F19","F20"]
encoderKeys = [("-","+"),("4","7"),("5","8"),("6","9")]
name = "SC2-CUBE_WHEEL" # Device name
BUTTON_UUID = "5a0c806c-eb42-48bd-8429-0e62bf93a4a6"
STATUS_UUID = "E35FCF33-0EF2-49C8-A3DF-352B666D4BEC"
encoderbits = [(2,3),(4,5),(6,7),(8,9)]
buttonbits = [0,1,12,13,14,15,19,20,21,22]
lastButtons = [False]*10
lastEncoder = [(False,False)]*4
address = None
async def getAddress(name):
devices = await BleakScanner.discover()
for d in devices:
if d.name == name:
print("Found",d)
return d.address
return None
def press(id,down):
#print("press ",id,down)
if VJOYMODE:
joy.set_button(buttonsToKeys[id],1 if down else 0)
else:
if down:
keyboard.press(buttonsToKeys[id])
else:
keyboard.release(buttonsToKeys[id])
def clickEncoder(id,dir):
#print("enc ",id,dir)
if VJOYMODE:
joy.set_button(encoderKeys[id][1 if dir else 0],1)
sleep(0.1)
joy.set_button(encoderKeys[id][1 if dir else 0],0)
else:
keyboard.press_and_release(encoderKeys[id][1 if dir else 0])
def decodeButton(sender,data):
bitstring = ''.join(format(byte, '08b')[::-1] for byte in data)
#high = [ i for i in range(len(bitstring)) if bitstring[i] == "1"]
buttons = [bitstring[i] == "1" for i in buttonbits]
#print(buttons)
# Decode buttons
for i,b in enumerate(buttons):
if b == lastButtons[i]:
continue
press(i,b)
lastButtons[i] = b
# Decode encoders (up/down)
for i,encoder in enumerate(encoderbits):
encoderValue = (bitstring[encoder[0]] == "1" , bitstring[encoder[1]] == "1")
lastVal = lastEncoder[i]
if(encoderValue == lastVal):
continue # No change
# calculate direction
direction = 0 # -1 down, 1 up
if(encoderValue[0] != lastVal[0]): # A changed
direction = -1 if encoderValue[1] == encoderValue[0] else 1
elif(encoderValue[1] != lastVal[1]): # B changed
direction = 1 if encoderValue[0] == encoderValue[1] else -1
lastEncoder[i] = encoderValue
# only update output if both pins are the same as there are 2 transistions per notch
if(encoderValue[0] == encoderValue[1]):
clickEncoder(i,direction == 1)
def statusHandler(sender, data):
voltage = (data[1]<<8) | data[0]
voltage = voltage /1000
print("Voltage: {}V".format(voltage))
async def runNotification(address):
async with BleakClient(address) as client:
await client.start_notify(STATUS_UUID, statusHandler)
await client.start_notify(BUTTON_UUID, decodeButton)
while client.is_connected:
await asyncio.sleep(5)
await client.stop_notify(BUTTON_UUID)
if __name__ == "__main__":
while(address == None):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
address = loop.run_until_complete(getAddress(name))
if(address == None):
print("Can not find device",name)
#exit()
# loop.set_debug(True)
loop.run_until_complete(runNotification(address))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment