test_sony.py 13 KB

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