Skip to content

Commit 3a9ef3b

Browse files
committed
Updated python examples
1 parent d397f9f commit 3a9ef3b

File tree

5 files changed

+162
-54
lines changed

5 files changed

+162
-54
lines changed

doc/python/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@ This folder contains examples for the HID command system and a helper class for
33

44
For the HID FFB example you need to enable the EXT FFB mainclass.
55

6-
Check the [commands page](https://github.com/Ultrawipf/OpenFFBoard/wiki/Commands) for more information about the available commands.
6+
Check the [commands page](https://github.com/Ultrawipf/OpenFFBoard/wiki/Commands) for more information about the available commands.
7+
8+
hidapi is the preferred api. Install it using `pip install hidapi`.
9+
10+
An older pywinusb example is also available that only works on Windows.

doc/python/hid_ffb_example.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@
1414

1515
effects = []
1616

17-
def readData(cmdtype,cls,instance,cmd,val,addr):
17+
def readData(cmdtype,cls,inst,cmd,val,addr):
1818
if cls == FX_MANAGER and cmd == 2:
1919
effects.append(val)
2020
print("Got new effect at index",val)
2121

22-
print(f"Type: {cmdtype}, Class: {cls}.{instance}: cmd: {cmd}, val: {val}, addr: {addr}")
22+
print(f"Type: {cmdtype}, Class: {cls}.{inst}: cmd: {cmd}, val: {val}, addr: {addr}")
2323

2424
dev = OpenFFBoard(OpenFFBoard.findDevices()[0])
2525
dev.open()

doc/python/hidlib_example.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
from openffboard import OpenFFBoard
2-
import time
3-
4-
def readData(cmdtype,cls,instance,cmd,val,addr):
5-
print(f"Type: {cmdtype}, Class: {cls}.{instance}: cmd: {cmd}, val: {val}, addr: {addr}")
62

73
dev = OpenFFBoard(OpenFFBoard.findDevices()[0])
84
dev.open()
9-
dev.registerReadCallback(readData)
105

11-
dev.readData(0xA01,0,0) # Read power
12-
dev.writeData(0xA01,0,0,100) # Set power
6+
print(dev.readData(0xA01,0,0)) # Read power
7+
print(dev.writeData(0xA01,0,0,100)) # Set power
138

14-
time.sleep(0.5) # Wait until report is sent
159
dev.close()

doc/python/openffboard.py

Lines changed: 86 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,110 @@
1-
import pywinusb.hid as hid
1+
import hid # hidapi
22
import struct
33
import time
44

55
CMDTYPE_WRITE=0x00
66
CMDTYPE_READ= 0x01
77
CMDTYPE_WRITEADR=0x03
88
CMDTYPE_READADR= 0x04
9+
CMDTYPE_ACK = 0x0A
10+
CMDTYPE_ERR = 0x07
911

10-
# Pywinusb version
12+
# HIDAPI version
1113

12-
class OpenFFBoard():
14+
class OpenFFBoard_base():
1315
@staticmethod
1416
def findDevices(vid=0x1209,pid=0xFFB0):
15-
return hid.HidDeviceFilter(vendor_id = vid,product_id = pid).get_devices()
17+
devices = []
18+
for dev in hid.enumerate():
19+
if(dev["vendor_id"] == vid and dev["product_id"] == pid):
20+
devices.append(dev)
21+
22+
return devices
1623

17-
def __init__(self,device : hid.HidDevice):
18-
self.device = device
19-
self.callback = self.printData
20-
self.device.set_raw_data_handler(self.readDataCallback)
24+
def __init__(self,devdict : dict):
25+
self.devdict = devdict
26+
self.device = hid.device()
2127

22-
def printData(cmdtype,cls,instance,cmd,val,addr):
23-
print(f"Type: {cmdtype}, Class: {cls}.{instance}: cmd: {cmd}, val: {val}, addr: {addr}")
28+
def printData(cmdtype,cls,inst,cmd,val,addr):
29+
"""Helper callback to print data nicely"""
30+
print(f"Type: {cmdtype}, Class: {cls}.{inst}: cmd: {cmd}, val: {val}, addr: {addr}")
2431

2532
def open(self):
26-
self.device.open()
33+
"""Opens the device"""
34+
self.device.open(self.devdict["vendor_id"],self.devdict["product_id"])
35+
2736

2837
def close(self):
2938
self.device.close()
3039

31-
def registerReadCallback(self,callback):
32-
self.callback = callback
40+
41+
def make_command(self,cmdtype,cls,inst,cmd,data=0,adr=0):
42+
"""Generates a command packet"""
43+
buffer = bytearray()
44+
buffer += bytearray(struct.pack('B',0xA1)) # HIDCMD
45+
buffer += bytearray(struct.pack('B',cmdtype)) # type. (0 = write, 1 = read)
46+
buffer += bytearray(struct.pack('<H',cls))
47+
buffer += bytearray(struct.pack('B',inst))
48+
buffer += bytearray(struct.pack('<L',cmd))
49+
buffer += bytearray(struct.pack('<q',data))
50+
buffer += bytearray(struct.pack('<q',adr if adr else 0 ))
51+
return buffer
52+
53+
def parse_command(self,data):
54+
"""Returns a parsed packet as a dict
55+
Entries: "cmdtype":cmdtype,"cls":cls,"inst":instance,"cmd":cmd,"val":val,"addr":addr
56+
"""
57+
cmdtype = int(data[1])
58+
cls = int(struct.unpack('<H', bytes(data[2:4]))[0])
59+
instance = int(data[4])
60+
cmd = int(struct.unpack('<L', bytes(data[5:9]))[0])
61+
val = int(struct.unpack('<q', bytes(data[9:17]))[0])
62+
addr = int(struct.unpack('<q', bytes(data[17:25]))[0])
63+
return {"cmdtype":cmdtype,"cls":cls,"inst":instance,"cmd":cmd,"val":val,"addr":addr}
3364

3465

35-
# Callback in format callback(cmdtype,cls,instance,cmd,val,addr)
36-
def readDataCallback(self,data):
37-
if(data[0] == 0xA1):
38-
self.done = True
39-
cmdtype = int(data[1])
40-
cls = int(struct.unpack('<H', bytes(data[2:4]))[0])
41-
instance = int(data[4])
42-
cmd = int(struct.unpack('<L', bytes(data[5:9]))[0])
43-
val = int(struct.unpack('<Q', bytes(data[9:17]))[0])
44-
addr = int(struct.unpack('<Q', bytes(data[17:25]))[0])
45-
self.callback(cmdtype,cls,instance,cmd,val,addr)
66+
class OpenFFBoard(OpenFFBoard_base):
67+
def __init__(self,devdict):
68+
"""devdict is one entry of hid api enumerate list"""
69+
OpenFFBoard_base.__init__(self,devdict)
70+
self.callback = None
4671

47-
def writeData(self,cls,inst,cmd,data,adr=None,wait=True):
48-
self.sendCommand(CMDTYPE_WRITE if adr == None else CMDTYPE_WRITEADR,cls=cls ,inst=inst,cmd=cmd,data=data,adr=adr,wait=wait)
72+
def writeData(self,cls,inst,cmd,data,adr=None,timeout = 100000):
73+
"""Sends data to the FFBoard. Returns True on success"""
74+
reply = self.sendCommand(CMDTYPE_WRITE if adr is None else CMDTYPE_WRITEADR,cls=cls ,inst=inst,cmd=cmd,data=data,adr=adr,timeout=timeout)
75+
return reply["cmdtype"] != CMDTYPE_ERR
4976

50-
def readData(self,cls,inst,cmd,adr=None,wait=True):
51-
self.sendCommand(CMDTYPE_READ if adr == None else CMDTYPE_READADR,cls,inst,cmd,0,adr,wait=wait)
77+
def registerReadCallback(self,callback):
78+
"""Register a callback to also call this function on every received reply"""
79+
self.callback = callback
80+
81+
def readData(self,cls,inst,cmd,adr=None,timeout = 100000):
82+
"""Returns a value from the FFBoard.
83+
Returns int for single value replies or a tuple for cmd and addr replies"""
84+
reply = self.sendCommand(CMDTYPE_READ if adr is None else CMDTYPE_READADR,cls,inst,cmd,0,adr,timeout=timeout)
85+
if reply:
86+
if reply["cmdtype"] == CMDTYPE_READ:
87+
return reply["val"]
88+
elif reply["cmdtype"] == CMDTYPE_READADR:
89+
return reply["val"],reply["addr"]
90+
91+
def sendCommand(self,cmdtype,cls,inst,cmd,data=0,adr=0,timeout = 100000):
92+
self.device.set_nonblocking(False)
93+
buffer = self.make_command(cmdtype,cls,inst,cmd,data,adr)
94+
self.device.write(buffer) # Send raw packet
95+
96+
found = False
97+
while not found and timeout: # Receive all reports until correct one is found.
98+
timeout -= 1
99+
reply = self.device.read(25)
100+
if reply[0] == 0xA1:
101+
found = True
102+
repl = self.parse_command(reply)
103+
if (repl["cls"] == cls and repl["inst"] == inst and repl["cmd"] == cmd):
104+
found = True
105+
if found:
106+
if self.callback:
107+
self.callback(repl["cmdtype"],repl["cls"],repl["inst"],repl["cmd"],repl["val"],repl["addr"])
108+
if repl:
109+
return repl
52110

53-
def sendCommand(self,type,cls,inst,cmd,data=0,adr=0,wait=True):
54-
reports = self.device.find_output_reports(0xff00,0x01)
55-
if(reports):
56-
self.done = False
57-
report = reports[0]
58-
report[hid.get_full_usage_id(0xff00, 0x01)]=type # type. (0 = write, 1 = read)
59-
report[hid.get_full_usage_id(0xff00, 0x02)]=cls # cls (axis)
60-
report[hid.get_full_usage_id(0xff00, 0x03)]=inst # instance
61-
report[hid.get_full_usage_id(0xff00, 0x04)]=cmd # cmd (power)
62-
report[hid.get_full_usage_id(0xff00, 0x05)]=data # data
63-
report[hid.get_full_usage_id(0xff00, 0x06)]=adr if adr else 0 # adr
64-
report.send()
65-
while not self.done and wait:
66-
time.sleep(0.001)
67-

doc/python/openffboard_pywinusb.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import pywinusb.hid as hid
2+
import struct
3+
import time
4+
5+
CMDTYPE_WRITE=0x00
6+
CMDTYPE_READ= 0x01
7+
CMDTYPE_WRITEADR=0x03
8+
CMDTYPE_READADR= 0x04
9+
10+
# Pywinusb version
11+
12+
class OpenFFBoard():
13+
@staticmethod
14+
def findDevices(vid=0x1209,pid=0xFFB0):
15+
return hid.HidDeviceFilter(vendor_id = vid,product_id = pid).get_devices()
16+
17+
def __init__(self,device : hid.HidDevice):
18+
self.device = device
19+
self.callback = self.printData
20+
self.device.set_raw_data_handler(self.readDataCallback)
21+
22+
def printData(cmdtype,cls,instance,cmd,val,addr):
23+
print(f"Type: {cmdtype}, Class: {cls}.{instance}: cmd: {cmd}, val: {val}, addr: {addr}")
24+
25+
def open(self):
26+
self.device.open()
27+
28+
def close(self):
29+
self.device.close()
30+
31+
def registerReadCallback(self,callback):
32+
self.callback = callback
33+
34+
35+
# Callback in format callback(cmdtype,cls,instance,cmd,val,addr)
36+
def readDataCallback(self,data):
37+
if(data[0] == 0xA1):
38+
self.done = True
39+
cmdtype = int(data[1])
40+
cls = int(struct.unpack('<H', bytes(data[2:4]))[0])
41+
instance = int(data[4])
42+
cmd = int(struct.unpack('<L', bytes(data[5:9]))[0])
43+
val = int(struct.unpack('<Q', bytes(data[9:17]))[0])
44+
addr = int(struct.unpack('<Q', bytes(data[17:25]))[0])
45+
self.callback(cmdtype,cls,instance,cmd,val,addr)
46+
47+
def writeData(self,cls,inst,cmd,data,adr=None,wait=True):
48+
self.sendCommand(CMDTYPE_WRITE if adr == None else CMDTYPE_WRITEADR,cls=cls ,inst=inst,cmd=cmd,data=data,adr=adr,wait=wait)
49+
50+
def readData(self,cls,inst,cmd,adr=None,wait=True):
51+
self.sendCommand(CMDTYPE_READ if adr == None else CMDTYPE_READADR,cls,inst,cmd,0,adr,wait=wait)
52+
53+
def sendCommand(self,type,cls,inst,cmd,data=0,adr=0,wait=True):
54+
reports = self.device.find_output_reports(0xff00,0x01)
55+
if(reports):
56+
self.done = False
57+
report = reports[0]
58+
report[hid.get_full_usage_id(0xff00, 0x01)]=type # type. (0 = write, 1 = read)
59+
report[hid.get_full_usage_id(0xff00, 0x02)]=cls # cls (axis)
60+
report[hid.get_full_usage_id(0xff00, 0x03)]=inst # instance
61+
report[hid.get_full_usage_id(0xff00, 0x04)]=cmd # cmd (power)
62+
report[hid.get_full_usage_id(0xff00, 0x05)]=data # data
63+
report[hid.get_full_usage_id(0xff00, 0x06)]=adr if adr else 0 # adr
64+
report.send()
65+
while not self.done and wait:
66+
time.sleep(0.001)
67+

0 commit comments

Comments
 (0)