test_sony.py 13 KB

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