xref: /linux/tools/testing/selftests/hid/tests/test_sony.py (revision b80a75cf6999fb79971b41eaec7af2bb4b514714)
1#!/bin/env python3
2# SPDX-License-Identifier: GPL-2.0
3# -*- coding: utf-8 -*-
4#
5# Copyright (c) 2020 Benjamin Tissoires <benjamin.tissoires@gmail.com>
6# Copyright (c) 2020 Red Hat, Inc.
7#
8
9from .base import application_matches
10from .base import KernelModule
11from .test_gamepad import BaseTest
12from hidtools.device.sony_gamepad import (
13    PS3Controller,
14    PS4ControllerBluetooth,
15    PS4ControllerUSB,
16    PS5ControllerBluetooth,
17    PS5ControllerUSB,
18    PSTouchPoint,
19)
20from hidtools.util import BusType
21
22import libevdev
23import logging
24import pytest
25
26logger = logging.getLogger("hidtools.test.sony")
27
28PS3_MODULE = KernelModule("sony", "hid_sony")
29PS4_MODULE = KernelModule("playstation", "hid_playstation")
30PS5_MODULE = KernelModule("playstation", "hid_playstation")
31
32
33class SonyBaseTest:
34    class SonyTest(BaseTest.TestGamepad):
35        pass
36
37    class SonyPS4ControllerTest(SonyTest):
38        kernel_modules = [PS4_MODULE]
39
40        def test_accelerometer(self):
41            uhdev = self.uhdev
42            evdev = uhdev.get_evdev("Accelerometer")
43
44            for x in range(-32000, 32000, 4000):
45                r = uhdev.event(accel=(x, None, None))
46                events = uhdev.next_sync_events("Accelerometer")
47                self.debug_reports(r, uhdev, events)
48
49                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X) in events
50                value = evdev.value[libevdev.EV_ABS.ABS_X]
51                # Check against range due to small loss in precision due
52                # to inverse calibration, followed by calibration by hid-sony.
53                assert x - 1 <= value <= x + 1
54
55            for y in range(-32000, 32000, 4000):
56                r = uhdev.event(accel=(None, y, None))
57                events = uhdev.next_sync_events("Accelerometer")
58                self.debug_reports(r, uhdev, events)
59
60                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y) in events
61                value = evdev.value[libevdev.EV_ABS.ABS_Y]
62                assert y - 1 <= value <= y + 1
63
64            for z in range(-32000, 32000, 4000):
65                r = uhdev.event(accel=(None, None, z))
66                events = uhdev.next_sync_events("Accelerometer")
67                self.debug_reports(r, uhdev, events)
68
69                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Z) in events
70                value = evdev.value[libevdev.EV_ABS.ABS_Z]
71                assert z - 1 <= value <= z + 1
72
73        def test_gyroscope(self):
74            uhdev = self.uhdev
75            evdev = uhdev.get_evdev("Accelerometer")
76
77            for rx in range(-2000000, 2000000, 200000):
78                r = uhdev.event(gyro=(rx, None, None))
79                events = uhdev.next_sync_events("Accelerometer")
80                self.debug_reports(r, uhdev, events)
81
82                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RX) in events
83                value = evdev.value[libevdev.EV_ABS.ABS_RX]
84                # Sensor internal value is 16-bit, but calibrated is 22-bit, so
85                # 6-bit (64) difference, so allow a range of +/- 64.
86                assert rx - 64 <= value <= rx + 64
87
88            for ry in range(-2000000, 2000000, 200000):
89                r = uhdev.event(gyro=(None, ry, None))
90                events = uhdev.next_sync_events("Accelerometer")
91                self.debug_reports(r, uhdev, events)
92
93                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RY) in events
94                value = evdev.value[libevdev.EV_ABS.ABS_RY]
95                assert ry - 64 <= value <= ry + 64
96
97            for rz in range(-2000000, 2000000, 200000):
98                r = uhdev.event(gyro=(None, None, rz))
99                events = uhdev.next_sync_events("Accelerometer")
100                self.debug_reports(r, uhdev, events)
101
102                assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RZ) in events
103                value = evdev.value[libevdev.EV_ABS.ABS_RZ]
104                assert rz - 64 <= value <= rz + 64
105
106        def test_battery(self):
107            uhdev = self.uhdev
108
109            assert uhdev.power_supply_class is not None
110
111            # DS4 capacity levels are in increments of 10.
112            # Battery is never below 5%.
113            for i in range(5, 105, 10):
114                uhdev.battery.capacity = i
115                uhdev.event()
116                assert uhdev.power_supply_class.capacity == i
117
118            # Discharging tests only make sense for BlueTooth.
119            if uhdev.bus == BusType.BLUETOOTH:
120                uhdev.battery.cable_connected = False
121                uhdev.battery.capacity = 45
122                uhdev.event()
123                assert uhdev.power_supply_class.status == "Discharging"
124
125            uhdev.battery.cable_connected = True
126            uhdev.battery.capacity = 5
127            uhdev.event()
128            assert uhdev.power_supply_class.status == "Charging"
129
130            uhdev.battery.capacity = 100
131            uhdev.event()
132            assert uhdev.power_supply_class.status == "Charging"
133
134            uhdev.battery.full = True
135            uhdev.event()
136            assert uhdev.power_supply_class.status == "Full"
137
138        def test_mt_single_touch(self):
139            """send a single touch in the first slot of the device,
140            and release it."""
141            uhdev = self.uhdev
142            evdev = uhdev.get_evdev("Touch Pad")
143
144            t0 = PSTouchPoint(1, 50, 100)
145            r = uhdev.event(touch=[t0])
146            events = uhdev.next_sync_events("Touch Pad")
147            self.debug_reports(r, uhdev, events)
148
149            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
150            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
151            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
152            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
153
154            t0.tipswitch = False
155            r = uhdev.event(touch=[t0])
156            events = uhdev.next_sync_events("Touch Pad")
157            self.debug_reports(r, uhdev, events)
158            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
159            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
160
161        def test_mt_dual_touch(self):
162            """Send 2 touches in the first 2 slots.
163            Make sure the kernel sees this as a dual touch.
164            Release and check
165
166            Note: PTP will send here BTN_DOUBLETAP emulation"""
167            uhdev = self.uhdev
168            evdev = uhdev.get_evdev("Touch Pad")
169
170            t0 = PSTouchPoint(1, 50, 100)
171            t1 = PSTouchPoint(2, 150, 200)
172
173            r = uhdev.event(touch=[t0])
174            events = uhdev.next_sync_events("Touch Pad")
175            self.debug_reports(r, uhdev, events)
176
177            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
178            assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
179            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
180            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
181            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
182            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
183
184            r = uhdev.event(touch=[t0, t1])
185            events = uhdev.next_sync_events("Touch Pad")
186            self.debug_reports(r, uhdev, events)
187            assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH) not in events
188            assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
189            assert (
190                libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events
191            )
192            assert (
193                libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events
194            )
195            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
196            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
197            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
198            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
199            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
200            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
201
202            t0.tipswitch = False
203            r = uhdev.event(touch=[t0, t1])
204            events = uhdev.next_sync_events("Touch Pad")
205            self.debug_reports(r, uhdev, events)
206            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
207            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
208            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events
209            assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events
210
211            t1.tipswitch = False
212            r = uhdev.event(touch=[t1])
213
214            events = uhdev.next_sync_events("Touch Pad")
215            self.debug_reports(r, uhdev, events)
216            assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
217            assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
218
219
220class TestPS3Controller(SonyBaseTest.SonyTest):
221    kernel_modules = [PS3_MODULE]
222
223    def create_device(self):
224        controller = PS3Controller()
225        controller.application_matches = application_matches
226        return controller
227
228    @pytest.fixture(autouse=True)
229    def start_controller(self):
230        # emulate a 'PS' button press to tell the kernel we are ready to accept events
231        self.assert_button(17)
232
233        # drain any remaining udev events
234        while self.uhdev.dispatch(10):
235            pass
236
237        def test_led(self):
238            for k, v in self.uhdev.led_classes.items():
239                # the kernel might have set a LED for us
240                logger.info(f"{k}: {v.brightness}")
241
242                idx = int(k[-1]) - 1
243                assert self.uhdev.hw_leds.get_led(idx)[0] == bool(v.brightness)
244
245                v.brightness = 0
246                self.uhdev.dispatch(10)
247                assert self.uhdev.hw_leds.get_led(idx)[0] is False
248
249                v.brightness = v.max_brightness
250                self.uhdev.dispatch(10)
251                assert self.uhdev.hw_leds.get_led(idx)[0]
252
253
254class CalibratedPS4Controller(object):
255    # DS4 reports uncalibrated sensor data. Calibration coefficients
256    # can be retrieved using a feature report (0x2 USB / 0x5 BT).
257    # The values below are the processed calibration values for the
258    # DS4s matching the feature reports of PS4ControllerBluetooth/USB
259    # as dumped from hid-sony 'ds4_get_calibration_data'.
260    #
261    # Note we duplicate those values here in case the kernel changes them
262    # so we can have tests passing even if hid-tools doesn't have the
263    # correct values.
264    accelerometer_calibration_data = {
265        "x": {"bias": -73, "numer": 16384, "denom": 16472},
266        "y": {"bias": -352, "numer": 16384, "denom": 16344},
267        "z": {"bias": 81, "numer": 16384, "denom": 16319},
268    }
269    gyroscope_calibration_data = {
270        "x": {"bias": 0, "numer": 1105920, "denom": 17827},
271        "y": {"bias": 0, "numer": 1105920, "denom": 17777},
272        "z": {"bias": 0, "numer": 1105920, "denom": 17748},
273    }
274
275
276class CalibratedPS4ControllerBluetooth(CalibratedPS4Controller, PS4ControllerBluetooth):
277    pass
278
279
280class TestPS4ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
281    def create_device(self):
282        controller = CalibratedPS4ControllerBluetooth()
283        controller.application_matches = application_matches
284        return controller
285
286
287class CalibratedPS4ControllerUSB(CalibratedPS4Controller, PS4ControllerUSB):
288    pass
289
290
291class TestPS4ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
292    def create_device(self):
293        controller = CalibratedPS4ControllerUSB()
294        controller.application_matches = application_matches
295        return controller
296
297
298class CalibratedPS5Controller(object):
299    # DualSense reports uncalibrated sensor data. Calibration coefficients
300    # can be retrieved using feature report 0x09.
301    # The values below are the processed calibration values for the
302    # DualSene matching the feature reports of PS5ControllerBluetooth/USB
303    # as dumped from hid-playstation 'dualsense_get_calibration_data'.
304    #
305    # Note we duplicate those values here in case the kernel changes them
306    # so we can have tests passing even if hid-tools doesn't have the
307    # correct values.
308    accelerometer_calibration_data = {
309        "x": {"bias": 0, "numer": 16384, "denom": 16374},
310        "y": {"bias": -114, "numer": 16384, "denom": 16362},
311        "z": {"bias": 2, "numer": 16384, "denom": 16395},
312    }
313    gyroscope_calibration_data = {
314        "x": {"bias": 0, "numer": 1105920, "denom": 17727},
315        "y": {"bias": 0, "numer": 1105920, "denom": 17728},
316        "z": {"bias": 0, "numer": 1105920, "denom": 17769},
317    }
318
319
320class CalibratedPS5ControllerBluetooth(CalibratedPS5Controller, PS5ControllerBluetooth):
321    pass
322
323
324class TestPS5ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
325    kernel_modules = [PS5_MODULE]
326
327    def create_device(self):
328        controller = CalibratedPS5ControllerBluetooth()
329        controller.application_matches = application_matches
330        return controller
331
332
333class CalibratedPS5ControllerUSB(CalibratedPS5Controller, PS5ControllerUSB):
334    pass
335
336
337class TestPS5ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
338    kernel_modules = [PS5_MODULE]
339
340    def create_device(self):
341        controller = CalibratedPS5ControllerUSB()
342        controller.application_matches = application_matches
343        return controller
344