tests: add tests for logitech_receiver device
This commit is contained in:
parent
50c8013cb1
commit
ebc76bca24
|
@ -184,8 +184,8 @@ class Device:
|
|||
if codename:
|
||||
self._codename = codename
|
||||
elif self.protocol < 2.0:
|
||||
self._codename = "? (%s)" % (self.wpid or self.product_id)
|
||||
return self._codename or "?? (%s)" % (self.wpid or self.product_id)
|
||||
self._codename = "? (%s)" % (self.wpid or hex(self.product_id)[2:].upper())
|
||||
return self._codename or "?? (%s)" % (self.wpid or hex(self.product_id)[2:].upper())
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -539,7 +539,7 @@ class Device:
|
|||
|
||||
def __str__(self):
|
||||
try:
|
||||
name = self.name or self.codename or "?"
|
||||
name = self._name or self._codename or "?"
|
||||
except exceptions.NoSuchDevice:
|
||||
name = "name not available"
|
||||
return "<Device(%d,%s,%s,%s)>" % (self.number, self.wpid or self.product_id, name, self.serial)
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
## Copyright (C) 2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
"""HID++ data and functions common to several logitech_receiver test files"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from struct import pack
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def open_path(path: Optional[str]) -> Optional[int]:
|
||||
return int(path, 16) if path is not None else None
|
||||
|
||||
|
||||
@dataclass
|
||||
class Response:
|
||||
response: Optional[str]
|
||||
id: int
|
||||
params: str = ""
|
||||
handle: int = 0x11
|
||||
devnumber: int = 0xFF
|
||||
no_reply: bool = False
|
||||
|
||||
|
||||
def replace_number(responses, number): # change the devnumber for a list of responses
|
||||
return [Response(r.response, r.id, r.params, r.handle, number, r.no_reply) for r in responses]
|
||||
|
||||
|
||||
def ping(responses, handle, devnumber, long_message=False):
|
||||
print("PING ", hex(handle), hex(devnumber) if devnumber else devnumber)
|
||||
print(responses)
|
||||
for r in responses:
|
||||
if handle == r.handle and devnumber == r.devnumber and r.id == 0x0010:
|
||||
print("RESPONSE", hex(r.handle), hex(r.devnumber), r.response)
|
||||
return r.response
|
||||
|
||||
|
||||
def request(responses, handle, devnumber, id, *params, no_reply=False, return_error=False, long_message=False, protocol=1.0):
|
||||
params = b"".join(pack("B", p) if isinstance(p, int) else p for p in params)
|
||||
print("REQUEST ", hex(handle), hex(devnumber), hex(id), params.hex())
|
||||
for r in responses:
|
||||
if handle == r.handle and devnumber == r.devnumber and r.id == id and bytes.fromhex(r.params) == params:
|
||||
print("RESPONSE", hex(r.handle), hex(r.devnumber), hex(r.id), r.params, r.response)
|
||||
return bytes.fromhex(r.response) if r.response is not None else None
|
||||
|
||||
|
||||
r_empty = [ # a HID++ device with no responses except for ping
|
||||
Response(1.0, 0x0010), # ping
|
||||
]
|
||||
|
||||
r_keyboard_1 = [ # a HID++ 1.0 keyboard
|
||||
Response(1.0, 0x0010), # ping
|
||||
Response("001234", 0x81F1, "01"), # firmware
|
||||
Response("003412", 0x81F1, "02"), # firmware
|
||||
Response("002345", 0x81F1, "03"), # firmware
|
||||
Response("003456", 0x81F1, "04"), # firmware
|
||||
]
|
||||
|
||||
r_keyboard_2 = [ # a HID++ 2.0 keyboard
|
||||
Response(4.2, 0x0010), # ping
|
||||
Response("010001", 0x0000, "0001"), # feature set at 0x01
|
||||
Response("020003", 0x0000, "1000"), # battery status at 0x02
|
||||
Response("030001", 0x0000, "0003"), # device information at 0x03
|
||||
Response("040003", 0x0000, "0100"), # unknown 0100 at 0x04
|
||||
Response("050003", 0x0000, "1B04"), # reprogrammable keys V4 at 0x05
|
||||
Response("08", 0x0100), # 8 features
|
||||
Response("00010001", 0x0110, "01"), # feature set at 0x01
|
||||
Response("10000001", 0x0110, "02"), # battery status at 0x02
|
||||
Response("00030001", 0x0110, "03"), # device information at 0x03
|
||||
Response("01000003", 0x0110, "04"), # unknown 0100 at 0x04
|
||||
Response("1B040003", 0x0110, "05"), # reprogrammable keys V4 at 0x05
|
||||
Response("0212345678000D1234567890ABAA01", 0x0300), # device information
|
||||
Response("00110012AB010203CD00", 0x0510, "00"), # reprogrammable keys V4
|
||||
Response("01110022AB010203CD00", 0x0510, "01"), # reprogrammable keys V4
|
||||
Response("00010111AB010203CD00", 0x0510, "02"), # reprogrammable keys V4
|
||||
Response("03110032AB010204CD00", 0x0510, "03"), # reprogrammable keys V4
|
||||
Response("00030333AB010203CD00", 0x0510, "04"), # reprogrammable keys V4
|
||||
]
|
||||
|
||||
r_mouse_1 = [ # a HID++ 1.0 mouse
|
||||
Response(1.0, 0x0010), # ping
|
||||
]
|
||||
|
||||
r_mouse_2 = [ # a HID++ 2.0 mouse with few responses except for ping
|
||||
Response(4.2, 0x0010), # ping
|
||||
]
|
||||
|
||||
r_mouse_3 = [ # a HID++ 2.0 mouse
|
||||
Response(4.5, 0x0010), # ping
|
||||
Response("010001", 0x0000, "0001"), # feature set at 0x01
|
||||
Response("020002", 0x0000, "8060"), # report rate at 0x02
|
||||
Response("040001", 0x0000, "0003"), # device information at 0x04
|
||||
Response("050002", 0x0000, "0005"), # device type and name at 0x05
|
||||
Response("08", 0x0100), # 8 features
|
||||
Response("00010001", 0x0110, "01"), # feature set at 0x01
|
||||
Response("80600002", 0x0110, "02"), # report rate at 0x02
|
||||
Response("00030001", 0x0110, "04"), # device information at 0x04
|
||||
Response("00050002", 0x0110, "05"), # device type and name at 0x05
|
||||
Response("09", 0x0210), # report rate - current rate
|
||||
Response("03123456790008123456780000AA01", 0x0400), # device information
|
||||
Response("0141424302030100", 0x0410, "00"), # firmware 0
|
||||
Response("0241", 0x0410, "01"), # firmware 1
|
||||
Response("05", 0x0410, "02"), # firmware 2
|
||||
Response("12", 0x0500), # name count - 18 characters
|
||||
Response("414241424142414241424142414241", 0x0510, "00"), # name - first 15 characters
|
||||
Response("444544000000000000000000000000", 0x0510, "0F"), # name - last 3 characters
|
||||
]
|
|
@ -0,0 +1,181 @@
|
|||
## Copyright (C) 2024 Solaar Contributors https://pwr-solaar.github.io/Solaar/
|
||||
##
|
||||
## This program is free software; you can redistribute it and/or modify
|
||||
## it under the terms of the GNU General Public License as published by
|
||||
## the Free Software Foundation; either version 2 of the License, or
|
||||
## (at your option) any later version.
|
||||
##
|
||||
## This program is distributed in the hope that it will be useful,
|
||||
## but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
## GNU General Public License for more details.
|
||||
##
|
||||
## You should have received a copy of the GNU General Public License along
|
||||
## with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
from dataclasses import dataclass
|
||||
from functools import partial
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
|
||||
from logitech_receiver import device
|
||||
|
||||
from . import hidpp
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeviceInfo:
|
||||
path: str
|
||||
vendor_id: int = 1133
|
||||
product_id: int = 4066
|
||||
hidpp_short: bool = False
|
||||
hidpp_long: bool = True
|
||||
bus_id: int = 0x0003 # USB
|
||||
|
||||
|
||||
di_CCCC = DeviceInfo("11", product_id=0xCCCC)
|
||||
di_C318 = DeviceInfo("11", product_id=0xC318)
|
||||
di_B530 = DeviceInfo("11", product_id=0xB350, bus_id=0x0005)
|
||||
di_C068 = DeviceInfo("11", product_id=0xC06B)
|
||||
di_C08A = DeviceInfo("11", product_id=0xC08A)
|
||||
di_DDDD = DeviceInfo("11", product_id=0xDDDD)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_base():
|
||||
with mock.patch("logitech_receiver.base.open_path", return_value=None) as mock_open_path:
|
||||
with mock.patch("logitech_receiver.base.request", return_value=None) as mock_request:
|
||||
with mock.patch("logitech_receiver.base.ping", return_value=None) as mock_ping:
|
||||
yield mock_open_path, mock_request, mock_ping
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device_info, responses, handle, _name, _codename, number, protocol",
|
||||
zip(
|
||||
[di_CCCC, di_C318, di_B530, di_C068, di_C08A, di_DDDD],
|
||||
[hidpp.r_empty, hidpp.r_keyboard_1, hidpp.r_keyboard_2, hidpp.r_mouse_1, hidpp.r_mouse_2, hidpp.r_mouse_3],
|
||||
[0x11, 0x11, 0x11, 0x11, 0x11, 0x11],
|
||||
[None, "Illuminated Keyboard", "Craft Advanced Keyboard", "G700 Gaming Mouse", "MX Vertical Wireless Mouse", None],
|
||||
[None, "Illuminated", "Craft", "G700", "MX Vertical", None],
|
||||
[0xFF, 0x0, 0xFF, 0x0, 0xFF, 0xFF],
|
||||
[1.0, 1.0, 4.5, 1.0, 4.5, 4.5],
|
||||
),
|
||||
)
|
||||
def test_Device_info(device_info, responses, handle, _name, _codename, number, protocol, mock_base):
|
||||
mock_base[0].side_effect = hidpp.open_path
|
||||
mock_base[1].side_effect = partial(hidpp.request, responses)
|
||||
mock_base[2].side_effect = partial(hidpp.ping, responses)
|
||||
|
||||
test_device = device.Device(None, None, None, handle=handle, device_info=device_info)
|
||||
|
||||
assert test_device.handle == handle
|
||||
assert test_device._name == _name
|
||||
assert test_device._codename == _codename
|
||||
assert test_device.number == number
|
||||
assert test_device._protocol == protocol
|
||||
|
||||
|
||||
@dataclass
|
||||
class Receiver:
|
||||
path: str = "1"
|
||||
handle: int = 0x11
|
||||
|
||||
def device_codename(self, number):
|
||||
return None
|
||||
|
||||
def __contains__(self, dev):
|
||||
return True
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_hid():
|
||||
with mock.patch("hidapi.find_paired_node", return_value=None) as find_paired_node:
|
||||
yield find_paired_node
|
||||
|
||||
|
||||
pi_CCCC = {"wpid": "CCCC", "kind": 0, "serial": None, "polling": "1ms", "power_switch": "top"}
|
||||
pi_2011 = {"wpid": "2011", "kind": 1, "serial": "1234", "polling": "2ms", "power_switch": "bottom"}
|
||||
pi_4066 = {"wpid": "4066", "kind": 1, "serial": "5678", "polling": "4ms", "power_switch": "left"}
|
||||
pi_1023 = {"wpid": "1023", "kind": 2, "serial": "1234", "polling": "8ms", "power_switch": "right"}
|
||||
pi_407B = {"wpid": "407B", "kind": 2, "serial": "5678", "polling": "1ms", "power_switch": "left"}
|
||||
pi_DDDD = {"wpid": "DDDD", "kind": 2, "serial": "1234", "polling": "2ms", "power_switch": "top"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"number, pairing_info, responses, handle, _name, codename, protocol, name",
|
||||
zip(
|
||||
range(1, 7),
|
||||
[pi_CCCC, pi_2011, pi_4066, pi_1023, pi_407B, pi_DDDD],
|
||||
[hidpp.r_empty, hidpp.r_keyboard_1, hidpp.r_keyboard_2, hidpp.r_mouse_1, hidpp.r_mouse_2, hidpp.r_mouse_3],
|
||||
[None, 0x11, 0x11, 0x11, 0x11, 0x11],
|
||||
[None, "Wireless Keyboard K520", "Craft Advanced Keyboard", "G700 Gaming Mouse", "MX Vertical Wireless Mouse", None],
|
||||
["? (CCCC)", "K520", "Craft", "G700", "MX Vertical", "ABABABABABABABADED"],
|
||||
[1.0, 1.0, 4.5, 1.0, 4.5, 4.5],
|
||||
[
|
||||
"? (CCCC)",
|
||||
"Wireless Keyboard K520",
|
||||
"Craft Advanced Keyboard",
|
||||
"G700 Gaming Mouse",
|
||||
"MX Vertical Wireless Mouse",
|
||||
"ABABABABABABABADED",
|
||||
],
|
||||
),
|
||||
)
|
||||
def test_Device_receiver(number, pairing_info, responses, handle, _name, codename, protocol, name, mock_base):
|
||||
mock_base[0].side_effect = hidpp.open_path
|
||||
mock_base[1].side_effect = partial(hidpp.request, hidpp.replace_number(responses, number))
|
||||
mock_base[2].side_effect = partial(hidpp.ping, hidpp.replace_number(responses, number))
|
||||
|
||||
test_device = device.Device(Receiver(), number, True, pairing_info, handle=handle)
|
||||
|
||||
assert test_device.handle == handle
|
||||
assert test_device._name == _name
|
||||
assert test_device.codename == codename
|
||||
assert test_device.number == number
|
||||
assert test_device._protocol == protocol
|
||||
assert test_device.protocol == (protocol or 0)
|
||||
assert test_device.codename == codename
|
||||
assert test_device.name == name
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"number, pairing_info, responses, handle, unitId, modelId, tid_map, kind, firmware, serial, id, psl, rate",
|
||||
zip(
|
||||
range(1, 7),
|
||||
[pi_CCCC, pi_2011, pi_4066, pi_1023, pi_407B, pi_DDDD],
|
||||
[hidpp.r_empty, hidpp.r_keyboard_1, hidpp.r_keyboard_2, hidpp.r_mouse_1, hidpp.r_mouse_2, hidpp.r_mouse_3],
|
||||
[None, 0x11, 0x11, 0x11, 0x11, 0x11],
|
||||
[None, None, "12345678", None, None, "12345679"], # unitId
|
||||
[None, None, "1234567890AB", None, None, "123456780000"], # modelId
|
||||
[None, None, {"btid": "1234", "wpid": "5678", "usbid": "90AB"}, None, None, {"usbid": "1234"}], # tid_map
|
||||
["?", 1, 1, 2, 2, 2], # kind
|
||||
[(), True, (), (), (), True], # firmware
|
||||
[None, "1234", "5678", "1234", "5678", "1234"], # serial
|
||||
["", "1234", "12345678", "1234", "5678", "12345679"], # id
|
||||
["top", "bottom", "left", "right", "left", "top"], # power switch location
|
||||
["1ms", "2ms", "4ms", "8ms", "1ms", "9ms"], # polling rate
|
||||
),
|
||||
)
|
||||
def test_Device_ids(
|
||||
number, pairing_info, responses, handle, unitId, modelId, tid_map, kind, firmware, serial, id, psl, rate, mock_base
|
||||
):
|
||||
mock_base[0].side_effect = hidpp.open_path
|
||||
mock_base[1].side_effect = partial(hidpp.request, hidpp.replace_number(responses, number))
|
||||
mock_base[2].side_effect = partial(hidpp.ping, hidpp.replace_number(responses, number))
|
||||
|
||||
test_device = device.Device(Receiver(), number, True, pairing_info, handle=handle)
|
||||
|
||||
assert test_device.unitId == unitId
|
||||
assert test_device.modelId == modelId
|
||||
assert test_device.tid_map == tid_map
|
||||
assert test_device.kind == kind
|
||||
|
||||
assert test_device.firmware == firmware or len(test_device.firmware) > 0 and firmware is True
|
||||
assert test_device.id == id
|
||||
assert test_device.power_switch_location == psl
|
||||
assert test_device.polling_rate == rate
|
||||
|
||||
|
||||
# IMPORTANT TODO - battery
|
Loading…
Reference in New Issue