reader-integration-kit-linux-aarch64 1.4.0__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.
Files changed (57) hide show
  1. reader_integration_kit/__init__.py +12 -0
  2. reader_integration_kit/enum/__init__.py +28 -0
  3. reader_integration_kit/enum/beep_duration.py +12 -0
  4. reader_integration_kit/enum/beep_volume.py +15 -0
  5. reader_integration_kit/enum/ble_data_type.py +12 -0
  6. reader_integration_kit/enum/blob_type.py +27 -0
  7. reader_integration_kit/enum/checkpoint_type.py +13 -0
  8. reader_integration_kit/enum/data_conversion_type.py +12 -0
  9. reader_integration_kit/enum/field_definition_type.py +14 -0
  10. reader_integration_kit/enum/led_color.py +15 -0
  11. reader_integration_kit/enum/protocol_type.py +14 -0
  12. reader_integration_kit/enum/proximity_card_type.py +166 -0
  13. reader_integration_kit/enum/reader_module_id.py +13 -0
  14. reader_integration_kit/enum/reader_module_state.py +12 -0
  15. reader_integration_kit/enum/serial_port_baud_rate.py +15 -0
  16. reader_integration_kit/enum/serial_port_data_bits.py +10 -0
  17. reader_integration_kit/enum/serial_port_flow_control.py +11 -0
  18. reader_integration_kit/enum/serial_port_parity.py +10 -0
  19. reader_integration_kit/enum/serial_port_stop_bits.py +9 -0
  20. reader_integration_kit/enum/transparent_mode_state.py +11 -0
  21. reader_integration_kit/enum/transparent_mode_status.py +11 -0
  22. reader_integration_kit/errors/__init__.py +4 -0
  23. reader_integration_kit/errors/reader_exception.py +58 -0
  24. reader_integration_kit/facade/__init__.py +852 -0
  25. reader_integration_kit/lib/libReaderIntegrationKit.so +0 -0
  26. reader_integration_kit/lib/libReaderIntegrationKit.so.1.4.0 +0 -0
  27. reader_integration_kit/structures/__init__.py +36 -0
  28. reader_integration_kit/structures/blob_header.py +54 -0
  29. reader_integration_kit/structures/bluetooth_firmware_version.py +35 -0
  30. reader_integration_kit/structures/card_data.py +77 -0
  31. reader_integration_kit/structures/card_type_info.py +51 -0
  32. reader_integration_kit/structures/device_id.py +12 -0
  33. reader_integration_kit/structures/extended_configuration.py +32 -0
  34. reader_integration_kit/structures/felica_sam_firmware_version.py +38 -0
  35. reader_integration_kit/structures/field_entry.py +120 -0
  36. reader_integration_kit/structures/field_separator_data_header.py +83 -0
  37. reader_integration_kit/structures/hash_data.py +55 -0
  38. reader_integration_kit/structures/hid_se_sam_firmware_version.py +38 -0
  39. reader_integration_kit/structures/led_configuration.py +13 -0
  40. reader_integration_kit/structures/library_info.py +74 -0
  41. reader_integration_kit/structures/luid_response_information.py +44 -0
  42. reader_integration_kit/structures/microcontroller_firmware_version.py +38 -0
  43. reader_integration_kit/structures/nxp_sam_firmware_version.py +38 -0
  44. reader_integration_kit/structures/reader_configuration.py +171 -0
  45. reader_integration_kit/structures/reader_data.py +49 -0
  46. reader_integration_kit/structures/reader_definition.py +14 -0
  47. reader_integration_kit/structures/reader_metadata_struct.py +195 -0
  48. reader_integration_kit/structures/rik_result.py +71 -0
  49. reader_integration_kit/structures/separator_character.py +44 -0
  50. reader_integration_kit/structures/separator_entry.py +73 -0
  51. reader_integration_kit/structures/serial_port_settings.py +45 -0
  52. reader_integration_kit/structures/smart_card_configuration.py +34 -0
  53. reader_integration_kit_linux_aarch64-1.4.0.dist-info/METADATA +115 -0
  54. reader_integration_kit_linux_aarch64-1.4.0.dist-info/RECORD +57 -0
  55. reader_integration_kit_linux_aarch64-1.4.0.dist-info/WHEEL +5 -0
  56. reader_integration_kit_linux_aarch64-1.4.0.dist-info/rfIDEAS_EULA.txt +281 -0
  57. reader_integration_kit_linux_aarch64-1.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,852 @@
