2 # SPDX-License-Identifier: GPL-2.0
3 # -*- coding: utf-8 -*-
5 # Copyright (c) 2020 Benjamin Tissoires <benjamin.tissoires@gmail.com>
6 # Copyright (c) 2020 Red Hat, Inc.
9 from .base
import application_matches
10 from .test_gamepad
import BaseTest
11 from hidtools
.device
.sony_gamepad
import (
13 PS4ControllerBluetooth
,
15 PS5ControllerBluetooth
,
19 from hidtools
.util
import BusType
25 logger
= logging
.getLogger("hidtools.test.sony")
27 PS3_MODULE
= ("sony", "hid_sony")
28 PS4_MODULE
= ("playstation", "hid_playstation")
29 PS5_MODULE
= ("playstation", "hid_playstation")
33 class SonyTest(BaseTest
.TestGamepad
):
36 class SonyPS4ControllerTest(SonyTest
):
37 kernel_modules
= [PS4_MODULE
]
39 def test_accelerometer(self
):
41 evdev
= uhdev
.get_evdev("Accelerometer")
43 for x
in range(-32000, 32000, 4000):
44 r
= uhdev
.event(accel
=(x
, None, None))
45 events
= uhdev
.next_sync_events("Accelerometer")
46 self
.debug_reports(r
, uhdev
, events
)
48 assert libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_X
) in events
49 value
= evdev
.value
[libevdev
.EV_ABS
.ABS_X
]
50 # Check against range due to small loss in precision due
51 # to inverse calibration, followed by calibration by hid-sony.
52 assert x
- 1 <= value
<= x
+ 1
54 for y
in range(-32000, 32000, 4000):
55 r
= uhdev
.event(accel
=(None, y
, None))
56 events
= uhdev
.next_sync_events("Accelerometer")
57 self
.debug_reports(r
, uhdev
, events
)
59 assert libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_Y
) in events
60 value
= evdev
.value
[libevdev
.EV_ABS
.ABS_Y
]
61 assert y
- 1 <= value
<= y
+ 1
63 for z
in range(-32000, 32000, 4000):
64 r
= uhdev
.event(accel
=(None, None, z
))
65 events
= uhdev
.next_sync_events("Accelerometer")
66 self
.debug_reports(r
, uhdev
, events
)
68 assert libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_Z
) in events
69 value
= evdev
.value
[libevdev
.EV_ABS
.ABS_Z
]
70 assert z
- 1 <= value
<= z
+ 1
72 def test_gyroscope(self
):
74 evdev
= uhdev
.get_evdev("Accelerometer")
76 for rx
in range(-2000000, 2000000, 200000):
77 r
= uhdev
.event(gyro
=(rx
, None, None))
78 events
= uhdev
.next_sync_events("Accelerometer")
79 self
.debug_reports(r
, uhdev
, events
)
81 assert libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_RX
) in events
82 value
= evdev
.value
[libevdev
.EV_ABS
.ABS_RX
]
83 # Sensor internal value is 16-bit, but calibrated is 22-bit, so
84 # 6-bit (64) difference, so allow a range of +/- 64.
85 assert rx
- 64 <= value
<= rx
+ 64
87 for ry
in range(-2000000, 2000000, 200000):
88 r
= uhdev
.event(gyro
=(None, ry
, None))
89 events
= uhdev
.next_sync_events("Accelerometer")
90 self
.debug_reports(r
, uhdev
, events
)
92 assert libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_RY
) in events
93 value
= evdev
.value
[libevdev
.EV_ABS
.ABS_RY
]
94 assert ry
- 64 <= value
<= ry
+ 64
96 for rz
in range(-2000000, 2000000, 200000):
97 r
= uhdev
.event(gyro
=(None, None, rz
))
98 events
= uhdev
.next_sync_events("Accelerometer")
99 self
.debug_reports(r
, uhdev
, events
)
101 assert libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_RZ
) in events
102 value
= evdev
.value
[libevdev
.EV_ABS
.ABS_RZ
]
103 assert rz
- 64 <= value
<= rz
+ 64
105 def test_battery(self
):
108 assert uhdev
.power_supply_class
is not None
110 # DS4 capacity levels are in increments of 10.
111 # Battery is never below 5%.
112 for i
in range(5, 105, 10):
113 uhdev
.battery
.capacity
= i
115 assert uhdev
.power_supply_class
.capacity
== i
117 # Discharging tests only make sense for BlueTooth.
118 if uhdev
.bus
== BusType
.BLUETOOTH
:
119 uhdev
.battery
.cable_connected
= False
120 uhdev
.battery
.capacity
= 45
122 assert uhdev
.power_supply_class
.status
== "Discharging"
124 uhdev
.battery
.cable_connected
= True
125 uhdev
.battery
.capacity
= 5
127 assert uhdev
.power_supply_class
.status
== "Charging"
129 uhdev
.battery
.capacity
= 100
131 assert uhdev
.power_supply_class
.status
== "Charging"
133 uhdev
.battery
.full
= True
135 assert uhdev
.power_supply_class
.status
== "Full"
137 def test_mt_single_touch(self
):
138 """send a single touch in the first slot of the device,
141 evdev
= uhdev
.get_evdev("Touch Pad")
143 t0
= PSTouchPoint(1, 50, 100)
144 r
= uhdev
.event(touch
=[t0
])
145 events
= uhdev
.next_sync_events("Touch Pad")
146 self
.debug_reports(r
, uhdev
, events
)
148 assert libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
, 1) in events
149 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == 0
150 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_POSITION_X
] == 50
151 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_POSITION_Y
] == 100
154 r
= uhdev
.event(touch
=[t0
])
155 events
= uhdev
.next_sync_events("Touch Pad")
156 self
.debug_reports(r
, uhdev
, events
)
157 assert libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
, 0) in events
158 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == -1
160 def test_mt_dual_touch(self
):
161 """Send 2 touches in the first 2 slots.
162 Make sure the kernel sees this as a dual touch.
165 Note: PTP will send here BTN_DOUBLETAP emulation"""
167 evdev
= uhdev
.get_evdev("Touch Pad")
169 t0
= PSTouchPoint(1, 50, 100)
170 t1
= PSTouchPoint(2, 150, 200)
172 r
= uhdev
.event(touch
=[t0
])
173 events
= uhdev
.next_sync_events("Touch Pad")
174 self
.debug_reports(r
, uhdev
, events
)
176 assert libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
, 1) in events
177 assert evdev
.value
[libevdev
.EV_KEY
.BTN_TOUCH
] == 1
178 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == 0
179 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_POSITION_X
] == 50
180 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_POSITION_Y
] == 100
181 assert evdev
.slots
[1][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == -1
183 r
= uhdev
.event(touch
=[t0
, t1
])
184 events
= uhdev
.next_sync_events("Touch Pad")
185 self
.debug_reports(r
, uhdev
, events
)
186 assert libevdev
.InputEvent(libevdev
.EV_KEY
.BTN_TOUCH
) not in events
187 assert evdev
.value
[libevdev
.EV_KEY
.BTN_TOUCH
] == 1
189 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_MT_POSITION_X
, 5) not in events
192 libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_MT_POSITION_Y
, 10) not in events
194 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == 0
195 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_POSITION_X
] == 50
196 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_POSITION_Y
] == 100
197 assert evdev
.slots
[1][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == 1
198 assert evdev
.slots
[1][libevdev
.EV_ABS
.ABS_MT_POSITION_X
] == 150
199 assert evdev
.slots
[1][libevdev
.EV_ABS
.ABS_MT_POSITION_Y
] == 200
202 r
= uhdev
.event(touch
=[t0
, t1
])
203 events
= uhdev
.next_sync_events("Touch Pad")
204 self
.debug_reports(r
, uhdev
, events
)
205 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == -1
206 assert evdev
.slots
[1][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == 1
207 assert libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_MT_POSITION_X
) not in events
208 assert libevdev
.InputEvent(libevdev
.EV_ABS
.ABS_MT_POSITION_Y
) not in events
211 r
= uhdev
.event(touch
=[t1
])
213 events
= uhdev
.next_sync_events("Touch Pad")
214 self
.debug_reports(r
, uhdev
, events
)
215 assert evdev
.slots
[0][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == -1
216 assert evdev
.slots
[1][libevdev
.EV_ABS
.ABS_MT_TRACKING_ID
] == -1
219 class TestPS3Controller(SonyBaseTest
.SonyTest
):
220 kernel_modules
= [PS3_MODULE
]
222 def create_device(self
):
223 controller
= PS3Controller()
224 controller
.application_matches
= application_matches
227 @pytest.fixture(autouse
=True)
228 def start_controller(self
):
229 # emulate a 'PS' button press to tell the kernel we are ready to accept events
230 self
.assert_button(17)
232 # drain any remaining udev events
233 while self
.uhdev
.dispatch(10):
237 for k
, v
in self
.uhdev
.led_classes
.items():
238 # the kernel might have set a LED for us
239 logger
.info(f
"{k}: {v.brightness}")
242 assert self
.uhdev
.hw_leds
.get_led(idx
)[0] == bool(v
.brightness
)
245 self
.uhdev
.dispatch(10)
246 assert self
.uhdev
.hw_leds
.get_led(idx
)[0] is False
248 v
.brightness
= v
.max_brightness
249 self
.uhdev
.dispatch(10)
250 assert self
.uhdev
.hw_leds
.get_led(idx
)[0]
253 class CalibratedPS4Controller(object):
254 # DS4 reports uncalibrated sensor data. Calibration coefficients
255 # can be retrieved using a feature report (0x2 USB / 0x5 BT).
256 # The values below are the processed calibration values for the
257 # DS4s matching the feature reports of PS4ControllerBluetooth/USB
258 # as dumped from hid-sony 'ds4_get_calibration_data'.
260 # Note we duplicate those values here in case the kernel changes them
261 # so we can have tests passing even if hid-tools doesn't have the
263 accelerometer_calibration_data
= {
264 "x": {"bias": -73, "numer": 16384, "denom": 16472},
265 "y": {"bias": -352, "numer": 16384, "denom": 16344},
266 "z": {"bias": 81, "numer": 16384, "denom": 16319},
268 gyroscope_calibration_data
= {
269 "x": {"bias": 0, "numer": 1105920, "denom": 17827},
270 "y": {"bias": 0, "numer": 1105920, "denom": 17777},
271 "z": {"bias": 0, "numer": 1105920, "denom": 17748},
275 class CalibratedPS4ControllerBluetooth(CalibratedPS4Controller
, PS4ControllerBluetooth
):
279 class TestPS4ControllerBluetooth(SonyBaseTest
.SonyPS4ControllerTest
):
280 def create_device(self
):
281 controller
= CalibratedPS4ControllerBluetooth()
282 controller
.application_matches
= application_matches
286 class CalibratedPS4ControllerUSB(CalibratedPS4Controller
, PS4ControllerUSB
):
290 class TestPS4ControllerUSB(SonyBaseTest
.SonyPS4ControllerTest
):
291 def create_device(self
):
292 controller
= CalibratedPS4ControllerUSB()
293 controller
.application_matches
= application_matches
297 class CalibratedPS5Controller(object):
298 # DualSense reports uncalibrated sensor data. Calibration coefficients
299 # can be retrieved using feature report 0x09.
300 # The values below are the processed calibration values for the
301 # DualSene matching the feature reports of PS5ControllerBluetooth/USB
302 # as dumped from hid-playstation 'dualsense_get_calibration_data'.
304 # Note we duplicate those values here in case the kernel changes them
305 # so we can have tests passing even if hid-tools doesn't have the
307 accelerometer_calibration_data
= {
308 "x": {"bias": 0, "numer": 16384, "denom": 16374},
309 "y": {"bias": -114, "numer": 16384, "denom": 16362},
310 "z": {"bias": 2, "numer": 16384, "denom": 16395},
312 gyroscope_calibration_data
= {
313 "x": {"bias": 0, "numer": 1105920, "denom": 17727},
314 "y": {"bias": 0, "numer": 1105920, "denom": 17728},
315 "z": {"bias": 0, "numer": 1105920, "denom": 17769},
319 class CalibratedPS5ControllerBluetooth(CalibratedPS5Controller
, PS5ControllerBluetooth
):
323 class TestPS5ControllerBluetooth(SonyBaseTest
.SonyPS4ControllerTest
):
324 kernel_modules
= [PS5_MODULE
]
326 def create_device(self
):
327 controller
= CalibratedPS5ControllerBluetooth()
328 controller
.application_matches
= application_matches
332 class CalibratedPS5ControllerUSB(CalibratedPS5Controller
, PS5ControllerUSB
):
336 class TestPS5ControllerUSB(SonyBaseTest
.SonyPS4ControllerTest
):
337 kernel_modules
= [PS5_MODULE
]
339 def create_device(self
):
340 controller
= CalibratedPS5ControllerUSB()
341 controller
.application_matches
= application_matches