File Index
Files and directories
Sign in
FILE

test_sony.py

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/hid/tests/test_sony.py PYTHON
  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
~/ Read-only file preview.