initial
This commit is contained in:
commit
f8de4e500c
4 changed files with 262 additions and 0 deletions
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Python: test.py",
|
||||
"type": "python",
|
||||
"request": "launch",
|
||||
"program": "test.py",
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
156
pyiforce.py
Normal file
156
pyiforce.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
_rx_stream = None
|
||||
_pkt_handlers = {}
|
||||
|
||||
|
||||
class Packet:
|
||||
SOP = 0x2B
|
||||
MAX_LENGTH = 255 # TBD
|
||||
|
||||
INPUT_JOYSTICK = 0x01
|
||||
INPUT_WHEEL = 0x03
|
||||
EFFECT_STATES = 0x02
|
||||
|
||||
SET_CONTROL = 0x40
|
||||
CONTROL = 0x41
|
||||
SET_EFFECT_STATE = 0x42
|
||||
SET_OVERALL = 0x43
|
||||
|
||||
QUERY = 0xFF
|
||||
|
||||
Types = [
|
||||
INPUT_JOYSTICK,
|
||||
INPUT_WHEEL,
|
||||
EFFECT_STATES,
|
||||
SET_CONTROL,
|
||||
CONTROL,
|
||||
SET_EFFECT_STATE,
|
||||
SET_OVERALL,
|
||||
QUERY
|
||||
]
|
||||
|
||||
|
||||
class ControlType:
|
||||
DEAD_ZONE = 0x00
|
||||
IGNORE_DEADMAN_SWITCH = 0x01
|
||||
ENABLE_COMM_WATCHDOG = 0x02
|
||||
SET_SPRING_STRENGTH = 0x03
|
||||
ENABLE_SPRING = 0x04
|
||||
SET_AXIS_SATURATION = 0x05
|
||||
|
||||
|
||||
class OverallControlType:
|
||||
GAIN = 0x00
|
||||
|
||||
|
||||
class Query:
|
||||
# Queries
|
||||
BufferSize = 0x42 # ('B'uffer size)
|
||||
Manufacturer = 0x4d # ('M'anufacturer)
|
||||
Product = 0x50 # ('P'roduct)
|
||||
Version = 0x56 # ('V'ersion)
|
||||
NumberOfEffects = 0x4e # ('N'umber of effects)
|
||||
Effect = 0x45 # ('E')ffect
|
||||
|
||||
# Control
|
||||
Open = 0x4f # ('O'pen)
|
||||
Close = 0x43 # ('C')lose
|
||||
|
||||
|
||||
def tx_packet(command, data):
|
||||
cs = Packet.SOP
|
||||
yield Packet.SOP
|
||||
cs ^= command
|
||||
yield command
|
||||
cs ^= len(data)
|
||||
yield len(data)
|
||||
for d in data:
|
||||
cs ^= d
|
||||
yield d
|
||||
yield cs
|
||||
|
||||
|
||||
def rx_packet(instream):
|
||||
while instream:
|
||||
# sync start
|
||||
c = 0
|
||||
cs = Packet.SOP
|
||||
while c != Packet.SOP:
|
||||
c = next(instream)
|
||||
|
||||
# packet type
|
||||
pkt_type = next(instream)
|
||||
cs ^= pkt_type
|
||||
if pkt_type not in Packet.Types:
|
||||
continue
|
||||
|
||||
# packet length
|
||||
pkt_len = next(instream)
|
||||
cs ^= pkt_len
|
||||
assert pkt_len < Packet.MAX_LENGTH
|
||||
|
||||
# data
|
||||
data = []
|
||||
for _ in range(pkt_len):
|
||||
d = next(instream)
|
||||
cs ^= d
|
||||
data.append(d)
|
||||
|
||||
# checksum
|
||||
cs_in = next(instream)
|
||||
cs ^= cs_in
|
||||
|
||||
yield (pkt_type, data, (cs == 0))
|
||||
|
||||
|
||||
def create_rx_stream(input_stream):
|
||||
global _rx_stream
|
||||
_rx_stream = rx_packet(input_stream)
|
||||
|
||||
|
||||
def rx_pump():
|
||||
global _rx_stream
|
||||
command, data, ok = next(_rx_stream)
|
||||
if ok:
|
||||
handle_pkt(command, data)
|
||||
|
||||
|
||||
def set_handler(pkt_type, handler):
|
||||
global _pkt_handlers
|
||||
_pkt_handlers[pkt_type] = handler
|
||||
|
||||
|
||||
def handle_pkt(command, data):
|
||||
global _pkt_handlers
|
||||
if command == Packet.INPUT_WHEEL:
|
||||
output = {
|
||||
'axis': {
|
||||
'wheel': int.from_bytes(data[0:2], byteorder='little', signed=True) / 2048,
|
||||
'gas': 1 - data[2] / 255,
|
||||
'brake': 1 - data[3] / 255
|
||||
},
|
||||
'buttons': {
|
||||
'B': bool(data[5] & 0x20),
|
||||
'A': bool(data[5] & 0x10),
|
||||
|
||||
'4': bool(data[5] & 0x08),
|
||||
'3': bool(data[5] & 0x04),
|
||||
'2_PaddleDown': bool(data[5] & 0x02),
|
||||
'1_PaddleUp_Start': bool(data[5] & 0x01)
|
||||
},
|
||||
'hat': {
|
||||
'center': (data[6] & 0xF0) == 0xF0,
|
||||
'up': (data[6] & 0xF0) == 0x00,
|
||||
'down': (data[6] & 0xF0) == 0x40,
|
||||
'left': (data[6] & 0xF0) == 0x60,
|
||||
'right': (data[6] & 0xF0) == 0x20
|
||||
}
|
||||
}
|
||||
elif command == Packet.QUERY:
|
||||
output = {
|
||||
'query': data[0],
|
||||
'data': data[1:]
|
||||
}
|
||||
else:
|
||||
output = data
|
||||
if _pkt_handlers.get(command):
|
||||
_pkt_handlers[command](output)
|
15
pyvjoy_ffb.py
Normal file
15
pyvjoy_ffb.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from ctypes import CFUNCTYPE, c_void_p
|
||||
|
||||
# void CALLBACK FfbFunction1(PVOID FfbPacket, PVOID userdata)
|
||||
PYVJOY_CB_FUNC = CFUNCTYPE(None, c_void_p, c_void_p)
|
||||
|
||||
|
||||
def pyvjoy_ffb_default_cb(ffb_pkt, userdata):
|
||||
print(' '.join('%02x' % x for x in ffb_pkt))
|
||||
|
||||
|
||||
# typedef struct _FFB_DATA {
|
||||
# ULONG size;
|
||||
# ULONG cmd;
|
||||
# UCHAR *data;
|
||||
# } FFB_DATA;
|
76
test.py
Normal file
76
test.py
Normal file
|
@ -0,0 +1,76 @@
|
|||
import pyiforce as ifr
|
||||
import serial
|
||||
import pyvjoy
|
||||
|
||||
PORT = 'COM7'
|
||||
BAUD = 38400
|
||||
|
||||
|
||||
def send_pkt(port, command, data):
|
||||
pkt = bytes(ifr.tx_packet(command, data))
|
||||
port.write(pkt)
|
||||
|
||||
def hex_print(d):
|
||||
print(' '.join('%02x'%x for x in d))
|
||||
|
||||
def query_print(d):
|
||||
print('QUERY[%02x]:%s'%(d['query'],' '.join('%02x'%x for x in d['data'])))
|
||||
|
||||
def setup_wheel(port):
|
||||
ifr.set_handler(ifr.Packet.QUERY, query_print)
|
||||
send_pkt(port, ifr.Packet.QUERY, [ifr.Query.Manufacturer])
|
||||
ifr.rx_pump()
|
||||
send_pkt(port, ifr.Packet.QUERY, [ifr.Query.Product])
|
||||
ifr.rx_pump()
|
||||
send_pkt(port, ifr.Packet.QUERY, [ifr.Query.Version])
|
||||
ifr.rx_pump()
|
||||
send_pkt(port, ifr.Packet.QUERY, [ifr.Query.NumberOfEffects])
|
||||
ifr.rx_pump()
|
||||
send_pkt(port, ifr.Packet.QUERY, [ifr.Query.BufferSize])
|
||||
ifr.rx_pump()
|
||||
|
||||
# send_pkt(port, ifr.Packet.SET_OVERALL, [ifr.OverallControlType.GAIN, 0x80])
|
||||
send_pkt(port, ifr.Packet.QUERY, [ifr.Query.Open])
|
||||
|
||||
|
||||
def serial_reader(sp):
|
||||
while sp.is_open:
|
||||
in_byte = sp.read(1)[0]
|
||||
# print(f'rx:%02X'%in_byte)
|
||||
yield in_byte
|
||||
|
||||
|
||||
with serial.Serial(port=PORT, baudrate=BAUD) as port:
|
||||
|
||||
port.rts = True
|
||||
ifr.create_rx_stream(serial_reader(port))
|
||||
|
||||
setup_wheel(port)
|
||||
|
||||
j = pyvjoy.VJoyDevice(1)
|
||||
|
||||
|
||||
# def handle_wheel(w):
|
||||
# j.data.wAxisX = int(0x4000 * (w['axis']['wheel'] + 1))
|
||||
# j.data.wAxisY = int(0x8000 * w['axis']['gas'])
|
||||
# j.data.wAxisZ = int(0x8000 * w['axis']['brake'])
|
||||
# j.data.lButtons = (
|
||||
# 0x0000000000000001 if w['buttons']['A'] else 0) + (
|
||||
# 0x0000000000000002 if w['buttons']['B'] else 0) + (
|
||||
# 0x0000000000000004 if w['buttons']['1_PaddleUp_Start'] else 0) + (
|
||||
# 0x0000000000000008 if w['buttons']['2_PaddleDown'] else 0) + (
|
||||
# 0x0000000000000010 if w['buttons']['3'] else 0) + (
|
||||
# 0x0000000000000020 if w['buttons']['4'] else 0)
|
||||
# j.data.bHats = (
|
||||
# -1 if w['hat']['center'] else
|
||||
# 0 if w['hat']['up'] else
|
||||
# 1 if w['hat']['right'] else
|
||||
# 2 if w['hat']['down'] else
|
||||
# 3 if w['hat']['left'] else -1)
|
||||
# j.update()
|
||||
|
||||
# ifr.set_handler(ifr.Packet.INPUT_WHEEL, handle_wheel)
|
||||
ifr.set_handler(ifr.Packet.INPUT_WHEEL, print)
|
||||
|
||||
while True:
|
||||
ifr.rx_pump()
|
Loading…
Reference in a new issue