-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmessages.py
145 lines (120 loc) · 4.75 KB
/
messages.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
from dataclasses import dataclass
from typing import ClassVar, List
from consts import HIDPPConstants
@dataclass
class HIDPPMessageBase:
header_length: ClassVar[int] = 0
data: bytes
def __init__(self, data: bytes):
assert len(data) in (HIDPPConstants.ShortMessageLength.value, HIDPPConstants.LongMessageLength.value)
self.data = data
@property
def report_id(self) -> int:
"""
:return: The report id of the message
"""
return self.data[0]
@property
def device_index(self) -> int:
"""
:return: The device index to route the message to
"""
return self.data[1]
@property
def feature_index(self) -> int:
"""
:note: This is called a sub_id in the HID++ 1.0 documentation
:return: A feature identifier, groups device functions
"""
return self.data[2]
@property
def parameters(self) -> bytes:
"""
:return: Parameters for device function
"""
return self.data[self.header_length:]
def parameter(self, parameter_index: int) -> int:
"""
Retrieve a specific parameter by index
:param parameter_index: The index of the parameter
:return: A specific parameter value
"""
assert len(self.data) - self.header_length > parameter_index >= 0
return self.parameters[parameter_index]
def __len__(self) -> int:
"""
:return: The number of bytes in the message
"""
return len(self.data)
def __str__(self) -> str:
"""
:return: A hex string representation of the message
"""
return self.data.hex(' ')
@dataclass
class HIDPP10Message(HIDPPMessageBase):
"""
A HID++ 1.0 protocol message
"""
header_length: ClassVar[int] = 3
def __init__(self, data: bytes):
super().__init__(data)
@classmethod
def create(cls, device_index: int, sub_id: int, parameters: List[int], is_long: bool) -> 'HIDPP10Message':
parameters = parameters if parameters else [0x00] * (HIDPPConstants.report_id(is_long) - cls.header_length)
data = bytes([HIDPPConstants.report_id(is_long), device_index, sub_id] + parameters)
return cls(data)
@dataclass
class HIDPP20Message(HIDPPMessageBase):
"""
A HID++ 2.0 protocol message
"""
header_length: ClassVar[int] = 4
def __init__(self, data: bytes):
super().__init__(data)
@classmethod
def create(cls, device_index: int, feature_index: int, function_id: int, parameters: List[int],
is_long: bool, software_id: int = HIDPPConstants.SoftwareID.value) -> 'HIDPP20Message':
"""
Creates a new HID++ 2.0 message
:param device_index: Device identifier
:param feature_index: Feature identifier
:param function_id: Feature function identifier
:param software_id: Communicating software ID, allows discriminating responses sent by this program
:param is_long: True if this is a long HID++ message, otherwise False
:param parameters: Message parameters
"""
data = [HIDPPConstants.LongReportID.value if is_long else HIDPPConstants.ShortReportID.value, device_index,
feature_index, (function_id << 4) | software_id]
assert len(bytes(data)) == cls.header_length, "Invalid header length"
expected_message_length = HIDPPConstants.LongMessageLength.value if is_long else \
HIDPPConstants.ShortMessageLength.value
if not parameters:
parameters = [0x00] * (expected_message_length - cls.header_length)
assert len(bytes(parameters)) == (expected_message_length - cls.header_length), "Invalid parameter length"
data += parameters
return cls(bytes(data))
@property
def device_index(self):
"""
A device identifier, multiple devices may communicate with the same physical device (e.g. unified receiver)
:return:
"""
return self.data[1]
@property
def function_id(self):
"""
A function is composed of a request sent by the host followed by one or more responses returned by the device.
Within a given feature, each function is defined by a function identifier.
Most functions will be read or write functions (functions which do both or neither are allowed).
Unless otherwise specified, the protocol is big-endian.
:return:
"""
return (self.data[3] & 0xF0) >> 4
@property
def software_id(self):
"""
:return: A number uniquely defining the software that sends a message. The firmware must copy the software identifier
in the response but does not use it in any other ways.
"""
return self.data[3] & 0x0F