1
+ """
2
+ Internal infrastructure module for loading the native library and managing function pointers.
3
+ This module is used internally by Reader and should not be used directly.
4
+ Use Reader instead for all reader operations.
5
+ """
6
+ import ctypes
7
+ from ctypes import (
8
+ c_size_t, c_ushort, c_uint, c_byte, c_char_p, c_ulong, c_int, POINTER, Structure, byref,
9
+ create_string_buffer, c_void_p, c_uint8, c_uint16, c_uint32, c_bool, CFUNCTYPE
10
+ )
11
+ import os
12
+ import platform
13
+ from typing import Callable, Optional
14
+
15
+ # Type alias for the Python-level credential callback.
16
+ # Signature: callback(card_data: CardData) -> None
17
+ CredentialCallback = Callable[['CardData'], None]
18
+
19
+ from reader_integration_kit.errors import ReaderException
20
+ from reader_integration_kit.structures import *
21
+ from reader_integration_kit.enum import *
22
+
23
+
24
+ class _NativeLibrary:
25
+ """Internal class for loading the native library and managing function pointers."""
26
+
27
+ _instance: Optional['_NativeLibrary'] = None
28
+ _native_library_handle = None
29
+ _dll = None
30
+
31
+ def __new__(cls):
32
+ if cls._instance is None:
33
+ cls._instance = super().__new__(cls)
34
+ cls._instance._load_library()
35
+ cls._instance._initialize_functions()
36
+ return cls._instance
37
+
38
+ @staticmethod
39
+ def _get_library_name() -> str:
40
+ """Get the library name based on the platform."""
41
+ if platform.system() == 'Windows':
42
+ return "ReaderIntegrationKit.dll"
43
+ elif platform.system() == 'Linux':
44
+ return "libReaderIntegrationKit.so"
45
+ elif platform.system() == 'Darwin':
46
+ return "libReaderIntegrationKit.dylib"
47
+ else:
48
+ raise RuntimeError(f"Unsupported OS platform: {platform.system()}")
49
+
50
+ def _get_library_path(self) -> str:
51
+ """Get the path to the native library."""
52
+ library_name = self._get_library_name()
53
+ lib_dir = os.path.join(os.path.dirname(__file__), '..', 'lib')
54
+ lib_path = os.path.join(lib_dir, library_name)
55
+
56
+ if not os.path.exists(lib_path):
57
+ # Try alternative locations
58
+ alt_paths = [
59
+ os.path.join(os.path.dirname(__file__), '..', '..', 'lib', library_name),
60
+ os.path.join(os.path.dirname(__file__), '..', '..', '..', 'lib', library_name),
61
+ library_name, # Try system library path
62
+ ]
63
+
64
+ for alt_path in alt_paths:
65
+ if os.path.exists(alt_path):
66
+ return alt_path
67
+
68
+ raise FileNotFoundError(
69
+ f"Shared library not found. Tried: {lib_path} and {alt_paths}"
70
+ )
71
+
72
+ return lib_path
73
+
74
+ def _load_library(self):
75
+ """Load the native library."""
76
+ try:
77
+ lib_path = self._get_library_path()
78
+ # On Windows, use WinDLL to properly capture stdout/stderr from the DLL
79
+ # On Linux/Mac, use CDLL
80
+ if platform.system() == 'Windows':
81
+ self._dll = ctypes.WinDLL(lib_path)
82
+ else:
83
+ self._dll = ctypes.CDLL(lib_path)
84
+ self._native_library_handle = self._dll
85
+ except Exception as e:
86
+ raise RuntimeError(f"Failed to load native library: {e}") from e
87
+
88
+ def _initialize_functions(self):
89
+ """Initialize all function signatures."""
90
+ # Factory functions
91
+ self._dll.RikReader_Open.argtypes = [POINTER(RikResult), POINTER(ReaderDefinition), c_int]
92
+ self._dll.RikReader_Open.restype = c_void_p
93
+
94
+ # Common reader methods
95
+ self._dll.Rik_Close.argtypes = [POINTER(RikResult), c_void_p]
96
+ self._dll.Rik_Close.restype = None
97
+
98
+ self._dll.Rik_Init.argtypes = [POINTER(RikResult), c_void_p]
99
+ self._dll.Rik_Init.restype = None
100
+
101
+ # Metadata functions - work directly with reader
102
+ self._dll.Rik_RefreshMetadata.argtypes = [POINTER(RikResult), c_void_p]
103
+ self._dll.Rik_RefreshMetadata.restype = None
104
+
105
+ # Rik_GetMetadataStruct
106
+ from reader_integration_kit.structures.reader_metadata_struct import ReaderMetadataStruct
107
+ self._dll.Rik_GetMetadataStruct.argtypes = [POINTER(RikResult), c_void_p, POINTER(ReaderMetadataStruct), c_bool]
108
+ self._dll.Rik_GetMetadataStruct.restype = None
109
+
110
+ # WaveID-specific methods
111
+ self._dll.RikReader_Beep.argtypes = [POINTER(RikResult), c_void_p, c_uint8, c_uint8]
112
+ self._dll.RikReader_Beep.restype = None
113
+
114
+ self._dll.RikReader_GetCardData.argtypes = [POINTER(RikResult), c_void_p, POINTER(c_uint8), ctypes.c_size_t, POINTER(ctypes.c_uint32)]
115
+ self._dll.RikReader_GetCardData.restype = None
116
+
117
+ self._dll.RikReader_GetBeeperVolume.argtypes = [POINTER(RikResult), c_void_p, POINTER(c_uint8)]
118
+ self._dll.RikReader_GetBeeperVolume.restype = None
119
+
120
+ self._dll.RikReader_SetBeeperVolume.argtypes = [POINTER(RikResult), c_void_p, c_uint8]
121
+ self._dll.RikReader_SetBeeperVolume.restype = None
122
+
123
+ self._dll.RikReader_GetReaderConfiguration.argtypes = [POINTER(RikResult), c_void_p, c_uint8, POINTER(ReaderConfigurationStruct), POINTER(ExtendedConfiguration)]
124
+ self._dll.RikReader_GetReaderConfiguration.restype = None
125
+
126
+ self._dll.RikReader_SetReaderConfiguration.argtypes = [POINTER(RikResult), c_void_p, c_uint8, POINTER(ReaderConfigurationStruct), POINTER(ExtendedConfiguration), POINTER(HashData)]
127
+ self._dll.RikReader_SetReaderConfiguration.restype = None
128
+
129
+ self._dll.RikReader_GetModuleState.argtypes = [POINTER(RikResult), c_void_p, c_uint8, POINTER(c_uint)]
130
+ self._dll.RikReader_GetModuleState.restype = None
131
+
132
+ self._dll.RikReader_SetModuleState.argtypes = [POINTER(RikResult), c_void_p, c_uint8, c_uint]
133
+ self._dll.RikReader_SetModuleState.restype = None
134
+
135
+ # LED configuration functions
136
+ from reader_integration_kit.structures.led_configuration import LedConfiguration
137
+ self._dll.RikReader_GetLedConfiguration.argtypes = [POINTER(RikResult), c_void_p, c_uint8, POINTER(LedConfiguration)]
138
+ self._dll.RikReader_GetLedConfiguration.restype = None
139
+
140
+ self._dll.RikReader_SetLedConfiguration.argtypes = [POINTER(RikResult), c_void_p, c_uint8, POINTER(LedConfiguration)]
141
+ self._dll.RikReader_SetLedConfiguration.restype = None
142
+
143
+ # Enable keystroking functions
144
+ self._dll.RikReader_EnableKeystroking.argtypes = [POINTER(RikResult), c_void_p, c_bool]
145
+ self._dll.RikReader_EnableKeystroking.restype = None
146
+
147
+ # LUID functions
148
+ self._dll.RikReader_GetLuid.argtypes = [POINTER(RikResult), c_void_p, POINTER(LuidResponseInformation)]
149
+ self._dll.RikReader_GetLuid.restype = None
150
+
151
+ self._dll.RikReader_SetLuid.argtypes = [POINTER(RikResult), c_void_p, c_uint16]
152
+ self._dll.RikReader_SetLuid.restype = None
153
+
154
+ # Get Supported Card Types
155
+ from reader_integration_kit.structures.card_type_info import SupportedCardTypesResult
156
+ self._dll.RikReader_GetSupportedCardTypes.argtypes = [POINTER(RikResult), c_void_p, POINTER(SupportedCardTypesResult)]
157
+ self._dll.RikReader_GetSupportedCardTypes.restype = None
158
+
159
+ # Read/Write BLE Configuration functions
160
+ self._dll.RikReader_ReadBleConfigurationFromReader.argtypes = [POINTER(RikResult), c_void_p, c_uint8, c_char_p]
161
+ self._dll.RikReader_ReadBleConfigurationFromReader.restype = None
162
+
163
+ self._dll.RikReader_WriteBleConfigurationToReader.argtypes = [POINTER(RikResult), c_void_p, c_uint8, c_char_p]
164
+ self._dll.RikReader_WriteBleConfigurationToReader.restype = None
165
+
166
+ # Hwg file functions
167
+ self._dll.RikReader_WriteHwgFileToReader.argtypes = [POINTER(RikResult), c_void_p, c_char_p]
168
+ self._dll.RikReader_WriteHwgFileToReader.restype = None
169
+
170
+ self._dll.RikReader_ReadHwgFileFromReader.argtypes = [POINTER(RikResult), c_void_p, c_char_p, c_bool]
171
+ self._dll.RikReader_ReadHwgFileFromReader.restype = None
172
+
173
+ # Smart card configuration functions
174
+ self._dll.RikReader_WriteSmartCardConfigurationToReader.argtypes = [POINTER(RikResult), c_void_p, c_char_p]
175
+ self._dll.RikReader_WriteSmartCardConfigurationToReader.restype = None
176
+
177
+ self._dll.RikReader_ReadSmartCardConfigurationFromReader.argtypes = [POINTER(RikResult), c_void_p, POINTER(SmartCardConfigurationStruct)]
178
+ self._dll.RikReader_ReadSmartCardConfigurationFromReader.restype = None
179
+
180
+ # Write and reset reader configuration functions
181
+ self._dll.RikReader_WriteUserDefaultsToReader.argtypes = [POINTER(RikResult), c_void_p]
182
+ self._dll.RikReader_WriteUserDefaultsToReader.restype = None
183
+
184
+ self._dll.RikReader_ResetReaderConfiguration.argtypes = [POINTER(RikResult), c_void_p, c_uint8]
185
+ self._dll.RikReader_ResetReaderConfiguration.restype = None
186
+
187
+ # Credential-presented callback functions
188
+ # NativeCredentialCallback: void (*)(const uint8_t* cardData, uint32_t bitCount)
189
+ NativeCredentialCallback = CFUNCTYPE(None, POINTER(c_uint8), c_uint32)
190
+ self._credential_callback_type = NativeCredentialCallback
191
+ self._dll.RikReader_OnCredentialPresented.argtypes = [POINTER(RikResult), c_void_p, NativeCredentialCallback]
192
+ self._dll.RikReader_OnCredentialPresented.restype = c_uint32
193
+
194
+ self._dll.RikReader_UnsubscribeCredentialCallback.argtypes = [POINTER(RikResult), c_void_p, c_uint32]
195
+ self._dll.RikReader_UnsubscribeCredentialCallback.restype = None
196
+
197
+ # Transparent mode functions
198
+ self._dll.RikReader_EnableTransparentMode.argtypes = [POINTER(RikResult), c_void_p, c_uint8, c_uint8]
199
+ self._dll.RikReader_EnableTransparentMode.restype = None
200
+
201
+ self._dll.RikReader_GetTransparentModeStatus.argtypes = [POINTER(RikResult), c_void_p, POINTER(c_uint8), POINTER(c_uint8)]
202
+ self._dll.RikReader_GetTransparentModeStatus.restype = None
203
+
204
+ # Library info function (extern "C")
205
+ from reader_integration_kit.structures.library_info import LibraryInfo
206
+ self._dll.Rik_BuildLibraryInfo.argtypes = []
207
+ self._dll.Rik_BuildLibraryInfo.restype = LibraryInfo
208
+
209
+ # Reader discovery functions
210
+ self._dll.Rik_DiscoverUsbReaders.argtypes = [POINTER(RikResult), POINTER(ReaderDefinition), POINTER(c_size_t)]
211
+ self._dll.Rik_DiscoverUsbReaders.restype = None
212
+
213
+
214
+ class ReaderHandle:
215
+ """Represents an opaque handle to an reader instance."""
216
+
217
+ def __init__(self, handle: c_void_p):
218
+ # Always store as c_void_p to guarantee correct type for is_valid comparison
219
+ self._handle = ctypes.c_void_p(handle if isinstance(handle, int) else getattr(handle, 'value', handle))
220
+
221
+ @property
222
+ def value(self) -> c_void_p:
223
+ """Get the raw handle value."""
224
+ return self._handle
225
+
226
+ @property
227
+ def is_valid(self) -> bool:
228
+ """Check if the handle is valid (not None/null)."""
229
+ return self._handle is not None and self._handle.value is not None
230
+
231
+ def __bool__(self) -> bool:
232
+ return self.is_valid
233
+
234
+
235
+ class AbstractReader:
236
+ """
237
+ Base class for Reader instances.
238
+ Provides common functionality for all reader types.
239
+ """
240
+
241
+ def __init__(self, handle: ReaderHandle, native_lib: _NativeLibrary):
242
+ """
243
+ Initialize a new instance of the AbstractReader class.
244
+
245
+ Args:
246
+ handle: The reader handle.
247
+ native_lib: The native library instance.
248
+ """
249
+ self._native_lib = native_lib
250
+ self._handle: Optional[ReaderHandle] = handle
251
+ self._disposed = False
252
+
253
+ if not self._handle.is_valid:
254
+ raise RuntimeError("Failed to create reader instance: invalid handle")
255
+
256
+ @property
257
+ def handle(self) -> ReaderHandle:
258
+ """Get the reader handle for this instance."""
259
+ return self._handle
260
+
261
+ def _throw_if_disposed(self):
262
+ """Throw an exception if the object has been disposed."""
263
+ if self._disposed:
264
+ raise RuntimeError("AbstractReader instance has been disposed")
265
+
266
+ def __enter__(self):
267
+ """Context manager entry."""
268
+ return self
269
+
270
+ def __exit__(self, exc_type, exc_val, exc_tb):
271
+ """Context manager exit - automatically dispose."""
272
+ self.dispose()
273
+
274
+ def dispose(self):
275
+ """Dispose of the reader instance, destroying the native handle."""
276
+ if not self._disposed and self._handle is not None and self._handle.is_valid:
277
+ try:
278
+ # Unsubscribe all active credential callbacks before closing.
279
+ if hasattr(self, '_credential_callbacks'):
280
+ for sub_id in list(self._credential_callbacks.keys()):
281
+ try:
282
+ result = RikResult()
283
+ self._native_lib._dll.RikReader_UnsubscribeCredentialCallback(
284
+ byref(result), self._handle.value, c_uint32(sub_id))
285
+ except Exception:
286
+ pass
287
+ self._credential_callbacks.clear()
288
+
289
+ result = RikResult()
290
+ self._native_lib._dll.Rik_Close(byref(result), self._handle.value)
291
+ ReaderException.raise_if_error(result)
292
+ except Exception:
293
+ # Optionally log or handle native errors
294
+ pass
295
+ finally:
296
+ self._handle = None
297
+ self._disposed = True
298
+
299
+ def __del__(self):
300
+ """Destructor - ensure cleanup."""
301
+ if hasattr(self, '_disposed') and not self._disposed:
302
+ self.dispose()
303
+
304
+ def init(self):
305
+ """Initialize the reader (populates metadata, etc.)."""
306
+ self._throw_if_disposed()
307
+ result = RikResult()
308
+ self._native_lib._dll.Rik_Init(byref(result), self._handle.value)
309
+ ReaderException.raise_if_error(result)
310
+
311
+ def refresh_metadata(self):
312
+ """Refresh metadata from device."""
313
+ self._throw_if_disposed()
314
+ result = RikResult()
315
+ self._native_lib._dll.Rik_RefreshMetadata(byref(result), self._handle.value)
316
+ ReaderException.raise_if_error(result)
317
+
318
+ def get_metadata(self, force_refresh: bool = False) -> dict:
319
+ """
320
+ Get metadata as a dictionary.
321
+ Returns a fully populated dictionary with all available metadata fields.
322
+ Only fields that are present (Has* flag is True) will be included.
323
+
324
+ Args:
325
+ force_refresh: If True, forces a refresh from the device even if metadata
326
+ has already been populated. If False (default), metadata is
327
+ populated lazily on first access.
328
+
329
+ Returns:
330
+ Dictionary containing all available metadata fields.
331
+ """
332
+ self._throw_if_disposed()
333
+ from reader_integration_kit.structures.reader_metadata_struct import ReaderMetadataStruct
334
+
335
+ # Create the struct to receive the data
336
+ metadata_struct = ReaderMetadataStruct()
337
+
338
+ # Call the native function to populate the struct
339
+ result = RikResult()
340
+ self._native_lib._dll.Rik_GetMetadataStruct(byref(result), self._handle.value, byref(metadata_struct), force_refresh)
341
+ ReaderException.raise_if_error(result)
342
+
343
+ # Convert the struct to a dictionary
344
+ return metadata_struct.to_dict()
345
+
346
+ @staticmethod
347
+ def get_library_info() -> dict:
348
+ """
349
+ Get library information including version and build metadata.
350
+ This is a static method that does not require an reader instance.
351
+
352
+ Returns:
353
+ Dictionary containing library information (name, version, build date, etc.).
354
+ """
355
+ from reader_integration_kit.structures.library_info import LibraryInfo
356
+
357
+ # Get the native library instance to access the DLL
358
+ native_lib = _NativeLibrary()
359
+
360
+ # Call Rik_BuildLibraryInfo (extern "C" function)
361
+ library_info = native_lib._dll.Rik_BuildLibraryInfo()
362
+
363
+ # Convert to dictionary
364
+ return library_info.to_dict()
365
+
366
+
367
+ class Reader(AbstractReader):
368
+ """
369
+ A class that encapsulates a WaveID reader handle and provides instance methods for WaveID operations.
370
+ The handle is automatically created in the constructor and destroyed when the object is disposed.
371
+ """
372
+
373
+ def __init__(self, reader_definition: ReaderDefinition, retry_count: int = 3):
374
+ """
375
+ Initialize a new instance of the Reader class with the specified reader definition.
376
+
377
+ Args:
378
+ reader_definition: The reader definition to use for creating the reader instance.
379
+ retry_count: The number of retries to attempt when creating the reader instance. Default is 3.
380
+ """
381
+ native_lib = _NativeLibrary()
382
+
383
+ # Create reader handle
384
+ result = RikResult()
385
+ handle_ptr = native_lib._dll.RikReader_Open(
386
+ byref(result), byref(reader_definition), c_int(retry_count)
387
+ )
388
+ ReaderException.raise_if_error(result)
389
+
390
+ if handle_ptr is None:
391
+ raise RuntimeError("Failed to create WaveID reader instance: returned None")
392
+
393
+ handle = ReaderHandle(handle_ptr)
394
+
395
+ self._credential_callbacks = {}
396
+ super().__init__(handle, native_lib)
397
+
398
+ def beep(self, beep_count: int, duration: BeepDuration) -> None:
399
+ """Beep the reader."""
400
+ self._throw_if_disposed()
401
+ duration_value = duration.value if hasattr(duration, 'value') else duration
402
+ result = RikResult()
403
+ self._native_lib._dll.RikReader_Beep(
404
+ byref(result), self._handle.value, c_uint8(beep_count), c_uint8(duration_value)
405
+ )
406
+ ReaderException.raise_if_error(result)
407
+
408
+ def get_beeper_volume(self) -> BeepVolume:
409
+ """Get the beeper volume."""
410
+ self._throw_if_disposed()
411
+ volume = c_uint8()
412
+ result = RikResult()
413
+ self._native_lib._dll.RikReader_GetBeeperVolume(
414
+ byref(result), self._handle.value, byref(volume)
415
+ )
416
+ ReaderException.raise_if_error(result)
417
+ return BeepVolume(volume.value)
418
+
419
+ def set_beeper_volume(self, volume: BeepVolume) -> None:
420
+ """Set the beeper volume."""
421
+ self._throw_if_disposed()
422
+ volume_value = volume.value if hasattr(volume, 'value') else volume
423
+ result = RikResult()
424
+ self._native_lib._dll.RikReader_SetBeeperVolume(
425
+ byref(result), self._handle.value, c_uint8(volume_value)
426
+ )
427
+ ReaderException.raise_if_error(result)
428
+
429
+ def get_module_state(self, module_id: ReaderModuleId) -> ReaderModuleState:
430
+ """
431
+ Get the module state for the specified module.
432
+
433
+ Args:
434
+ module_id: The module ID to query.
435
+
436
+ Returns:
437
+ The current state of the specified module.
438
+ """
439
+ self._throw_if_disposed()
440
+ module_id_value = module_id.value if hasattr(module_id, 'value') else module_id
441
+ state = c_uint()
442
+ result = RikResult()
443
+ self._native_lib._dll.RikReader_GetModuleState(
444
+ byref(result), self._handle.value, c_uint8(module_id_value), byref(state)
445
+ )
446
+ ReaderException.raise_if_error(result)
447
+
448
+ # Try to convert to enum, but handle unexpected values gracefully
449
+ try:
450
+ return ReaderModuleState(state.value)
451
+ except ValueError:
452
+ # If the value isn't in the enum, raise a more helpful error
453
+ raise ValueError(
454
+ f"Received unexpected module state value: {state.value} (0x{state.value:X}). "
455
+ f"Valid values are: {[f'{s.name}={s.value} (0x{s.value:X})' for s in ReaderModuleState]}"
456
+ )
457
+
458
+ def set_module_state(self, module_id: ReaderModuleId, state: ReaderModuleState) -> None:
459
+ """
460
+ Set the module state for the specified module.
461
+
462
+ Args:
463
+ module_id: The module ID to configure.
464
+ state: The desired state for the module.
465
+ """
466
+ self._throw_if_disposed()
467
+ module_id_value = module_id.value if hasattr(module_id, 'value') else module_id
468
+ state_value = state.value if hasattr(state, 'value') else state
469
+ result = RikResult()
470
+ self._native_lib._dll.RikReader_SetModuleState(
471
+ byref(result), self._handle.value, c_uint8(module_id_value), c_uint(state_value)
472
+ )
473
+ ReaderException.raise_if_error(result)
474
+
475
+ def get_card_data(self) -> 'CardData':
476
+ """
477
+ Get card data from the reader.
478
+ Returns:
479
+ CardData: The card data read from the reader.
480
+ Raises:
481
+ ReaderException: If the operation fails or no valid card data is available.
482
+ """
483
+ self._throw_if_disposed()
484
+ from reader_integration_kit.structures.card_data import CardData
485
+
486
+ # Allocate buffer for card data (32 bytes)
487
+ buffer = (ctypes.c_uint8 * 32)()
488
+ bit_count = ctypes.c_uint32()
489
+ result = RikResult()
490
+ self._native_lib._dll.RikReader_GetCardData(
491
+ byref(result), self._handle.value, buffer, ctypes.c_size_t(32), byref(bit_count)
492
+ )
493
+ ReaderException.raise_if_error(result)
494
+ # Create CardData and populate it
495
+ card_data = CardData()
496
+ for i in range(32):
497
+ card_data.data[i] = buffer[i]
498
+ card_data.bit_count = bit_count.value
499
+ return card_data
500
+
501
+ def get_reader_configuration(self, configuration_number: int) -> tuple[ReaderConfigurationStruct, ExtendedConfiguration]:
502
+ """Get the WaveID reader configuration and Extended configuration for the specified configuration number."""
503
+ self._throw_if_disposed()
504
+ config = ReaderConfigurationStruct()
505
+ extended_config = ExtendedConfiguration()
506
+ result = RikResult()
507
+ self._native_lib._dll.RikReader_GetReaderConfiguration(
508
+ byref(result), self._handle.value, c_uint8(configuration_number), byref(config), byref(extended_config)
509
+ )
510
+ ReaderException.raise_if_error(result)
511
+ return config, extended_config
512
+
513
+ def set_reader_configuration(self, configuration_number: int, config: ReaderConfigurationStruct, extended_config: ExtendedConfiguration, hash_data: HashData) -> None:
514
+ """Set the WaveID reader configuration and Extended configuration for the specified configuration number."""
515
+ self._throw_if_disposed()
516
+ result = RikResult()
517
+ self._native_lib._dll.RikReader_SetReaderConfiguration(
518
+ byref(result), self._handle.value, c_uint8(configuration_number), byref(config), byref(extended_config), byref(hash_data)
519
+ )
520
+ ReaderException.raise_if_error(result)
521
+
522
+ def get_led_configuration(self, configuration_number: int):
523
+ """Get the LED configuration for the specified configuration number."""
524
+ self._throw_if_disposed()
525
+ from reader_integration_kit.structures.led_configuration import LedConfiguration
526
+ led_config = LedConfiguration()
527
+ result = RikResult()
528
+ self._native_lib._dll.RikReader_GetLedConfiguration(
529
+ byref(result), self._handle.value, c_uint8(configuration_number), byref(led_config)
530
+ )
531
+ ReaderException.raise_if_error(result)
532
+ return led_config
533
+
534
+ def set_led_configuration(self, configuration_number: int, led_configuration):
535
+ """Set the LED configuration for the specified configuration number."""
536
+ self._throw_if_disposed()
537
+ result = RikResult()
538
+ self._native_lib._dll.RikReader_SetLedConfiguration(
539
+ byref(result), self._handle.value, c_uint8(configuration_number), byref(led_configuration)
540
+ )
541
+ ReaderException.raise_if_error(result)
542
+
543
+ def enable_keystroking(self, enable: bool) -> None:
544
+ """Enable or disable key stroking."""
545
+ self._throw_if_disposed()
546
+ result = RikResult()
547
+ self._native_lib._dll.RikReader_EnableKeystroking(
548
+ byref(result), self._handle.value, c_bool(enable)
549
+ )
550
+ ReaderException.raise_if_error(result)
551
+
552
+ def get_luid(self) -> LuidResponseInformation:
553
+ """
554
+ Get Luid information from the reader.
555
+
556
+ Returns:
557
+ LuidResponseInformation: Structure containing Luid, application version, and bootloader version.
558
+ """
559
+ self._throw_if_disposed()
560
+ luid_info = LuidResponseInformation()
561
+ result = RikResult()
562
+ self._native_lib._dll.RikReader_GetLuid(
563
+ byref(result), self._handle.value, byref(luid_info)
564
+ )
565
+ ReaderException.raise_if_error(result)
566
+ return luid_info
567
+
568
+ def set_luid(self, luid: int) -> None:
569
+ """
570
+ Set Luid on the reader.
571
+
572
+ Args:
573
+ luid: The Luid value to set.
574
+ """
575
+ self._throw_if_disposed()
576
+ result = RikResult()
577
+ self._native_lib._dll.RikReader_SetLuid(
578
+ byref(result), self._handle.value, c_uint16(luid)
579
+ )
580
+ ReaderException.raise_if_error(result)
581
+
582
+ def write_user_defaults_to_reader(self) -> None:
583
+ """Write user defaults to reader (copies active flash configuration to stored flash)."""
584
+ self._throw_if_disposed()
585
+ result = RikResult()
586
+ self._native_lib._dll.RikReader_WriteUserDefaultsToReader(
587
+ byref(result), self._handle.value
588
+ )
589
+ ReaderException.raise_if_error(result)
590
+
591
+ def reset_reader_configuration(self, checkpoint_type: CheckpointType) -> None:
592
+ """
593
+ Reset reader configuration to a specified checkpoint.
594
+
595
+ Args:
596
+ checkpoint_type: CheckpointType enum value (FACTORY_DEFAULTS=1, USER_SETTINGS=2).
597
+ """
598
+ self._throw_if_disposed()
599
+ result = RikResult()
600
+ self._native_lib._dll.RikReader_ResetReaderConfiguration(
601
+ byref(result), self._handle.value, c_uint8(checkpoint_type.value)
602
+ )
603
+ ReaderException.raise_if_error(result)
604
+
605
+ def get_supported_card_types(self) -> list:
606
+ """Get the supported card types for this reader.
607
+
608
+ Returns:
609
+ list: A list of CardTypeInfo dicts, each containing 'Value', 'Name', and 'EnumName'.
610
+ """
611
+ self._throw_if_disposed()
612
+ from reader_integration_kit.structures.card_type_info import SupportedCardTypesResult
613
+
614
+ result = RikResult()
615
+ supported = SupportedCardTypesResult()
616
+
617
+ self._native_lib._dll.RikReader_GetSupportedCardTypes(
618
+ byref(result), self._handle.value, byref(supported)
619
+ )
620
+ ReaderException.raise_if_error(result)
621
+
622
+ # Convert valid entries to dicts
623
+ return [supported.CardTypes[i].to_dict() for i in range(supported.Count)]
624
+
625
+ def read_ble_configuration_from_reader(self, data_type: BleDataType, file_name: str) -> None:
626
+ """
627
+ Read BLE configuration from the reader and write it to a file.
628
+
629
+ Args:
630
+ data_type: The BLE data type (BleDataType.DATA or BleDataType.KEY).
631
+ file_name: The file path to write the BLE configuration to.
632
+ The file will be in BLE HWG+ format (.hwg+ extension recommended).
633
+
634
+ Raises:
635
+ ReaderException: If the operation fails.
636
+ """
637
+ self._throw_if_disposed()
638
+ data_type_value = data_type.value if hasattr(data_type, 'value') else data_type
639
+
640
+ result = RikResult()
641
+ file_name_bytes = file_name.encode('utf-8')
642
+
643
+ self._native_lib._dll.RikReader_ReadBleConfigurationFromReader(
644
+ byref(result), self._handle.value, c_uint8(data_type_value), file_name_bytes
645
+ )
646
+ ReaderException.raise_if_error(result)
647
+
648
+ def write_ble_configuration_to_reader(self, data_type: BleDataType, file_name: str) -> None:
649
+ """
650
+ Write BLE configuration from a file to the reader.
651
+
652
+ Args:
653
+ data_type: The BLE data type (BleDataType.DATA, BleDataType.KEY, or BleDataType.UNENCRYPTED_KEY).
654
+ file_name: The file path to read the BLE configuration from.
655
+ The file must be in BLE HWG+ format (.hwg+ extension).
656
+
657
+ Raises:
658
+ ReaderException: If the operation fails.
659
+ """
660
+ self._throw_if_disposed()
661
+ data_type_value = data_type.value if hasattr(data_type, 'value') else data_type
662
+
663
+ result = RikResult()
664
+ file_name_bytes = file_name.encode('utf-8')
665
+
666
+ self._native_lib._dll.RikReader_WriteBleConfigurationToReader(
667
+ byref(result), self._handle.value, c_uint8(data_type_value), file_name_bytes
668
+ )
669
+ ReaderException.raise_if_error(result)
670
+
671
+
672
+ def write_hwg_file_to_reader(self, file_name: str) -> None:
673
+ """Write an HWG file to the WaveID reader."""
674
+ self._throw_if_disposed()
675
+ result = RikResult()
676
+ self._native_lib._dll.RikReader_WriteHwgFileToReader(
677
+ byref(result), self._handle.value, c_char_p(file_name.encode('utf-8'))
678
+ )
679
+ ReaderException.raise_if_error(result)
680
+
681
+ def read_hwg_file_from_reader(self, file_name: str, secure_hwg_format: bool = True) -> None:
682
+ """Read an HWG file from the WaveID reader."""
683
+ self._throw_if_disposed()
684
+ result = RikResult()
685
+ self._native_lib._dll.RikReader_ReadHwgFileFromReader(
686
+ byref(result), self._handle.value, c_char_p(file_name.encode('utf-8')), c_bool(secure_hwg_format)
687
+ )
688
+ ReaderException.raise_if_error(result)
689
+
690
+ def write_smart_card_configuration_to_reader(self, file_name: str) -> None:
691
+ """Write smart card configuration to the reader."""
692
+ self._throw_if_disposed()
693
+ result = RikResult()
694
+ self._native_lib._dll.RikReader_WriteSmartCardConfigurationToReader(
695
+ byref(result), self._handle.value, file_name.encode('utf-8')
696
+ )
697
+ ReaderException.raise_if_error(result)
698
+
699
+ def read_smart_card_configuration_from_reader(self, config: SmartCardConfigurationStruct) -> None:
700
+ """Read smart card configuration from the reader."""
701
+ self._throw_if_disposed()
702
+ result = RikResult()
703
+ self._native_lib._dll.RikReader_ReadSmartCardConfigurationFromReader(
704
+ byref(result), self._handle.value, byref(config)
705
+ )
706
+ ReaderException.raise_if_error(result)
707
+
708
+ def on_credential_presented(self, callback: CredentialCallback) -> int:
709
+ """
710
+ Subscribe to card-read events. The callback is invoked on the reader's
711
+ background executor thread whenever a new credential is detected.
712
+
713
+ The event fires on the leading edge of a card presence. If a card remains
714
+ on the reader continuously - including across periods with no active
715
+ subscribers - the event will not re-fire for that card. The event only
716
+ fires again after the card is removed and a new (or the same) card is
717
+ presented.
718
+
719
+ Note:
720
+ If a card is removed and re-placed while no subscribers are active,this may not be detected.
721
+
722
+ Args:
723
+ callback: A Python callable with signature
724
+ ``callback(card_data: CardData) -> None``.
725
+ The callable is kept alive automatically for the duration
726
+ of the subscription.
727
+
728
+ Returns:
729
+ int: A subscription ID. Pass this to unsubscribe_credential_callback()
730
+ to cancel the subscription.
731
+ """
732
+ self._throw_if_disposed()
733
+
734
+ # Build the CFUNCTYPE wrapper and keep it alive on self to prevent GC.
735
+ NativeCredentialCallback = self._native_lib._credential_callback_type
736
+
737
+ def _native_cb(card_data_ptr, bit_count):
738
+ card_data = CardData()
739
+ for i in range(len(card_data.data)):
740
+ card_data.data[i] = card_data_ptr[i]
741
+ card_data.bit_count = bit_count
742
+ callback(card_data)
743
+
744
+ native_cb = NativeCredentialCallback(_native_cb)
745
+
746
+ # Store against subscription ID after we know the ID.
747
+ result = RikResult()
748
+ sub_id = self._native_lib._dll.RikReader_OnCredentialPresented(
749
+ byref(result), self._handle.value, native_cb
750
+ )
751
+ ReaderException.raise_if_error(result)
752
+
753
+ # Keep the ctypes wrapper alive for the duration of the subscription.
754
+ self._credential_callbacks[sub_id] = native_cb
755
+
756
+ return sub_id
757
+
758
+ def unsubscribe_credential_callback(self, subscription_id: int) -> None:
759
+ """
760
+ Cancel a previously registered credential-callback subscription.
761
+ Stops the polling thread if no subscribers remain.
762
+
763
+ Args:
764
+ subscription_id: The ID previously returned by on_credential_presented().
765
+ """
766
+ self._throw_if_disposed()
767
+ result = RikResult()
768
+ self._native_lib._dll.RikReader_UnsubscribeCredentialCallback(
769
+ byref(result), self._handle.value, c_uint32(subscription_id)
770
+ )
771
+ ReaderException.raise_if_error(result)
772
+
773
+ # Release the ctypes wrapper now that the C++ side no longer holds it.
774
+ self._credential_callbacks.pop(subscription_id, None)
775
+
776
+ def enable_transparent_mode(self, enable: bool, write_to_flash: bool) -> None:
777
+ """Enable or disable transparent mode on the reader.
778
+
779
+ Args:
780
+ enable: True to enable transparent mode, False to disable.
781
+ write_to_flash: True for permanently enabling/disabling the transparent mode state irrespective of the reader power cycle. False otherwise.
782
+ """
783
+ self._throw_if_disposed()
784
+ result = RikResult()
785
+ self._native_lib._dll.RikReader_EnableTransparentMode(
786
+ byref(result), self._handle.value,
787
+ c_uint8(1 if enable else 0),
788
+ c_uint8(1 if write_to_flash else 0)
789
+ )
790
+ ReaderException.raise_if_error(result)
791
+
792
+ def get_transparent_mode_status(self) -> tuple[TransparentModeState, TransparentModeStatus]:
793
+ """
794
+ Get the current transparent mode state and status from the reader.
795
+
796
+ Returns:
797
+ tuple:
798
+ TransparentModeState indicates whether transparent mode is currently enabled or disabled in the reader. <br>
799
+ TransparentModeStatus Can be Ready/NotReady. Ready indicates that reader card polling has been terminated. Not ready indicates that the reader is still polling.
800
+ """
801
+ self._throw_if_disposed()
802
+ state_raw = c_uint8(0)
803
+ status_raw = c_uint8(0)
804
+ result = RikResult()
805
+ self._native_lib._dll.RikReader_GetTransparentModeStatus(
806
+ byref(result), self._handle.value, byref(state_raw), byref(status_raw)
807
+ )
808
+ ReaderException.raise_if_error(result)
809
+ return TransparentModeState(state_raw.value), TransparentModeStatus(status_raw.value)
810
+
811
+ class ReaderDiscovery:
812
+ """
813
+ A helper class for discovering connected rfIDEAS readers.
814
+ """
815
+
816
+ @staticmethod
817
+ def discover_usb_readers() -> list:
818
+ """
819
+ Enumerates the available rfIDEAS USB readers.
820
+
821
+ Returns:
822
+ List: ReaderDefinition objects describing each connected USB reader.
823
+
824
+ Raises:
825
+ ReaderException: If device enumeration fails.
826
+ """
827
+
828
+ # Get the native library instance to access the DLL
829
+ native_lib = _NativeLibrary()
830
+
831
+ # First call: get the count of readers
832
+ result = RikResult()
833
+ reader_count = ctypes.c_size_t(0)
834
+ native_lib._dll.Rik_DiscoverUsbReaders(byref(result), None, byref(reader_count))
835
+ ReaderException.raise_if_error(result)
836
+
837
+ # If no readers found, return empty list
838
+ if reader_count.value == 0:
839
+ return []
840
+
841
+ # Second call: get the actual reader information
842
+ readers_array = (ReaderDefinition * reader_count.value)()
843
+ result = RikResult()
844
+ native_lib._dll.Rik_DiscoverUsbReaders(byref(result), readers_array, byref(reader_count))
845
+ ReaderException.raise_if_error(result)
846
+
847
+ # Convert to list of ReaderDefinition objects
848
+ return list(readers_array[:reader_count.value])
849
+
850
+ # Export the main classes
851
+ __all__ = ['AbstractReader', 'Reader', 'ReaderDiscovery', 'ReaderHandle', 'CredentialCallback']
852
+