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 .test_gamepad import BaseTest 11from hidtools.device.sony_gamepad import ( 12 PS3Controller, 13 PS4ControllerBluetooth, 14 PS4ControllerUSB, 15 PS5ControllerBluetooth, 16 PS5ControllerUSB, 17 PSTouchPoint, 18) 19from hidtools.util import BusType 20 21import libevdev 22import logging 23import pytest 24 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 pass 35 36 class SonyPS4ControllerTest(SonyTest): 37 kernel_modules = [PS4_MODULE] 38 39 def test_accelerometer(self): 40 uhdev = self.uhdev 41 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 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 53 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 events 60 value = evdev.value[libevdev.EV_ABS.ABS_Y] 61 assert y - 1 <= value <= y + 1 62 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 events 69 value = evdev.value[libevdev.EV_ABS.ABS_Z] 70 assert z - 1 <= value <= z + 1 71 72 def test_gyroscope(self): 73 uhdev = self.uhdev 74 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 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 86 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 events 93 value = evdev.value[libevdev.EV_ABS.ABS_RY] 94 assert ry - 64 <= value <= ry + 64 95 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 events 102 value = evdev.value[libevdev.EV_ABS.ABS_RZ] 103 assert rz - 64 <= value <= rz + 64 104 105 def test_battery(self): 106 uhdev = self.uhdev 107 108 assert uhdev.power_supply_class is not None 109 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 114 uhdev.event() 115 assert uhdev.power_supply_class.capacity == i 116 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 121 uhdev.event() 122 assert uhdev.power_supply_class.status == "Discharging" 123 124 uhdev.battery.cable_connected = True 125 uhdev.battery.capacity = 5 126 uhdev.event() 127 assert uhdev.power_supply_class.status == "Charging" 128 129 uhdev.battery.capacity = 100 130 uhdev.event() 131 assert uhdev.power_supply_class.status == "Charging" 132 133 uhdev.battery.full = True 134 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.uhdev 141 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 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 152 153 t0.tipswitch = False 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 159 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 check 164 165 Note: PTP will send here BTN_DOUBLETAP emulation""" 166 uhdev = self.uhdev 167 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 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 182 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 188 assert ( 189 libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events 190 ) 191 assert ( 192 libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events 193 ) 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 200 201 t0.tipswitch = False 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 209 210 t1.tipswitch = False 211 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] == -1 216 assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1 217 218 219class TestPS3Controller(SonyBaseTest.SonyTest): 220 kernel_modules = [PS3_MODULE] 221 222 def create_device(self): 223 controller = PS3Controller() 224 controller.application_matches = application_matches 225 return controller 226 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) 231 232 # drain any remaining udev events 233 while self.uhdev.dispatch(10): 234 pass 235 236 def test_led(self): 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}") 240 241 idx = int(k[-1]) - 1 242 assert self.uhdev.hw_leds.get_led(idx)[0] == bool(v.brightness) 243 244 v.brightness = 0 245 self.uhdev.dispatch(10) 246 assert self.uhdev.hw_leds.get_led(idx)[0] is False 247 248 v.brightness = v.max_brightness 249 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 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'. 259 # 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 262 # 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 pass 277 278 279class TestPS4ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest): 280 def create_device(self): 281 controller = CalibratedPS4ControllerBluetooth() 282 controller.application_matches = application_matches 283 return controller 284 285 286class CalibratedPS4ControllerUSB(CalibratedPS4Controller, PS4ControllerUSB): 287 pass 288 289 290class TestPS4ControllerUSB(SonyBaseTest.SonyPS4ControllerTest): 291 def create_device(self): 292 controller = CalibratedPS4ControllerUSB() 293 controller.application_matches = application_matches 294 return controller 295 296 297class 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'. 303 # 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 306 # 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 pass 321 322 323class TestPS5ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest): 324 kernel_modules = [PS5_MODULE] 325 326 def create_device(self): 327 controller = CalibratedPS5ControllerBluetooth() 328 controller.application_matches = application_matches 329 return controller 330 331 332class CalibratedPS5ControllerUSB(CalibratedPS5Controller, PS5ControllerUSB): 333 pass 334 335 336class TestPS5ControllerUSB(SonyBaseTest.SonyPS4ControllerTest): 337 kernel_modules = [PS5_MODULE] 338 339 def create_device(self): 340 controller = CalibratedPS5ControllerUSB() 341 controller.application_matches = application_matches 342 return controller 343