HidOverI2C 0.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hidoveri2c/__init__.py ADDED
@@ -0,0 +1,386 @@
1
+ import struct
2
+ import time
3
+ from dataclasses import dataclass
4
+ from enum import Enum, Flag
5
+ from typing import Type
6
+
7
+ from .i2c_msg import i2c_msg as _i2c_msg
8
+ from .protocols import I2CMessageClass
9
+
10
+ class HidOverI2c:
11
+ @dataclass
12
+ class HidOverI2cDescriptorHeader:
13
+ wHIDDescLength: int
14
+ bcdVersion: int
15
+
16
+ STRUCT = struct.Struct("<HH")
17
+
18
+ @classmethod
19
+ def unpack(cls, data: bytes):
20
+ values = cls.STRUCT.unpack(data)
21
+ return cls(*values)
22
+
23
+ @dataclass
24
+ class HidOverI2cDescriptor:
25
+ wHIDDescLength: int
26
+ bcdVersion: int
27
+ wReportDescLength: int
28
+ wReportDescRegister: int
29
+ wInputRegister: int
30
+ wMaxInputLength: int
31
+ wOutputRegister: int
32
+ wMaxOutputLength: int
33
+ wCommandRegister: int
34
+ wDataRegister: int
35
+ wVendorID: int
36
+ wProductID: int
37
+ wVersionID: int
38
+ RESERVED: int
39
+
40
+ STRUCT = struct.Struct("<HHHHHHHHHHHHHI")
41
+
42
+ @classmethod
43
+ def unpack(cls, data: bytes):
44
+ values = cls.STRUCT.unpack(data)
45
+ return cls(*values)
46
+
47
+ def pack(self) -> bytes:
48
+ return self.STRUCT.pack(
49
+ self.wHIDDescLength,
50
+ self.bcdVersion,
51
+ self.wReportDescLength,
52
+ self.wReportDescRegister,
53
+ self.wInputRegister,
54
+ self.wMaxInputLength,
55
+ self.wOutputRegister,
56
+ self.wMaxOutputLength,
57
+ self.wCommandRegister,
58
+ self.wDataRegister,
59
+ self.wVendorID,
60
+ self.wProductID,
61
+ self.wVersionID,
62
+ self.RESERVED
63
+ )
64
+
65
+ class RequestOpcode(Enum):
66
+ RESERVED_0 = 0b0000
67
+ RESET = 0b0001
68
+ GET_REPORT = 0b0010
69
+ SET_REPORT = 0b0011
70
+ GET_IDLE = 0b0100
71
+ SET_IDLE = 0b0101
72
+ GET_PROTOCOL = 0b0110
73
+ SET_PROTOCOL = 0b0111
74
+ SET_POWER = 0b1000
75
+ RESERVED_9 = 0b1001
76
+ RESERVED_A = 0b1010
77
+ RESERVED_B = 0b1011
78
+ RESERVED_C = 0b1100
79
+ RESERVED_D = 0b1101
80
+ VENDOR = 0b1110
81
+ RESERVED_F = 0b1111
82
+
83
+ class Flags(Flag):
84
+ ALLOW_INVALID = 0
85
+
86
+ class ReportType(Enum):
87
+ RESERVED = 0b00
88
+ Input = 0b01
89
+ Output = 0b10
90
+ Feature = 0b11
91
+
92
+ class Version:
93
+ def __init__(self, version_id):
94
+ self.major = version_id >> 8
95
+ self.minor = (version_id & 0xFF) >> 4
96
+ self.patch = (version_id & 0x0F)
97
+
98
+
99
+ """
100
+ Public Methods
101
+ """
102
+
103
+ def __init__(self, bus, addr, descriptor_reg, msg_type: Type[I2CMessageClass] = _i2c_msg):
104
+ self._bus = bus
105
+ self._msg = msg_type
106
+ self._addr = addr
107
+ self._descriptor_reg = descriptor_reg
108
+
109
+ read_msgs = self._prepare_register_read(self._descriptor_reg, 4)
110
+ self._bus.i2c_rdwr(*read_msgs)
111
+ descriptor_header = self.HidOverI2cDescriptorHeader.unpack(bytes(read_msgs[-1]))
112
+
113
+ read_msgs = self._prepare_register_read(self._descriptor_reg, descriptor_header.wHIDDescLength)
114
+ self._bus.i2c_rdwr(*read_msgs)
115
+ self._descriptor = self.HidOverI2cDescriptor.unpack(bytes(read_msgs[-1]))
116
+
117
+ def read(self, size: int, timeout_ms=0):
118
+ return self._input_read(size, timeout_ms)
119
+
120
+ def write(self, data) -> None:
121
+ self._output_write(data)
122
+
123
+ def get_report(self, report_type, report_id, size) -> bytes:
124
+ assert report_type in (self.ReportType.Input, self.ReportType.Feature)
125
+ if report_type == self.ReportType.Input:
126
+ return self.get_input_report(report_id, size)
127
+ elif report_type == self.ReportType.Feature:
128
+ return self.get_feature_report(report_id, size)
129
+
130
+ def set_report(self, report_type, report_id, data) -> None:
131
+ if report_type == self.ReportType.Output:
132
+ self._set_output_report(report_id, data)
133
+ elif report_type == self.ReportType.Feature:
134
+ self._set_feature_report(report_id, data)
135
+
136
+ def get_input_report(self, report_id, size) -> bytes:
137
+ raw_data = self._get_request(self.RequestOpcode.GET_REPORT, report_type=self.ReportType.Input, report_id=report_id, size=size)[2:]
138
+ length, data = struct.unpack("<H", raw_data[:2])[0], raw_data[2:]
139
+ assert length == (len(data)+2)
140
+ return data
141
+
142
+ def get_feature_report(self, report_id, size) -> bytes:
143
+ raw_data = self._get_request(self.RequestOpcode.GET_REPORT, report_type=self.ReportType.Feature, report_id=report_id, size=size)
144
+ length, data = struct.unpack("<H", raw_data[:2])[0], raw_data[2:]
145
+ assert length == (len(data)+2)
146
+ return data
147
+
148
+ def get_report_descriptor(self, size = 4096):
149
+ i2c_msgs = self._prepare_register_read(self._report_descriptor_register, size)
150
+ self._bus.i2c_rdwr(*i2c_msgs)
151
+ return bytes(i2c_msgs[-1])
152
+
153
+ def get_idle(self, report_id=0):
154
+ raw_data = self._get_request(self.RequestOpcode.GET_IDLE, report_type=self.ReportType.Feature, report_id=report_id, size=2)
155
+ length, idle = struct.unpack("<HH", raw_data)
156
+ assert length == (2 + 2)
157
+ return idle
158
+
159
+ def set_idle(self, duration, report_id=0):
160
+ _bytes = struct.pack("<H", duration)
161
+ self._set_request(self.RequestOpcode.SET_IDLE, data=_bytes, report_id=report_id)
162
+
163
+ def get_protocol(self):
164
+ raw_data = self._get_request(self.RequestOpcode.GET_PROTOCOL, size=2)
165
+ length, protocol = struct.unpack("<HH", raw_data)
166
+ assert length == (2 + 2)
167
+ return protocol
168
+
169
+ def set_protocol(self, protocol: int):
170
+ assert 0 <= protocol <= 1
171
+ _bytes = struct.pack("<H", protocol)
172
+ self._set_request(self.RequestOpcode.SET_PROTOCOL, data=_bytes)
173
+
174
+ def reset(self):
175
+ self._set_request(self.RequestOpcode.RESET)
176
+
177
+ def set_power(self, power) -> None:
178
+ assert 0 <= power <= 1
179
+ self._set_request(self.RequestOpcode.SET_POWER, report_id=power)
180
+
181
+ """
182
+ Private Methods
183
+ """
184
+ def _read(self, size: int) -> bytes:
185
+ """
186
+ Perform an immediate, unsolicited read from HidOverI2c device
187
+
188
+ """
189
+ read = self._msg.read(self._addr, size)
190
+ self._bus.i2c_rdwr(read)
191
+ return bytes(read)
192
+
193
+ def _input_read(self, size, timeout_ms=0):
194
+ """
195
+ Perform a read from the input register.
196
+ """
197
+ # TODO: check if we're non blocking and whether we have IRQ signal
198
+ start_time = time.time()
199
+ while True:
200
+ if timeout_ms > 0:
201
+ elapsed = (time.time() - start_time)*1000 # ms
202
+ remaining = timeout_ms - elapsed
203
+ if remaining <= 0:
204
+ return None
205
+
206
+ # wMaxInputLength includes the 2 byte length prefix, so we read that many bytes to ensure we get a full report.
207
+ data = self._read(self._descriptor.wMaxInputLength)
208
+ if len(data) <= 2:
209
+ continue # device initiated reset?
210
+ data_len = struct.unpack("<H", data[:2])[0]
211
+ if data_len <= 2:
212
+ continue # null report?
213
+
214
+ report = data[2:data_len]
215
+ return report
216
+
217
+ def _output_write(self, data) -> bytes:
218
+ i2c_msgs = self._prepare_register_write(self._output_register, data)
219
+ self._bus.i2c_rdwr(*i2c_msgs)
220
+ return bytes(i2c_msgs[-1]) # final read operation
221
+
222
+ def _set_output_report(self, report_id, data) -> None:
223
+ self._set_request(self.RequestOpcode.SET_REPORT, self.ReportType.Output, report_id, data)
224
+
225
+ def _set_feature_report(self, report_id, data) -> None:
226
+ self._set_request(self.RequestOpcode.SET_REPORT, self.ReportType.Feature, report_id=report_id, data=data)
227
+
228
+ def _get_request(self, opcode: RequestOpcode, *, report_type = ReportType.RESERVED, report_id = 0, size) -> bytes:
229
+ """
230
+ Perform a request which will Read from the data-register.
231
+ This function returns the data read INCLUDING the 2-byte length prefix.
232
+ """
233
+ _command_bytes = self._register_bytes(self._command_register) + self._pack_request(opcode, report_type, report_id)
234
+ _data_bytes = self._register_bytes(self._data_register)
235
+ write = self._msg.write(self._addr, _command_bytes + _data_bytes)
236
+ read = self._msg.read(self._addr, size+2) # +2 for length prefix
237
+ self._bus.i2c_rdwr(write, read)
238
+ return bytes(read)
239
+
240
+ def _set_request(self, opcode: RequestOpcode, report_type = ReportType.RESERVED, report_id = 0, data: bytes|None = None) -> None:
241
+ """
242
+ Perform a request which will Write to the data-register.
243
+ """
244
+ _command_bytes = self._register_bytes(self._command_register) + self._pack_request(opcode, report_type, report_id)
245
+ if data is not None:
246
+ _data_bytes = self._register_bytes(self._data_register) + struct.pack("<H", len(data)+2) + data
247
+ write = self._msg.write(self._addr, _command_bytes + _data_bytes)
248
+ else:
249
+ write = self._msg.write(self._addr, _command_bytes)
250
+ self._bus.i2c_rdwr(write)
251
+
252
+ def _read_register(self, register, size) -> bytes:
253
+ i2c_msgs = self._prepare_register_read(register, size)
254
+ self._bus.i2c_rdwr(*i2c_msgs)
255
+ return bytes(i2c_msgs[-1])
256
+
257
+ def _write_register(self, register, data) -> None:
258
+ i2c_msgs = self._prepare_register_write(register, data)
259
+ self._bus.i2c_rdwr(*i2c_msgs)
260
+
261
+ def _write_read_register(self, register, wr_data, rd_data):
262
+ i2c_msgs = self._prepare_register_write(register, wr_data)
263
+ i2c_msgs += self._prepare_register_read(register, rd_data)
264
+ self._bus.i2c_rdwr(*i2c_msgs)
265
+ return bytes(i2c_msgs[-1])
266
+
267
+ def _prepare_register_write(self, register, data) -> tuple[I2CMessageClass]:
268
+ _data = self._register_bytes(register) + struct.pack("<H", len(data)+2) + data
269
+ write = self._msg.write(self._addr, _data)
270
+ return (write,)
271
+
272
+ def _prepare_register_read(self, register, size) -> tuple[I2CMessageClass, I2CMessageClass]:
273
+ """
274
+ Prepares a Write (regnum)+Read (regdata) I2C messages for a single transaction.
275
+ """
276
+
277
+ write = self._msg.write(self._addr, self._register_bytes(register))
278
+ read = self._msg.read(self._addr, size)
279
+ return (write, read)
280
+
281
+ """
282
+ Static methods
283
+ """
284
+
285
+ @staticmethod
286
+ def _pack_request(opcode: RequestOpcode, report_type: ReportType, report_id) -> bytes:
287
+ """
288
+ Packs an opcode+report_type+report_id into 2 or 3 bytes, as required for the command register.
289
+ #7.1.1
290
+ """
291
+ assert 0 <= report_id <= 255
292
+ if report_id < 15:
293
+ _bytes = [report_id | (report_type.value << 4), opcode.value]
294
+ else:
295
+ _bytes = [15 | (report_type.value << 4), opcode.value, report_id]
296
+
297
+ return bytes(_bytes)
298
+
299
+ @staticmethod
300
+ def _register_bytes(register) -> bytes:
301
+ """
302
+ Returns the specified 16-bit value as bytes(2) as required for an i2c write register.
303
+ """
304
+ return struct.pack("<H", register)
305
+
306
+ """
307
+ Properties
308
+ """
309
+
310
+ @property
311
+ def manufacturer(self):
312
+ return "Microsoft"
313
+
314
+ @property
315
+ def product(self):
316
+ return "HID I2C Devic"
317
+
318
+ @property
319
+ def serial(self):
320
+ return None
321
+
322
+ @property
323
+ def vid(self):
324
+ return self._descriptor.wVendorID
325
+
326
+ @property
327
+ def pid(self):
328
+ return self._descriptor.wProductID
329
+
330
+ @property
331
+ def version(self):
332
+ return self.Version(self._descriptor.bcdVersion)
333
+
334
+ @property
335
+ def _output_register(self):
336
+ return self._descriptor.wOutputRegister
337
+
338
+ @property
339
+ def _input_register(self):
340
+ return self._descriptor.wInputRegister
341
+
342
+ @property
343
+ def _report_descriptor_register(self):
344
+ return self._descriptor.wReportDescRegister
345
+
346
+ @property
347
+ def _command_register(self):
348
+ return self._descriptor.wCommandRegister
349
+
350
+ @property
351
+ def _data_register(self):
352
+ return self._descriptor.wDataRegister
353
+
354
+ @property
355
+ def report_descriptor_length(self):
356
+ return self._descriptor.wReportDescLength
357
+
358
+ class HIDAPI_HidOverI2c(HidOverI2c):
359
+
360
+ def set_feature_report(self, report, *args, **kwargs):
361
+ report_id = report[0]
362
+ return super()._set_feature_report(report_id, report, *args, **kwargs)
363
+
364
+ def set_output_report(self, report, *args, **kwargs):
365
+ report_id = report[0]
366
+ return super()._set_output_report(report_id, report, *args, **kwargs)
367
+
368
+ send_feature_report = set_feature_report # provided for compatibility.
369
+ send_output_report = set_output_report # provided for compatibility.
370
+
371
+ def get_manufacturer_string(self) -> str|None:
372
+ return self.manufacturer
373
+
374
+ def get_product_string(self) -> str|None:
375
+ return None
376
+
377
+ def get_serial_number_string(self):
378
+ return None
379
+
380
+ def get_indexed_string(self, index):
381
+ return None
382
+
383
+ __all__ = [
384
+ 'HidOverI2c',
385
+ 'HIDAPI_HidOverI2c',
386
+ ]
hidoveri2c/i2c_msg.py ADDED
@@ -0,0 +1,48 @@
1
+
2
+ class i2c_msg:
3
+ READ = 0x01
4
+ WRITE = 0x00
5
+ buf: list[int]
6
+ len: int
7
+ addr: int
8
+ flags: int
9
+
10
+ def __init__(self, addr, buf, len, flags):
11
+ self.addr = addr
12
+ self.buf = buf
13
+ self.len = len
14
+ self.flags = flags
15
+
16
+ @staticmethod
17
+ def read(addr, size):
18
+ return i2c_msg(addr, [0]*size, size, i2c_msg.READ)
19
+
20
+ @staticmethod
21
+ def write(addr, data):
22
+ return i2c_msg(addr, data, len(data), i2c_msg.WRITE)
23
+
24
+ def __iter__(self):
25
+ """ Iterator / Generator
26
+
27
+ :return: iterates over :py:attr:`buf`
28
+ :rtype: :py:class:`generator` which returns int values
29
+ """
30
+ idx = 0
31
+ while idx < self.len:
32
+ yield self.buf[idx]
33
+ idx += 1
34
+
35
+ def __len__(self):
36
+ return self.len
37
+
38
+ def __bytes__(self):
39
+ return bytes(self.buf[:self.len])
40
+
41
+ def __repr__(self):
42
+ return 'i2c_msg(%d,%d,%r)' % (self.addr, self.flags, self.__bytes__())
43
+
44
+ def __str__(self):
45
+ s = self.__bytes__()
46
+ # Throw away non-decodable bytes
47
+ s = s.decode(errors="ignore")
48
+ return s
@@ -0,0 +1,5 @@
1
+ from .i2c_msg import I2CMessageClass
2
+
3
+ __all__ = [
4
+ 'I2CMessageClass'
5
+ ]
@@ -0,0 +1,21 @@
1
+ from typing import Protocol, runtime_checkable, Iterator
2
+
3
+ @runtime_checkable
4
+ class I2CMessageClass(Protocol):
5
+ """
6
+ Using flexible signatures to match both smbus2 and alternatives
7
+ """
8
+ addr: int
9
+ flags: int
10
+ len: int
11
+ def __bytes__(self) -> bytes: ...
12
+
13
+ def __iter__(self) -> Iterator[int]: ...
14
+
15
+ def __len__(self) -> int: ...
16
+
17
+ @staticmethod
18
+ def read(address: int, length: int) -> 'I2CMessageClass': ...
19
+
20
+ @staticmethod
21
+ def write(address: int, buf: bytes|str|list[int]) -> 'I2CMessageClass': ...