| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343 |
- #!/bin/env python3
- # SPDX-License-Identifier: GPL-2.0
- # -*- coding: utf-8 -*-
- #
- # Copyright (c) 2020 Benjamin Tissoires <benjamin.tissoires@gmail.com>
- # Copyright (c) 2020 Red Hat, Inc.
- #
- from .base import application_matches
- from .base import KernelModule
- from .test_gamepad import BaseTest
- from hidtools.device.sony_gamepad import (
- PS3Controller,
- PS4ControllerBluetooth,
- PS4ControllerUSB,
- PS5ControllerBluetooth,
- PS5ControllerUSB,
- PSTouchPoint,
- )
- from hidtools.util import BusType
- import libevdev
- import logging
- import pytest
- logger = logging.getLogger("hidtools.test.sony")
- PS3_MODULE = KernelModule("sony", "hid_sony")
- PS4_MODULE = KernelModule("playstation", "hid_playstation")
- PS5_MODULE = KernelModule("playstation", "hid_playstation")
- class SonyBaseTest:
- class SonyTest(BaseTest.TestGamepad):
- pass
- class SonyPS4ControllerTest(SonyTest):
- kernel_modules = [PS4_MODULE]
- def test_accelerometer(self):
- uhdev = self.uhdev
- evdev = uhdev.get_evdev("Accelerometer")
- for x in range(-32000, 32000, 4000):
- r = uhdev.event(accel=(x, None, None))
- events = uhdev.next_sync_events("Accelerometer")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_ABS.ABS_X) in events
- value = evdev.value[libevdev.EV_ABS.ABS_X]
- # Check against range due to small loss in precision due
- # to inverse calibration, followed by calibration by hid-sony.
- assert x - 1 <= value <= x + 1
- for y in range(-32000, 32000, 4000):
- r = uhdev.event(accel=(None, y, None))
- events = uhdev.next_sync_events("Accelerometer")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Y) in events
- value = evdev.value[libevdev.EV_ABS.ABS_Y]
- assert y - 1 <= value <= y + 1
- for z in range(-32000, 32000, 4000):
- r = uhdev.event(accel=(None, None, z))
- events = uhdev.next_sync_events("Accelerometer")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_ABS.ABS_Z) in events
- value = evdev.value[libevdev.EV_ABS.ABS_Z]
- assert z - 1 <= value <= z + 1
- def test_gyroscope(self):
- uhdev = self.uhdev
- evdev = uhdev.get_evdev("Accelerometer")
- for rx in range(-2000000, 2000000, 200000):
- r = uhdev.event(gyro=(rx, None, None))
- events = uhdev.next_sync_events("Accelerometer")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RX) in events
- value = evdev.value[libevdev.EV_ABS.ABS_RX]
- # Sensor internal value is 16-bit, but calibrated is 22-bit, so
- # 6-bit (64) difference, so allow a range of +/- 64.
- assert rx - 64 <= value <= rx + 64
- for ry in range(-2000000, 2000000, 200000):
- r = uhdev.event(gyro=(None, ry, None))
- events = uhdev.next_sync_events("Accelerometer")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RY) in events
- value = evdev.value[libevdev.EV_ABS.ABS_RY]
- assert ry - 64 <= value <= ry + 64
- for rz in range(-2000000, 2000000, 200000):
- r = uhdev.event(gyro=(None, None, rz))
- events = uhdev.next_sync_events("Accelerometer")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_ABS.ABS_RZ) in events
- value = evdev.value[libevdev.EV_ABS.ABS_RZ]
- assert rz - 64 <= value <= rz + 64
- def test_battery(self):
- uhdev = self.uhdev
- assert uhdev.power_supply_class is not None
- # DS4 capacity levels are in increments of 10.
- # Battery is never below 5%.
- for i in range(5, 105, 10):
- uhdev.battery.capacity = i
- uhdev.event()
- assert uhdev.power_supply_class.capacity == i
- # Discharging tests only make sense for BlueTooth.
- if uhdev.bus == BusType.BLUETOOTH:
- uhdev.battery.cable_connected = False
- uhdev.battery.capacity = 45
- uhdev.event()
- assert uhdev.power_supply_class.status == "Discharging"
- uhdev.battery.cable_connected = True
- uhdev.battery.capacity = 5
- uhdev.event()
- assert uhdev.power_supply_class.status == "Charging"
- uhdev.battery.capacity = 100
- uhdev.event()
- assert uhdev.power_supply_class.status == "Charging"
- uhdev.battery.full = True
- uhdev.event()
- assert uhdev.power_supply_class.status == "Full"
- def test_mt_single_touch(self):
- """send a single touch in the first slot of the device,
- and release it."""
- uhdev = self.uhdev
- evdev = uhdev.get_evdev("Touch Pad")
- t0 = PSTouchPoint(1, 50, 100)
- r = uhdev.event(touch=[t0])
- events = uhdev.next_sync_events("Touch Pad")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
- t0.tipswitch = False
- r = uhdev.event(touch=[t0])
- events = uhdev.next_sync_events("Touch Pad")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 0) in events
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
- def test_mt_dual_touch(self):
- """Send 2 touches in the first 2 slots.
- Make sure the kernel sees this as a dual touch.
- Release and check
- Note: PTP will send here BTN_DOUBLETAP emulation"""
- uhdev = self.uhdev
- evdev = uhdev.get_evdev("Touch Pad")
- t0 = PSTouchPoint(1, 50, 100)
- t1 = PSTouchPoint(2, 150, 200)
- r = uhdev.event(touch=[t0])
- events = uhdev.next_sync_events("Touch Pad")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH, 1) in events
- assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
- assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
- r = uhdev.event(touch=[t0, t1])
- events = uhdev.next_sync_events("Touch Pad")
- self.debug_reports(r, uhdev, events)
- assert libevdev.InputEvent(libevdev.EV_KEY.BTN_TOUCH) not in events
- assert evdev.value[libevdev.EV_KEY.BTN_TOUCH] == 1
- assert (
- libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X, 5) not in events
- )
- assert (
- libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y, 10) not in events
- )
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 0
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_X] == 50
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 100
- assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
- assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_X] == 150
- assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_POSITION_Y] == 200
- t0.tipswitch = False
- r = uhdev.event(touch=[t0, t1])
- events = uhdev.next_sync_events("Touch Pad")
- self.debug_reports(r, uhdev, events)
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
- assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == 1
- assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_X) not in events
- assert libevdev.InputEvent(libevdev.EV_ABS.ABS_MT_POSITION_Y) not in events
- t1.tipswitch = False
- r = uhdev.event(touch=[t1])
- events = uhdev.next_sync_events("Touch Pad")
- self.debug_reports(r, uhdev, events)
- assert evdev.slots[0][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
- assert evdev.slots[1][libevdev.EV_ABS.ABS_MT_TRACKING_ID] == -1
- class TestPS3Controller(SonyBaseTest.SonyTest):
- kernel_modules = [PS3_MODULE]
- def create_device(self):
- controller = PS3Controller()
- controller.application_matches = application_matches
- return controller
- @pytest.fixture(autouse=True)
- def start_controller(self):
- # emulate a 'PS' button press to tell the kernel we are ready to accept events
- self.assert_button(17)
- # drain any remaining udev events
- while self.uhdev.dispatch(10):
- pass
- def test_led(self):
- for k, v in self.uhdev.led_classes.items():
- # the kernel might have set a LED for us
- logger.info(f"{k}: {v.brightness}")
- idx = int(k[-1]) - 1
- assert self.uhdev.hw_leds.get_led(idx)[0] == bool(v.brightness)
- v.brightness = 0
- self.uhdev.dispatch(10)
- assert self.uhdev.hw_leds.get_led(idx)[0] is False
- v.brightness = v.max_brightness
- self.uhdev.dispatch(10)
- assert self.uhdev.hw_leds.get_led(idx)[0]
- class CalibratedPS4Controller(object):
- # DS4 reports uncalibrated sensor data. Calibration coefficients
- # can be retrieved using a feature report (0x2 USB / 0x5 BT).
- # The values below are the processed calibration values for the
- # DS4s matching the feature reports of PS4ControllerBluetooth/USB
- # as dumped from hid-sony 'ds4_get_calibration_data'.
- #
- # Note we duplicate those values here in case the kernel changes them
- # so we can have tests passing even if hid-tools doesn't have the
- # correct values.
- accelerometer_calibration_data = {
- "x": {"bias": -73, "numer": 16384, "denom": 16472},
- "y": {"bias": -352, "numer": 16384, "denom": 16344},
- "z": {"bias": 81, "numer": 16384, "denom": 16319},
- }
- gyroscope_calibration_data = {
- "x": {"bias": 0, "numer": 1105920, "denom": 17827},
- "y": {"bias": 0, "numer": 1105920, "denom": 17777},
- "z": {"bias": 0, "numer": 1105920, "denom": 17748},
- }
- class CalibratedPS4ControllerBluetooth(CalibratedPS4Controller, PS4ControllerBluetooth):
- pass
- class TestPS4ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
- def create_device(self):
- controller = CalibratedPS4ControllerBluetooth()
- controller.application_matches = application_matches
- return controller
- class CalibratedPS4ControllerUSB(CalibratedPS4Controller, PS4ControllerUSB):
- pass
- class TestPS4ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
- def create_device(self):
- controller = CalibratedPS4ControllerUSB()
- controller.application_matches = application_matches
- return controller
- class CalibratedPS5Controller(object):
- # DualSense reports uncalibrated sensor data. Calibration coefficients
- # can be retrieved using feature report 0x09.
- # The values below are the processed calibration values for the
- # DualSene matching the feature reports of PS5ControllerBluetooth/USB
- # as dumped from hid-playstation 'dualsense_get_calibration_data'.
- #
- # Note we duplicate those values here in case the kernel changes them
- # so we can have tests passing even if hid-tools doesn't have the
- # correct values.
- accelerometer_calibration_data = {
- "x": {"bias": 0, "numer": 16384, "denom": 16374},
- "y": {"bias": -114, "numer": 16384, "denom": 16362},
- "z": {"bias": 2, "numer": 16384, "denom": 16395},
- }
- gyroscope_calibration_data = {
- "x": {"bias": 0, "numer": 1105920, "denom": 17727},
- "y": {"bias": 0, "numer": 1105920, "denom": 17728},
- "z": {"bias": 0, "numer": 1105920, "denom": 17769},
- }
- class CalibratedPS5ControllerBluetooth(CalibratedPS5Controller, PS5ControllerBluetooth):
- pass
- class TestPS5ControllerBluetooth(SonyBaseTest.SonyPS4ControllerTest):
- kernel_modules = [PS5_MODULE]
- def create_device(self):
- controller = CalibratedPS5ControllerBluetooth()
- controller.application_matches = application_matches
- return controller
- class CalibratedPS5ControllerUSB(CalibratedPS5Controller, PS5ControllerUSB):
- pass
- class TestPS5ControllerUSB(SonyBaseTest.SonyPS4ControllerTest):
- kernel_modules = [PS5_MODULE]
- def create_device(self):
- controller = CalibratedPS5ControllerUSB()
- controller.application_matches = application_matches
- return controller
|