343 lines · python
1#!/bin/env python32# SPDX-License-Identifier: GPL-2.03# -*- 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_matches10from .test_gamepad import BaseTest11from hidtools.device.sony_gamepad import (12 PS3Controller,13 PS4ControllerBluetooth,14 PS4ControllerUSB,15 PS5ControllerBluetooth,16 PS5ControllerUSB,17 PSTouchPoint,18)19from hidtools.util import BusType20 21import libevdev22import logging23import pytest24 25logger = logging.getLogger("hidtools.test.sony")26 27PS3_MODULE = ("sony", "hid_sony")28PS4_MODULE = ("playstation", "hid_playstation")29PS5_MODULE = ("playstation", "hid_playstation")30 31 32class SonyBaseTest:33 class SonyTest(BaseTest.TestGamepad):34 pass35 36 class SonyPS4ControllerTest(SonyTest):37 kernel_modules = [PS4_MODULE]38 39 def test_accelerometer(self):40 uhdev = self.uhdev41 evdev = uhdev.get_evdev("Accelerometer")42 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)47 48 assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X) in events49 value = evdev.value[libevdev.EV_ABS.ABS_X]50 # Check against range due to small loss in precision due51 # to inverse calibration, followed by calibration by hid-sony.52 assert x - 1 <= value <= x + 153 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)58 59 assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y) in events60 value = evdev.value[libevdev.EV_ABS.ABS_Y]61 assert y - 1 <= value <= y + 162 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)67 68 assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Z) in events69 value = evdev.value[libevdev.EV_ABS.ABS_Z]70 assert z - 1 <= value <= z + 171 72 def test_gyroscope(self):73 uhdev = self.uhdev74 evdev = uhdev.get_evdev("Accelerometer")75 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)80 81 assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RX) in events82 value = evdev.value[libevdev.EV_ABS.ABS_RX]83 # Sensor internal value is 16-bit, but calibrated is 22-bit, so84 # 6-bit (64) difference, so allow a range of +/- 64.85 assert rx - 64 <= value <= rx + 6486 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)91 92 assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RY) in events93 value = evdev.value[libevdev.EV_ABS.ABS_RY]94 assert ry - 64 <= value <= ry + 6495 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)100 101 assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RZ) in events102 value = evdev.value[libevdev.EV_ABS.ABS_RZ]103 assert rz - 64 <= value <= rz + 64104 105 def test_battery(self):106 uhdev = self.uhdev107 108 assert uhdev.power_supply_class is not None109 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 = i114 uhdev.event()115 assert uhdev.power_supply_class.capacity == i116 117 # Discharging tests only make sense for BlueTooth.118 if uhdev.bus == BusType.BLUETOOTH:119 uhdev.battery.cable_connected = False120 uhdev.battery.capacity = 45121 uhdev.event()122 assert uhdev.power_supply_class.status == "Discharging"123 124 uhdev.battery.cable_connected = True125 uhdev.battery.capacity = 5126 uhdev.event()127 assert uhdev.power_supply_class.status == "Charging"128 129 uhdev.battery.capacity = 100130 uhdev.event()131 assert uhdev.power_supply_class.status == "Charging"132 133 uhdev.battery.full = True134 uhdev.event()135 assert uhdev.power_supply_class.status == "Full"136 137 def test_mt_single_touch(self):138 """send a single touch in the first slot of the device,139 and release it."""140 uhdev = self.uhdev141 evdev = uhdev.get_evdev("Touch Pad")142 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)147 148 assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events149 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0150 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50151 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100152 153 t0.tipswitch = False154 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 events158 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1159 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.163 Release and check164 165 Note: PTP will send here BTN_DOUBLETAP emulation"""166 uhdev = self.uhdev167 evdev = uhdev.get_evdev("Touch Pad")168 169 t0 = PSTouchPoint(1, 50, 100)170 t1 = PSTouchPoint(2, 150, 200)171 172 r = uhdev.event(touch=[t0])173 events = uhdev.next_sync_events("Touch Pad")174 self.debug_reports(r, uhdev, events)175 176 assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events177 assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1178 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0179 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50180 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100181 assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1182 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 events187 assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1188 assert (189 libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events190 )191 assert (192 libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events193 )194 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0195 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50196 assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100197 assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1198 assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150199 assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200200 201 t0.tipswitch = False202 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] == -1206 assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1207 assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events208 assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events209 210 t1.tipswitch = False211 r = uhdev.event(touch=[t1])212 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] == -1216 assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1217 218 219class TestPS3Controller(SonyBaseTest.SonyTest):220 kernel_modules = [PS3_MODULE]221 222 def create_device(self):223 controller = PS3Controller()224 controller.application_matches = application_matches225 return controller226 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 events230 self.assert_button(17)231 232 # drain any remaining udev events233 while self.uhdev.dispatch(10):234 pass235 236 def test_led(self):237 for k, v in self.uhdev.led_classes.items():238 # the kernel might have set a LED for us239 logger.info(f"{k}: {v.brightness}")240 241 idx = int(k[-1]) - 1242 assert self.uhdev.hw_leds.get_led(idx)[0] == bool(v.brightness)243 244 v.brightness = 0245 self.uhdev.dispatch(10)246 assert self.uhdev.hw_leds.get_led(idx)[0] is False247 248 v.brightness = v.max_brightness249 self.uhdev.dispatch(10)250 assert self.uhdev.hw_leds.get_led(idx)[0]251 252 253class CalibratedPS4Controller(object):254 # DS4 reports uncalibrated sensor data. Calibration coefficients255 # can be retrieved using a feature report (0x2 USB / 0x5 BT).256 # The values below are the processed calibration values for the257 # DS4s matching the feature reports of PS4ControllerBluetooth/USB258 # as dumped from hid-sony 'ds4_get_calibration_data'.259 #260 # Note we duplicate those values here in case the kernel changes them261 # so we can have tests passing even if hid-tools doesn't have the262 # correct values.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},267 }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},272 }273 274 275class CalibratedPS4ControllerBluetooth(CalibratedPS4Controller, PS4ControllerBluetooth):276 pass277 278 279class TestPS4ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):280 def create_device(self):281 controller = CalibratedPS4ControllerBluetooth()282 controller.application_matches = application_matches283 return controller284 285 286class CalibratedPS4ControllerUSB(CalibratedPS4Controller, PS4ControllerUSB):287 pass288 289 290class TestPS4ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):291 def create_device(self):292 controller = CalibratedPS4ControllerUSB()293 controller.application_matches = application_matches294 return controller295 296 297class CalibratedPS5Controller(object):298 # DualSense reports uncalibrated sensor data. Calibration coefficients299 # can be retrieved using feature report 0x09.300 # The values below are the processed calibration values for the301 # DualSene matching the feature reports of PS5ControllerBluetooth/USB302 # as dumped from hid-playstation 'dualsense_get_calibration_data'.303 #304 # Note we duplicate those values here in case the kernel changes them305 # so we can have tests passing even if hid-tools doesn't have the306 # correct values.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},311 }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},316 }317 318 319class CalibratedPS5ControllerBluetooth(CalibratedPS5Controller, PS5ControllerBluetooth):320 pass321 322 323class TestPS5ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):324 kernel_modules = [PS5_MODULE]325 326 def create_device(self):327 controller = CalibratedPS5ControllerBluetooth()328 controller.application_matches = application_matches329 return controller330 331 332class CalibratedPS5ControllerUSB(CalibratedPS5Controller, PS5ControllerUSB):333 pass334 335 336class TestPS5ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):337 kernel_modules = [PS5_MODULE]338 339 def create_device(self):340 controller = CalibratedPS5ControllerUSB()341 controller.application_matches = application_matches342 return controller343