g29py 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- g29py-0.0.1/PKG-INFO +53 -0
- g29py-0.0.1/README.md +37 -0
- g29py-0.0.1/g29py/__init__.py +3 -0
- g29py-0.0.1/g29py/g29.py +157 -0
- g29py-0.0.1/pyproject.toml +19 -0
- g29py-0.0.1/setup.py +30 -0
g29py-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: g29py
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: python driver for g29 wheel/pedals
|
|
5
|
+
Author: sean pollock
|
|
6
|
+
Author-email: seanap@protonmail.com
|
|
7
|
+
Requires-Python: >=3.8,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Requires-Dist: hid (==1.0.4)
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# g29py
|
|
17
|
+
> python driver for logitech g29 wheel/pedals
|
|
18
|
+
|
|
19
|
+
#### install
|
|
20
|
+
`$ pip install g29py`
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
from g29py import G29
|
|
24
|
+
g29 = G29()
|
|
25
|
+
g29.reset() # wheel cal
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
# write
|
|
30
|
+
g29.set_range(500)
|
|
31
|
+
g29.set_friction(0.5)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
#read
|
|
36
|
+
g29.start_pumping() # thread
|
|
37
|
+
while 1:
|
|
38
|
+
state = g29.get_state()
|
|
39
|
+
print("steering:", state["steering"])
|
|
40
|
+
print("brake:", state["brake"])
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### sources
|
|
44
|
+
|
|
45
|
+
- Write.md Read.md
|
|
46
|
+
- Commands based on nightmode's [logitech-g29](https://github.com/nightmode/logitech-g29) node.js driver.
|
|
47
|
+
- Interface uses libhidapi ctype bindings from apmorton's [pyhidapi](https://github.com/apmorton/pyhidapi).
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
### support
|
|
51
|
+
|
|
52
|
+
Only Logitech G29 Driving Force Racing Wheels & Pedals kit supported on linux in ps3 mode.
|
|
53
|
+
|
g29py-0.0.1/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# g29py
|
|
2
|
+
> python driver for logitech g29 wheel/pedals
|
|
3
|
+
|
|
4
|
+
#### install
|
|
5
|
+
`$ pip install g29py`
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
from g29py import G29
|
|
9
|
+
g29 = G29()
|
|
10
|
+
g29.reset() # wheel cal
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
# write
|
|
15
|
+
g29.set_range(500)
|
|
16
|
+
g29.set_friction(0.5)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
#read
|
|
21
|
+
g29.start_pumping() # thread
|
|
22
|
+
while 1:
|
|
23
|
+
state = g29.get_state()
|
|
24
|
+
print("steering:", state["steering"])
|
|
25
|
+
print("brake:", state["brake"])
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
#### sources
|
|
29
|
+
|
|
30
|
+
- Write.md Read.md
|
|
31
|
+
- Commands based on nightmode's [logitech-g29](https://github.com/nightmode/logitech-g29) node.js driver.
|
|
32
|
+
- Interface uses libhidapi ctype bindings from apmorton's [pyhidapi](https://github.com/apmorton/pyhidapi).
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
### support
|
|
36
|
+
|
|
37
|
+
Only Logitech G29 Driving Force Racing Wheels & Pedals kit supported on linux in ps3 mode.
|
g29py-0.0.1/g29py/g29.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import hid
|
|
2
|
+
import time
|
|
3
|
+
import threading
|
|
4
|
+
import logging as log
|
|
5
|
+
|
|
6
|
+
NAME = "Logitech G29 Driving Force Racing Wheel"
|
|
7
|
+
GUID = "030000006d0400004fc2000011010000"
|
|
8
|
+
VENDOR_ID = 1133
|
|
9
|
+
PRODUCT_ID = 49743
|
|
10
|
+
|
|
11
|
+
STEERING_COARSE_AXIS = 4
|
|
12
|
+
STEERING_FINE_AXIS = 5
|
|
13
|
+
ACCELERATOR_AXIS = 6
|
|
14
|
+
BRAKE_AXIS = 7
|
|
15
|
+
CLUTCH_AXIS = 8
|
|
16
|
+
|
|
17
|
+
class G29:
|
|
18
|
+
cache = None
|
|
19
|
+
state = {
|
|
20
|
+
"steering": int,
|
|
21
|
+
"accelerator": int,
|
|
22
|
+
"brake": int,
|
|
23
|
+
"clutch": int,
|
|
24
|
+
}
|
|
25
|
+
def __init__(self):
|
|
26
|
+
try:
|
|
27
|
+
device = hid.Device(VENDOR_ID, PRODUCT_ID)
|
|
28
|
+
except:
|
|
29
|
+
raise Exception("Device not found. Is it plugged in?")
|
|
30
|
+
log.debug(f'Device manufacturer: {device.manufacturer}')
|
|
31
|
+
log.debug(f'Product: {device.product}')
|
|
32
|
+
self.device = device
|
|
33
|
+
|
|
34
|
+
def connect(self):
|
|
35
|
+
self.pump() # load cache
|
|
36
|
+
self.reset()
|
|
37
|
+
|
|
38
|
+
def reset(self):
|
|
39
|
+
# wheel calibration
|
|
40
|
+
self.device.write(bytes([0xf8, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00]))
|
|
41
|
+
self.device.write(bytes([0xf8, 0x09, 0x05, 0x01, 0x01, 0x00, 0x00]))
|
|
42
|
+
time.sleep(10) # wait for calibration
|
|
43
|
+
|
|
44
|
+
# WRITE
|
|
45
|
+
|
|
46
|
+
def force_constant(self, val=0.5):
|
|
47
|
+
if val < 0 or val > 1:
|
|
48
|
+
raise ValueError("force_constant val must be between 0 and 1")
|
|
49
|
+
# normalze to 0-255
|
|
50
|
+
val = round(int(val * 255))
|
|
51
|
+
log.debug(f'force_constant: {val}')
|
|
52
|
+
msg = [0x11, 0x00, val, 0x00, 0x00, 0x00, 0x00]
|
|
53
|
+
self.device.write(bytes(msg))
|
|
54
|
+
|
|
55
|
+
def force_friction(self, val=0.5):
|
|
56
|
+
if val < 0 or val > 1:
|
|
57
|
+
raise ValueError("force_fricion val must be between 0 and 1")
|
|
58
|
+
# normalze to 0-8
|
|
59
|
+
val = round(int(val * 8))
|
|
60
|
+
log.debug(f'force_friction: {val}')
|
|
61
|
+
msg = [0x21, 0x02, val, 0x00, val, 0x00, 0x00]
|
|
62
|
+
self.device.write(bytes(msg))
|
|
63
|
+
|
|
64
|
+
def set_range(self, val=400):
|
|
65
|
+
if val < 400 or val > 900:
|
|
66
|
+
raise ValueError("set_range val must be between 400 and 900")
|
|
67
|
+
range1 = val & 0x00ff
|
|
68
|
+
range2 = (val & 0xff00) >> 8
|
|
69
|
+
log.debug(f'range: {range1},{range2}')
|
|
70
|
+
msg = [0xf8, 0x81, range1, range2, 0x00, 0x00, 0x00]
|
|
71
|
+
self.device.write(bytes(msg))
|
|
72
|
+
|
|
73
|
+
def set_autocenter(self, strength=0.5, rate=0.05):
|
|
74
|
+
if strength < 0 or strength > 1:
|
|
75
|
+
raise ValueError("force_constant val must be between 0 and 1")
|
|
76
|
+
if rate < 0 or rate > 1:
|
|
77
|
+
raise ValueError("force_constant val must be between 0 and 1")
|
|
78
|
+
# autocenter up
|
|
79
|
+
up_msg = [0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
|
80
|
+
self.device.write(bytes(up_msg))
|
|
81
|
+
# normalze strength to 0-15
|
|
82
|
+
strength = round(int(strength * 15))
|
|
83
|
+
# normalze rate to 0-255
|
|
84
|
+
rate = round(int(rate * 255))
|
|
85
|
+
log.debug(f'autocenter: {strength} {rate}')
|
|
86
|
+
msg = [0xfe, 0x0d, strength, strength, rate, 0x00, 0x00, 0x00]
|
|
87
|
+
self.device.write(bytes(msg))
|
|
88
|
+
|
|
89
|
+
def autocenter_off(self):
|
|
90
|
+
msg = [0xf5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
|
91
|
+
self.device.write(bytes(msg))
|
|
92
|
+
|
|
93
|
+
# slot 0-4, or 0xf3 for all
|
|
94
|
+
def force_off(self, slot=0xf3):
|
|
95
|
+
if slot < 0 or slot > 4 and slot !=0xf3:
|
|
96
|
+
raise ValueError("force_off slot must be between 0 and 4 or 0xf3")
|
|
97
|
+
log.debug(f'force_off: {slot}')
|
|
98
|
+
msg = [slot, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
|
|
99
|
+
self.device.write(bytes(msg))
|
|
100
|
+
|
|
101
|
+
# READ
|
|
102
|
+
|
|
103
|
+
def pump(self, timeout=10):
|
|
104
|
+
dat = self.device.read(16, timeout)
|
|
105
|
+
|
|
106
|
+
# only handle 12 byte msgs
|
|
107
|
+
byte_array = bytearray(dat)
|
|
108
|
+
if len(byte_array) >= 12:
|
|
109
|
+
self.update_state(byte_array)
|
|
110
|
+
self.cache = byte_array
|
|
111
|
+
|
|
112
|
+
return dat
|
|
113
|
+
|
|
114
|
+
def start_pumping(self, timeout=10):
|
|
115
|
+
self.pump_thread = threading.Thread(target=self.pump_forever, args=(timeout,))
|
|
116
|
+
self.pump_thread.start()
|
|
117
|
+
|
|
118
|
+
def pump_forever(self, timeout=10):
|
|
119
|
+
while 1:
|
|
120
|
+
self.pump(timeout)
|
|
121
|
+
|
|
122
|
+
def stop_pumping(self):
|
|
123
|
+
if self.thread is not None:
|
|
124
|
+
self.pump_thread.join()
|
|
125
|
+
|
|
126
|
+
def get_state(self):
|
|
127
|
+
return self.state
|
|
128
|
+
|
|
129
|
+
def update_state(self, byte_array):
|
|
130
|
+
if self.cache is None:
|
|
131
|
+
log.warn("cache not available")
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
# update only diffs
|
|
135
|
+
# steering
|
|
136
|
+
if byte_array[4] != self.cache[4] or byte_array[5] != self.cache[5]:
|
|
137
|
+
steering_val = self.calc_steering(byte_array[5], byte_array[4])
|
|
138
|
+
self.state["steering"] = steering_val
|
|
139
|
+
# accelerator
|
|
140
|
+
if byte_array[6] != self.cache[6]:
|
|
141
|
+
self.state["accelerator"] = byte_array[6]
|
|
142
|
+
# brake
|
|
143
|
+
if byte_array[7] != self.cache[7]:
|
|
144
|
+
self.state["brake"] = byte_array[7]
|
|
145
|
+
# clutch
|
|
146
|
+
if byte_array[8] != self.cache[8]:
|
|
147
|
+
self.state["clutch"] = byte_array[8]
|
|
148
|
+
|
|
149
|
+
def calc_steering(self, coarse, fine):
|
|
150
|
+
# coarse 0-255
|
|
151
|
+
# fine 0-255
|
|
152
|
+
# normalize to 0-100
|
|
153
|
+
coarse = (coarse/256) * (100-(100/256))
|
|
154
|
+
# normalize to 0-3
|
|
155
|
+
fine = (fine/256) * (100/256)
|
|
156
|
+
# add together
|
|
157
|
+
return round(coarse + fine)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "g29py"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "python driver for g29 wheel/pedals"
|
|
5
|
+
authors = ["sean pollock <seanap@protonmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
packages = [{include = "g29py"}]
|
|
8
|
+
|
|
9
|
+
[tool.poetry.dependencies]
|
|
10
|
+
python = "^3.8"
|
|
11
|
+
hid = "1.0.4"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
[tool.poetry.group.dev.dependencies]
|
|
15
|
+
pytest = "^7.3.2"
|
|
16
|
+
|
|
17
|
+
[build-system]
|
|
18
|
+
requires = ["poetry-core", "libhidapi-hidraw0"]
|
|
19
|
+
build-backend = "poetry.core.masonry.api"
|
g29py-0.0.1/setup.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
from setuptools import setup
|
|
3
|
+
|
|
4
|
+
packages = \
|
|
5
|
+
['g29py']
|
|
6
|
+
|
|
7
|
+
package_data = \
|
|
8
|
+
{'': ['*']}
|
|
9
|
+
|
|
10
|
+
install_requires = \
|
|
11
|
+
['hid==1.0.4']
|
|
12
|
+
|
|
13
|
+
setup_kwargs = {
|
|
14
|
+
'name': 'g29py',
|
|
15
|
+
'version': '0.0.1',
|
|
16
|
+
'description': 'python driver for g29 wheel/pedals',
|
|
17
|
+
'long_description': '# g29py\n> python driver for logitech g29 wheel/pedals\n\n#### install\n`$ pip install g29py`\n\n```\nfrom g29py import G29\ng29 = G29()\ng29.reset() # wheel cal\n```\n\n```\n# write \ng29.set_range(500)\ng29.set_friction(0.5)\n```\n\n```\n#read\ng29.start_pumping() # thread\nwhile 1:\n state = g29.get_state()\n print("steering:", state["steering"])\n print("brake:", state["brake"])\n```\n\n#### sources\n\n- Write.md Read.md \n- Commands based on nightmode\'s [logitech-g29](https://github.com/nightmode/logitech-g29) node.js driver.\n- Interface uses libhidapi ctype bindings from apmorton\'s [pyhidapi](https://github.com/apmorton/pyhidapi).\n\n\n### support\n\nOnly Logitech G29 Driving Force Racing Wheels & Pedals kit supported on linux in ps3 mode.\n',
|
|
18
|
+
'author': 'sean pollock',
|
|
19
|
+
'author_email': 'seanap@protonmail.com',
|
|
20
|
+
'maintainer': 'None',
|
|
21
|
+
'maintainer_email': 'None',
|
|
22
|
+
'url': 'None',
|
|
23
|
+
'packages': packages,
|
|
24
|
+
'package_data': package_data,
|
|
25
|
+
'install_requires': install_requires,
|
|
26
|
+
'python_requires': '>=3.8,<4.0',
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
setup(**setup_kwargs)
|