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