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