smartx-rfid 0.4.0__py3-none-any.whl → 1.1.6__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.
- smartx_rfid/devices/RFID/R700_IOT/_main.py +35 -0
- smartx_rfid/devices/RFID/R700_IOT/on_event.py +9 -0
- smartx_rfid/devices/RFID/R700_IOT/reader_helpers.py +10 -0
- smartx_rfid/devices/RFID/R700_IOT/write_commands.py +10 -0
- smartx_rfid/devices/RFID/X714/_main.py +41 -1
- smartx_rfid/devices/RFID/X714/on_receive.py +20 -0
- smartx_rfid/devices/RFID/X714/rfid.py +8 -0
- smartx_rfid/devices/RFID/X714/write_commands.py +10 -0
- smartx_rfid/devices/generic/SERIAL/_main.py +14 -5
- smartx_rfid/devices/generic/TCP/_main.py +18 -0
- smartx_rfid/devices/generic/TCP/helpers.py +4 -0
- smartx_rfid/schemas/events.py +6 -0
- smartx_rfid/schemas/tag.py +3 -3
- smartx_rfid/utils/__init__.py +2 -0
- smartx_rfid/utils/logger_manager.py +167 -0
- smartx_rfid/utils/path.py +117 -0
- smartx_rfid/utils/tag_list.py +227 -0
- {smartx_rfid-0.4.0.dist-info → smartx_rfid-1.1.6.dist-info}/METADATA +1 -2
- smartx_rfid-1.1.6.dist-info/RECORD +28 -0
- smartx_rfid-0.4.0.dist-info/RECORD +0 -24
- {smartx_rfid-0.4.0.dist-info → smartx_rfid-1.1.6.dist-info}/WHEEL +0 -0
- {smartx_rfid-0.4.0.dist-info → smartx_rfid-1.1.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -13,6 +13,8 @@ from .reader_config_example import R700_IOT_config_example
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class R700_IOT(OnEvent, ReaderHelpers, WriteCommands):
|
|
16
|
+
"""Impinj R700 RFID reader using HTTP REST API."""
|
|
17
|
+
|
|
16
18
|
def __init__(
|
|
17
19
|
self,
|
|
18
20
|
# READER CONFIG
|
|
@@ -26,6 +28,18 @@ class R700_IOT(OnEvent, ReaderHelpers, WriteCommands):
|
|
|
26
28
|
# Firmware Version
|
|
27
29
|
firmware_version: str = "8.4.1",
|
|
28
30
|
):
|
|
31
|
+
"""
|
|
32
|
+
Create R700 RFID reader.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
reading_config: Configuration for tag reading
|
|
36
|
+
name: Device name
|
|
37
|
+
ip: IP address of reader
|
|
38
|
+
username: Login username
|
|
39
|
+
password: Login password
|
|
40
|
+
start_reading: Start reading tags automatically
|
|
41
|
+
firmware_version: Expected firmware version
|
|
42
|
+
"""
|
|
29
43
|
self.name = name
|
|
30
44
|
self.device_type = "rfid"
|
|
31
45
|
|
|
@@ -62,6 +76,7 @@ class R700_IOT(OnEvent, ReaderHelpers, WriteCommands):
|
|
|
62
76
|
self.on_event = on_event
|
|
63
77
|
|
|
64
78
|
async def disconnect(self):
|
|
79
|
+
"""Safely disconnect from reader and stop reading."""
|
|
65
80
|
"""Desconecta o reader de forma segura."""
|
|
66
81
|
logging.info(f"{self.name} 🔌 Disconnecting reader")
|
|
67
82
|
self._stop_connection = True
|
|
@@ -79,6 +94,7 @@ class R700_IOT(OnEvent, ReaderHelpers, WriteCommands):
|
|
|
79
94
|
self.on_event(self.name, "connection", False)
|
|
80
95
|
|
|
81
96
|
async def connect(self):
|
|
97
|
+
"""Connect to R700 reader and start tag reading."""
|
|
82
98
|
self._stop_connection = False
|
|
83
99
|
while not self._stop_connection:
|
|
84
100
|
async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=10.0) as session:
|
|
@@ -130,6 +146,7 @@ class R700_IOT(OnEvent, ReaderHelpers, WriteCommands):
|
|
|
130
146
|
await self.get_tag_list(session)
|
|
131
147
|
|
|
132
148
|
async def clear_tags(self):
|
|
149
|
+
"""Clear all stored tags from memory."""
|
|
133
150
|
self.tags = {}
|
|
134
151
|
|
|
135
152
|
async def write_gpo(
|
|
@@ -141,6 +158,15 @@ class R700_IOT(OnEvent, ReaderHelpers, WriteCommands):
|
|
|
141
158
|
*args,
|
|
142
159
|
**kwargs,
|
|
143
160
|
):
|
|
161
|
+
"""
|
|
162
|
+
Control GPO (output) pins on reader.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
pin: GPO pin number
|
|
166
|
+
state: Turn pin on (True) or off (False)
|
|
167
|
+
control: Control type (static or pulse)
|
|
168
|
+
time: Pulse duration in milliseconds
|
|
169
|
+
"""
|
|
144
170
|
gpo_command = await self.get_gpo_command(pin=pin, state=state, control=control, time=time)
|
|
145
171
|
try:
|
|
146
172
|
async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=10.0) as session:
|
|
@@ -149,6 +175,15 @@ class R700_IOT(OnEvent, ReaderHelpers, WriteCommands):
|
|
|
149
175
|
logging.warning(f"{self.name} - Failed to set GPO: {e}")
|
|
150
176
|
|
|
151
177
|
def write_epc(self, target_identifier: str | None, target_value: str | None, new_epc: str, password: str):
|
|
178
|
+
"""
|
|
179
|
+
Write new EPC code to RFID tag.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
target_identifier: How to find tag (epc, tid, user)
|
|
183
|
+
target_value: Current tag value to match
|
|
184
|
+
new_epc: New EPC code to write
|
|
185
|
+
password: Tag access password
|
|
186
|
+
"""
|
|
152
187
|
"""
|
|
153
188
|
Writes a new EPC (Electronic Product Code) to RFID tags.
|
|
154
189
|
"""
|
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
class OnEvent:
|
|
2
|
+
"""Handle R700 reader events."""
|
|
3
|
+
|
|
2
4
|
async def on_start(self):
|
|
5
|
+
"""Called when reader starts reading tags."""
|
|
3
6
|
self.is_reading = True
|
|
4
7
|
self.on_event(self.name, "reading", True)
|
|
5
8
|
|
|
6
9
|
async def on_stop(self):
|
|
10
|
+
"""Called when reader stops reading tags."""
|
|
7
11
|
self.is_reading = False
|
|
8
12
|
self.on_event(self.name, "reading", False)
|
|
9
13
|
|
|
10
14
|
async def on_tag(self, tag):
|
|
15
|
+
"""Process detected RFID tag data.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
tag: Raw tag data from reader API
|
|
19
|
+
"""
|
|
11
20
|
current_tag = {
|
|
12
21
|
"epc": tag.get("epcHex"),
|
|
13
22
|
"tid": tag.get("tidHex"),
|
|
@@ -6,7 +6,17 @@ import httpx
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class ReaderHelpers:
|
|
9
|
+
"""Helper methods for R700 reader management."""
|
|
10
|
+
|
|
9
11
|
async def check_firmware_version(self, session: httpx.AsyncClient | None = None):
|
|
12
|
+
"""Check if reader firmware version is compatible.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
session: HTTP session to use
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
bool: True if firmware is compatible
|
|
19
|
+
"""
|
|
10
20
|
endpoint = self.check_version_endpoint
|
|
11
21
|
|
|
12
22
|
try:
|
|
@@ -4,7 +4,17 @@ import httpx
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class WriteCommands:
|
|
7
|
+
"""RFID tag write commands for R700 reader."""
|
|
8
|
+
|
|
7
9
|
async def get_write_cmd(self, tag):
|
|
10
|
+
"""Generate write command for tag programming.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
tag: Tag data with target and new EPC
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
dict: Write command for R700 API
|
|
17
|
+
"""
|
|
8
18
|
identifier = tag.target_identifier
|
|
9
19
|
target = tag.target_value
|
|
10
20
|
epc = tag.new_epc
|
|
@@ -21,6 +21,8 @@ ant_default_config = {
|
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class X714(SerialProtocol, OnReceive, RfidCommands, BLEProtocol, WriteCommands, TCPProtocol):
|
|
24
|
+
"""RFID reader that supports SERIAL, TCP and BLE connections."""
|
|
25
|
+
|
|
24
26
|
def __init__(
|
|
25
27
|
self,
|
|
26
28
|
name: str = "X714",
|
|
@@ -56,6 +58,37 @@ class X714(SerialProtocol, OnReceive, RfidCommands, BLEProtocol, WriteCommands,
|
|
|
56
58
|
read_power: int = 22,
|
|
57
59
|
read_rssi: int = -120,
|
|
58
60
|
):
|
|
61
|
+
"""
|
|
62
|
+
Create X714 RFID reader.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
name: Device name
|
|
66
|
+
connection_type: How to connect (SERIAL, TCP, BLE)
|
|
67
|
+
port: Serial port or AUTO to detect
|
|
68
|
+
baudrate: Serial speed
|
|
69
|
+
vid: USB vendor ID for auto-detect
|
|
70
|
+
pid: USB product ID for auto-detect
|
|
71
|
+
ip: IP address for TCP connection
|
|
72
|
+
tcp_port: TCP port number
|
|
73
|
+
ble_name: Bluetooth device name
|
|
74
|
+
buzzer: Make sound when reading tags
|
|
75
|
+
session: EPC session number (0-3)
|
|
76
|
+
start_reading: Start reading tags automatically
|
|
77
|
+
gpi_start: Use GPI input to start reading
|
|
78
|
+
ignore_read: Skip duplicate tags
|
|
79
|
+
always_send: Send all tag events
|
|
80
|
+
simple_send: Use simple tag format
|
|
81
|
+
keyboard: Act like keyboard input
|
|
82
|
+
decode_gtin: Decode GTIN barcodes
|
|
83
|
+
hotspot: Enable hotspot mode
|
|
84
|
+
reconnection_time: Seconds to wait before reconnect
|
|
85
|
+
prefix: Text to add before tag data
|
|
86
|
+
protected_inventory_password: Password for protected reading
|
|
87
|
+
ant_dict: Custom antenna settings
|
|
88
|
+
active_ant: Which antennas to use
|
|
89
|
+
read_power: TX power in dBm
|
|
90
|
+
read_rssi: RSSI threshold in dBm
|
|
91
|
+
"""
|
|
59
92
|
# Name
|
|
60
93
|
self.name = name
|
|
61
94
|
self.device_type = "rfid"
|
|
@@ -125,6 +158,12 @@ class X714(SerialProtocol, OnReceive, RfidCommands, BLEProtocol, WriteCommands,
|
|
|
125
158
|
self.on_event: Callable = on_event
|
|
126
159
|
|
|
127
160
|
def write(self, to_send, verbose=True):
|
|
161
|
+
"""Send data to reader using current connection type.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
to_send: Data to send
|
|
165
|
+
verbose: Show sent data in logs
|
|
166
|
+
"""
|
|
128
167
|
if self.connection_type == "SERIAL":
|
|
129
168
|
self.write_serial(to_send, verbose)
|
|
130
169
|
elif self.connection_type == "BLE":
|
|
@@ -133,6 +172,7 @@ class X714(SerialProtocol, OnReceive, RfidCommands, BLEProtocol, WriteCommands,
|
|
|
133
172
|
asyncio.create_task(self.write_tcp(to_send, verbose))
|
|
134
173
|
|
|
135
174
|
async def connect(self):
|
|
175
|
+
"""Connect to reader using configured connection type."""
|
|
136
176
|
if self.connection_type == "SERIAL":
|
|
137
177
|
await self.connect_serial()
|
|
138
178
|
elif self.connection_type == "BLE":
|
|
@@ -141,6 +181,6 @@ class X714(SerialProtocol, OnReceive, RfidCommands, BLEProtocol, WriteCommands,
|
|
|
141
181
|
await self.connect_tcp(self.ip, self.tcp_port)
|
|
142
182
|
|
|
143
183
|
def on_connected(self):
|
|
144
|
-
"""
|
|
184
|
+
"""Called when connection is established. Sets up reader."""
|
|
145
185
|
self.config_reader()
|
|
146
186
|
self.on_event(self.name, "connected", True)
|
|
@@ -3,7 +3,15 @@ import logging
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class OnReceive:
|
|
6
|
+
"""Handle incoming data from X714 reader."""
|
|
7
|
+
|
|
6
8
|
def on_receive(self, data, verbose: bool = False):
|
|
9
|
+
"""Process data received from reader.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
data: Raw data from reader
|
|
13
|
+
verbose: Show received data in logs
|
|
14
|
+
"""
|
|
7
15
|
if not isinstance(data, str):
|
|
8
16
|
data = data.decode(errors="ignore")
|
|
9
17
|
data = data.replace("\r", "").replace("\n", "")
|
|
@@ -39,13 +47,25 @@ class OnReceive:
|
|
|
39
47
|
self.on_event(self.name, "tags_cleared", True)
|
|
40
48
|
|
|
41
49
|
def on_start(self):
|
|
50
|
+
"""Called when reader starts reading tags."""
|
|
42
51
|
self.clear_tags()
|
|
43
52
|
self.on_event(self.name, "reading", True)
|
|
44
53
|
|
|
45
54
|
def on_stop(self):
|
|
55
|
+
"""Called when reader stops reading tags."""
|
|
46
56
|
self.on_event(self.name, "reading", False)
|
|
47
57
|
|
|
48
58
|
def on_tag(self, tag: dict):
|
|
59
|
+
"""Process detected RFID tag data.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
tag: Tag information dictionary
|
|
63
|
+
"""
|
|
64
|
+
"""Process detected RFID tag data.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
tag: Tag information dictionary
|
|
68
|
+
"""
|
|
49
69
|
try:
|
|
50
70
|
tag_data = TagSchema(**tag)
|
|
51
71
|
tag = tag_data.model_dump()
|
|
@@ -2,16 +2,22 @@ import asyncio
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
class RfidCommands:
|
|
5
|
+
"""RFID reader control commands for X714."""
|
|
6
|
+
|
|
5
7
|
def start_inventory(self):
|
|
8
|
+
"""Start reading RFID tags."""
|
|
6
9
|
self.write("#READ:ON")
|
|
7
10
|
|
|
8
11
|
def stop_inventory(self):
|
|
12
|
+
"""Stop reading RFID tags."""
|
|
9
13
|
self.write("#READ:OFF")
|
|
10
14
|
|
|
11
15
|
def clear_tags(self):
|
|
16
|
+
"""Clear all stored tags from memory."""
|
|
12
17
|
self.write("#CLEAR")
|
|
13
18
|
|
|
14
19
|
def config_reader(self):
|
|
20
|
+
"""Configure reader settings like antennas, session, etc."""
|
|
15
21
|
set_cmd = "#set_cmd:"
|
|
16
22
|
|
|
17
23
|
# ANTENNAS
|
|
@@ -26,6 +32,8 @@ class RfidCommands:
|
|
|
26
32
|
|
|
27
33
|
# START_READING
|
|
28
34
|
set_cmd += f"|START_READING:{self.start_reading}"
|
|
35
|
+
if self.start_reading:
|
|
36
|
+
self.is_reading = True
|
|
29
37
|
|
|
30
38
|
# GPI_START
|
|
31
39
|
set_cmd += f"|GPI_START:{self.gpi_start}"
|
|
@@ -3,7 +3,17 @@ from smartx_rfid.schemas.tag import WriteTagValidator
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class WriteCommands:
|
|
6
|
+
"""RFID tag write commands for X714."""
|
|
7
|
+
|
|
6
8
|
def write_epc(self, target_identifier: str | None, target_value: str | None, new_epc: str, password: str):
|
|
9
|
+
"""Write new EPC code to RFID tag.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
target_identifier: How to find tag (epc, tid, user)
|
|
13
|
+
target_value: Current tag value to match
|
|
14
|
+
new_epc: New EPC code to write
|
|
15
|
+
password: Tag access password
|
|
16
|
+
"""
|
|
7
17
|
try:
|
|
8
18
|
validated_tag = WriteTagValidator(
|
|
9
19
|
target_identifier=target_identifier,
|
|
@@ -137,13 +137,11 @@ class SERIAL(asyncio.Protocol):
|
|
|
137
137
|
|
|
138
138
|
def write(self, to_send, verbose=True):
|
|
139
139
|
"""
|
|
140
|
-
Send data through
|
|
141
|
-
|
|
142
|
-
Automatically adds newline to strings and calculates CRC16 for bytes.
|
|
140
|
+
Send data through serial port.
|
|
143
141
|
|
|
144
142
|
Args:
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
to_send: Data to send (string or bytes)
|
|
144
|
+
verbose: Show sent data in logs
|
|
147
145
|
"""
|
|
148
146
|
if self.transport:
|
|
149
147
|
if isinstance(to_send, str):
|
|
@@ -167,6 +165,7 @@ class SERIAL(asyncio.Protocol):
|
|
|
167
165
|
logging.warning("❌ Send attempt failed: connection not established.")
|
|
168
166
|
|
|
169
167
|
async def connect(self):
|
|
168
|
+
"""Connect to serial port and keep connection alive."""
|
|
170
169
|
"""
|
|
171
170
|
Establish and maintain serial connection with automatic reconnection.
|
|
172
171
|
|
|
@@ -216,6 +215,16 @@ class SERIAL(asyncio.Protocol):
|
|
|
216
215
|
await asyncio.sleep(3)
|
|
217
216
|
|
|
218
217
|
def crc16(self, data: bytes, poly=0x8408):
|
|
218
|
+
"""
|
|
219
|
+
Calculate CRC16 checksum for data validation.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
data: Input bytes to calculate checksum
|
|
223
|
+
poly: CRC polynomial value
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
int: 16-bit checksum value
|
|
227
|
+
"""
|
|
219
228
|
"""
|
|
220
229
|
Calculate CRC-16/CCITT-FALSE checksum.
|
|
221
230
|
|
|
@@ -7,12 +7,22 @@ from typing import Callable
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class TCP(Helpers):
|
|
10
|
+
"""TCP connection handler for network communication."""
|
|
11
|
+
|
|
10
12
|
def __init__(
|
|
11
13
|
self,
|
|
12
14
|
name: str = "GENERIC_TCP",
|
|
13
15
|
ip: str = "192.168.1.101",
|
|
14
16
|
port: int = 23,
|
|
15
17
|
):
|
|
18
|
+
"""
|
|
19
|
+
Create TCP connection.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
name: Device name
|
|
23
|
+
ip: IP address to connect
|
|
24
|
+
port: TCP port number
|
|
25
|
+
"""
|
|
16
26
|
self.name = name
|
|
17
27
|
self.device_type = "generic"
|
|
18
28
|
|
|
@@ -26,6 +36,7 @@ class TCP(Helpers):
|
|
|
26
36
|
self.on_event: Callable = on_event
|
|
27
37
|
|
|
28
38
|
async def connect(self):
|
|
39
|
+
"""Connect to TCP server and keep connection alive."""
|
|
29
40
|
while True:
|
|
30
41
|
try:
|
|
31
42
|
logging.info(f"Connecting: {self.name} - {self.ip}:{self.port}")
|
|
@@ -59,6 +70,13 @@ class TCP(Helpers):
|
|
|
59
70
|
await asyncio.sleep(3)
|
|
60
71
|
|
|
61
72
|
async def write(self, data: str, verbose=True):
|
|
73
|
+
"""
|
|
74
|
+
Send data through TCP connection.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
data: Text to send
|
|
78
|
+
verbose: Show sent data in logs
|
|
79
|
+
"""
|
|
62
80
|
if self.is_connected and self.writer:
|
|
63
81
|
try:
|
|
64
82
|
data = data + "\n"
|
|
@@ -3,7 +3,10 @@ import logging
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class Helpers:
|
|
6
|
+
"""Helper functions for TCP connection management."""
|
|
7
|
+
|
|
6
8
|
async def monitor_connection(self):
|
|
9
|
+
"""Check if TCP connection is still alive."""
|
|
7
10
|
while self.is_connected:
|
|
8
11
|
await asyncio.sleep(3)
|
|
9
12
|
if (self.writer and self.writer.is_closing()) or (self.reader and self.reader.at_eof()):
|
|
@@ -14,6 +17,7 @@ class Helpers:
|
|
|
14
17
|
await self.write("ping", verbose=False)
|
|
15
18
|
|
|
16
19
|
async def receive_data(self):
|
|
20
|
+
"""Receive and process incoming TCP data."""
|
|
17
21
|
buffer = ""
|
|
18
22
|
try:
|
|
19
23
|
while True:
|
smartx_rfid/schemas/tag.py
CHANGED
|
@@ -5,9 +5,9 @@ from pydantic import BaseModel, Field, field_validator
|
|
|
5
5
|
|
|
6
6
|
class TagSchema(BaseModel):
|
|
7
7
|
epc: str = Field("000000000000000000000001")
|
|
8
|
-
tid: Optional[str | None] = Field(
|
|
9
|
-
ant: Optional[int | None] =
|
|
10
|
-
rssi: Optional[int | None] =
|
|
8
|
+
tid: Optional[str | None] = Field(None)
|
|
9
|
+
ant: Optional[int | None] = None
|
|
10
|
+
rssi: Optional[int | None] = None
|
|
11
11
|
|
|
12
12
|
@field_validator("epc", "tid")
|
|
13
13
|
def validate_epc_length_and_hex(cls, v, field):
|
smartx_rfid/utils/__init__.py
CHANGED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import queue
|
|
4
|
+
import threading
|
|
5
|
+
import sys
|
|
6
|
+
import asyncio
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LoggerManager:
|
|
11
|
+
"""
|
|
12
|
+
Simple plug-and-play daily rotating logger.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
logger_manager = LoggerManager("logs", "myapp", storage_days=7)
|
|
16
|
+
logging.info("App started")
|
|
17
|
+
# Exceptions, even uncaught, are automatically logged
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, log_path: str, base_filename: str, storage_days: int = 7, use_utc: bool = False):
|
|
21
|
+
self.log_path = os.path.abspath(log_path)
|
|
22
|
+
self.base_filename = base_filename
|
|
23
|
+
self.storage_days = storage_days
|
|
24
|
+
self.use_utc = use_utc
|
|
25
|
+
|
|
26
|
+
os.makedirs(self.log_path, exist_ok=True)
|
|
27
|
+
|
|
28
|
+
# Queue and thread for async logging
|
|
29
|
+
self.log_queue: queue.Queue[str] = queue.Queue(maxsize=10_000)
|
|
30
|
+
self.stop_event = threading.Event()
|
|
31
|
+
self.current_date = None
|
|
32
|
+
self.filename = self._get_filename_for_date(self._now().date())
|
|
33
|
+
|
|
34
|
+
self.worker_thread = threading.Thread(target=self._worker, name="LogWriterThread", daemon=True)
|
|
35
|
+
self.worker_thread.start()
|
|
36
|
+
|
|
37
|
+
# Setup logging
|
|
38
|
+
self._setup_logging()
|
|
39
|
+
|
|
40
|
+
# Global exception handlers
|
|
41
|
+
sys.excepthook = self._handle_exception
|
|
42
|
+
try:
|
|
43
|
+
loop = asyncio.get_event_loop()
|
|
44
|
+
loop.set_exception_handler(self._asyncio_exception_handler)
|
|
45
|
+
except RuntimeError:
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
logging.info(f"Logger initialized: {self.filename}")
|
|
49
|
+
|
|
50
|
+
# -------------------
|
|
51
|
+
# Date & filename
|
|
52
|
+
# -------------------
|
|
53
|
+
def _now(self):
|
|
54
|
+
return datetime.now(timezone.utc) if self.use_utc else datetime.now()
|
|
55
|
+
|
|
56
|
+
def _get_filename_for_date(self, date: datetime.date):
|
|
57
|
+
# Date before filename: YYYY-MM-DD_myapp.log
|
|
58
|
+
return os.path.join(self.log_path, f"{date:%Y-%m-%d}_{self.base_filename}.log")
|
|
59
|
+
|
|
60
|
+
# -------------------
|
|
61
|
+
# Worker
|
|
62
|
+
# -------------------
|
|
63
|
+
def _worker(self):
|
|
64
|
+
while not self.stop_event.is_set() or not self.log_queue.empty():
|
|
65
|
+
try:
|
|
66
|
+
msg = self.log_queue.get(timeout=0.5)
|
|
67
|
+
self._write(msg)
|
|
68
|
+
except queue.Empty:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
def _write(self, msg: str):
|
|
72
|
+
today = self._now().date()
|
|
73
|
+
if today != self.current_date:
|
|
74
|
+
self.current_date = today
|
|
75
|
+
self.filename = self._get_filename_for_date(today)
|
|
76
|
+
self._cleanup_old_logs()
|
|
77
|
+
with open(self.filename, "a", encoding="utf-8", errors="replace") as f:
|
|
78
|
+
f.write(msg)
|
|
79
|
+
|
|
80
|
+
# -------------------
|
|
81
|
+
# Cleanup old logs
|
|
82
|
+
# -------------------
|
|
83
|
+
def _cleanup_old_logs(self):
|
|
84
|
+
files = []
|
|
85
|
+
for f in os.listdir(self.log_path):
|
|
86
|
+
if f.endswith(".log") and f.startswith(f"{self.current_date:%Y-%m-%d}"):
|
|
87
|
+
# skip current file
|
|
88
|
+
continue
|
|
89
|
+
if f.startswith("_".join([self.base_filename, ""])): # ignore wrongly named files
|
|
90
|
+
continue
|
|
91
|
+
if f.endswith(".log"):
|
|
92
|
+
try:
|
|
93
|
+
date_str = f.split("_")[0]
|
|
94
|
+
file_date = datetime.strptime(date_str, "%Y-%m-%d").date()
|
|
95
|
+
files.append((file_date, os.path.join(self.log_path, f)))
|
|
96
|
+
except Exception:
|
|
97
|
+
continue
|
|
98
|
+
files.sort(key=lambda x: x[0])
|
|
99
|
+
for _, old_file in files[: -self.storage_days]:
|
|
100
|
+
try:
|
|
101
|
+
os.remove(old_file)
|
|
102
|
+
except Exception:
|
|
103
|
+
pass
|
|
104
|
+
|
|
105
|
+
# -------------------
|
|
106
|
+
# Logging setup
|
|
107
|
+
# -------------------
|
|
108
|
+
def _setup_logging(self):
|
|
109
|
+
logger = logging.getLogger()
|
|
110
|
+
logger.setLevel(logging.INFO)
|
|
111
|
+
|
|
112
|
+
# Remove old handlers
|
|
113
|
+
for handler in logger.handlers[:]:
|
|
114
|
+
logger.removeHandler(handler)
|
|
115
|
+
|
|
116
|
+
# Console
|
|
117
|
+
ch = logging.StreamHandler()
|
|
118
|
+
ch.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
119
|
+
logger.addHandler(ch)
|
|
120
|
+
|
|
121
|
+
# File (async)
|
|
122
|
+
fh = logging.Handler()
|
|
123
|
+
fh.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s"))
|
|
124
|
+
fh.emit = self._enqueue
|
|
125
|
+
logger.addHandler(fh)
|
|
126
|
+
|
|
127
|
+
# -------------------
|
|
128
|
+
# Enqueue log messages
|
|
129
|
+
# -------------------
|
|
130
|
+
def _enqueue(self, record: logging.LogRecord):
|
|
131
|
+
try:
|
|
132
|
+
# Include exception info if present
|
|
133
|
+
msg = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s").format(record)
|
|
134
|
+
if record.exc_info:
|
|
135
|
+
msg += "\n" + logging.Formatter().formatException(record.exc_info)
|
|
136
|
+
msg += "\n"
|
|
137
|
+
self.log_queue.put_nowait(msg)
|
|
138
|
+
except queue.Full:
|
|
139
|
+
pass
|
|
140
|
+
except Exception:
|
|
141
|
+
self.handleError(record)
|
|
142
|
+
|
|
143
|
+
# -------------------
|
|
144
|
+
# Exception hooks
|
|
145
|
+
# -------------------
|
|
146
|
+
@staticmethod
|
|
147
|
+
def _handle_exception(exc_type, exc_value, exc_traceback):
|
|
148
|
+
if issubclass(exc_type, KeyboardInterrupt):
|
|
149
|
+
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
|
150
|
+
return
|
|
151
|
+
logging.getLogger().error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
|
|
152
|
+
|
|
153
|
+
@staticmethod
|
|
154
|
+
def _asyncio_exception_handler(loop, context):
|
|
155
|
+
exception = context.get("exception")
|
|
156
|
+
logger = logging.getLogger()
|
|
157
|
+
if exception:
|
|
158
|
+
logger.error("Unhandled asyncio exception", exc_info=exception)
|
|
159
|
+
else:
|
|
160
|
+
logger.error("Unhandled asyncio error: %s", context.get("message"))
|
|
161
|
+
|
|
162
|
+
# -------------------
|
|
163
|
+
# Close
|
|
164
|
+
# -------------------
|
|
165
|
+
def close(self):
|
|
166
|
+
self.stop_event.set()
|
|
167
|
+
self.worker_thread.join(timeout=3)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_frozen_path(relative_path: str) -> Path:
|
|
8
|
+
"""
|
|
9
|
+
Return the absolute path to a file or directory, taking into account
|
|
10
|
+
whether the application is running from source or as a frozen executable.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
relative_path: Relative path to the file or directory.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
The resolved absolute path.
|
|
17
|
+
"""
|
|
18
|
+
if getattr(sys, "frozen", False):
|
|
19
|
+
base_path = Path(sys._MEIPASS)
|
|
20
|
+
else:
|
|
21
|
+
base_path = Path(sys.argv[0]).resolve().parent
|
|
22
|
+
|
|
23
|
+
return base_path / relative_path
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_prefix_from_path(current_file: str, base_dir: str = "routers") -> str:
|
|
27
|
+
"""
|
|
28
|
+
Automatically generate an API router prefix based on the folder structure
|
|
29
|
+
starting from the given base directory.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
current_file: Usually __file__.
|
|
33
|
+
base_dir: Root directory name for routers.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The router prefix (e.g. "/rfid/get").
|
|
37
|
+
"""
|
|
38
|
+
path = Path(current_file).resolve()
|
|
39
|
+
parts = path.parts
|
|
40
|
+
|
|
41
|
+
if base_dir not in parts:
|
|
42
|
+
raise ValueError(f"'{base_dir}' not found in path: {path}")
|
|
43
|
+
|
|
44
|
+
base_index = parts.index(base_dir)
|
|
45
|
+
prefix_parts = parts[base_index + 1 :]
|
|
46
|
+
prefix_string = "/" + "/".join(prefix_parts)
|
|
47
|
+
|
|
48
|
+
return prefix_string.replace(".py", "")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def include_all_routers(current_path: str, app) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Recursively discover and include all API routers found in the given path.
|
|
54
|
+
"""
|
|
55
|
+
routes_path = get_frozen_path(current_path)
|
|
56
|
+
|
|
57
|
+
for entry in Path(routes_path).iterdir():
|
|
58
|
+
if entry.is_dir() and entry.name != "__pycache__":
|
|
59
|
+
include_all_routers(str(Path(current_path) / entry.name), app)
|
|
60
|
+
|
|
61
|
+
elif entry.is_file() and entry.suffix == ".py" and entry.name != "__init__.py":
|
|
62
|
+
module_name = entry.stem
|
|
63
|
+
file_path = entry
|
|
64
|
+
|
|
65
|
+
spec = importlib.util.spec_from_file_location(f"app.routers.{module_name}", str(file_path))
|
|
66
|
+
module = importlib.util.module_from_spec(spec)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
spec.loader.exec_module(module)
|
|
70
|
+
|
|
71
|
+
if hasattr(module, "router"):
|
|
72
|
+
prefix = getattr(module.router, "prefix", "") or ""
|
|
73
|
+
app.include_router(
|
|
74
|
+
module.router,
|
|
75
|
+
include_in_schema=prefix.startswith("/api"),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
routers_dir = Path(routes_path).resolve()
|
|
80
|
+
relative_path = file_path.resolve().relative_to(routers_dir.parent)
|
|
81
|
+
except Exception:
|
|
82
|
+
relative_path = file_path.name
|
|
83
|
+
|
|
84
|
+
logging.info(f"✅ Route loaded: {relative_path}")
|
|
85
|
+
|
|
86
|
+
else:
|
|
87
|
+
logging.warning(f"⚠️ File {current_path} does not define a 'router'")
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logging.error(f"❌ Error loading {current_path}: {e}", exc_info=True)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def load_file(file_path: str | Path) -> str:
|
|
94
|
+
"""
|
|
95
|
+
Load the content of a file as a string.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
file_path (str | Path): Path to the file.
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
str: The content of the file, or a default message if loading fails.
|
|
102
|
+
"""
|
|
103
|
+
file_path = Path(file_path)
|
|
104
|
+
|
|
105
|
+
if not file_path.exists():
|
|
106
|
+
logging.warning(f"File not found: {file_path}. Using default content.")
|
|
107
|
+
return "File not found."
|
|
108
|
+
|
|
109
|
+
if not file_path.is_file():
|
|
110
|
+
logging.warning(f"Path is not a file: {file_path}. Using default content.")
|
|
111
|
+
return "Invalid file path."
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
return file_path.read_text(encoding="utf-8")
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logging.error(f"Error reading file {file_path}: {e}", exc_info=True)
|
|
117
|
+
return "Error loading file content."
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
from typing import Literal, Dict, Any, Optional, Tuple
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from threading import Lock
|
|
4
|
+
import logging
|
|
5
|
+
from pyepc import SGTIN
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TagList:
|
|
9
|
+
"""
|
|
10
|
+
Thread-safe container for RFID tags.
|
|
11
|
+
|
|
12
|
+
Tags are stored as dictionaries to allow flexible schemas per client.
|
|
13
|
+
Each tag is uniquely identified by either EPC or TID.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, unique_identifier: Literal["epc", "tid"] = "epc"):
|
|
17
|
+
"""
|
|
18
|
+
Initialize the tag list.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
unique_identifier: Field used as the unique tag identifier ("epc" or "tid").
|
|
22
|
+
"""
|
|
23
|
+
if unique_identifier not in ("epc", "tid"):
|
|
24
|
+
raise ValueError("unique_identifier must be 'epc' or 'tid'")
|
|
25
|
+
|
|
26
|
+
self.unique_identifier = unique_identifier
|
|
27
|
+
self._tags: Dict[str, Dict[str, Any]] = {}
|
|
28
|
+
self._lock = Lock()
|
|
29
|
+
|
|
30
|
+
def __len__(self) -> int:
|
|
31
|
+
"""
|
|
32
|
+
Return the number of stored tags.
|
|
33
|
+
"""
|
|
34
|
+
return len(self._tags)
|
|
35
|
+
|
|
36
|
+
def __contains__(self, identifier: str) -> bool:
|
|
37
|
+
"""
|
|
38
|
+
Check if a tag identifier exists in the list.
|
|
39
|
+
"""
|
|
40
|
+
return identifier in self._tags
|
|
41
|
+
|
|
42
|
+
def __repr__(self) -> str:
|
|
43
|
+
"""
|
|
44
|
+
Return a string representation of the stored tags.
|
|
45
|
+
"""
|
|
46
|
+
return repr(self.get_all())
|
|
47
|
+
|
|
48
|
+
def add(self, tag: Dict[str, Any], device: str = "Unknown") -> Tuple[bool, Optional[Dict[str, Any]]]:
|
|
49
|
+
"""
|
|
50
|
+
Add or update a tag.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
(True, tag_dict) if the tag is new;
|
|
54
|
+
(False, tag_dict) if the tag already exists;
|
|
55
|
+
(False, None) if an error occurs;
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
identifier_value = tag.get(self.unique_identifier)
|
|
59
|
+
if not identifier_value:
|
|
60
|
+
raise ValueError(f"Tag missing '{self.unique_identifier}'")
|
|
61
|
+
|
|
62
|
+
with self._lock:
|
|
63
|
+
if identifier_value not in self._tags:
|
|
64
|
+
stored = self._new_tag(tag, device)
|
|
65
|
+
return True, stored
|
|
66
|
+
else:
|
|
67
|
+
stored = self._existing_tag(tag)
|
|
68
|
+
return False, stored
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logging.warning(f"[ TAG ERROR ] {e}")
|
|
72
|
+
return False, None
|
|
73
|
+
|
|
74
|
+
def _new_tag(self, tag: Dict[str, Any], device: str) -> Dict[str, Any]:
|
|
75
|
+
"""
|
|
76
|
+
Create and store a new tag.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
tag: Raw tag data.
|
|
80
|
+
device: Source device identifier.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
The stored tag dictionary.
|
|
84
|
+
"""
|
|
85
|
+
now = datetime.now()
|
|
86
|
+
|
|
87
|
+
try:
|
|
88
|
+
gtin = SGTIN.decode(tag.get("epc")).gtin
|
|
89
|
+
except Exception:
|
|
90
|
+
gtin = None
|
|
91
|
+
|
|
92
|
+
stored_tag = {
|
|
93
|
+
**tag,
|
|
94
|
+
"device": device,
|
|
95
|
+
"timestamp": now,
|
|
96
|
+
"count": 1,
|
|
97
|
+
"gtin": gtin,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
self._tags[tag[self.unique_identifier]] = stored_tag
|
|
101
|
+
|
|
102
|
+
return stored_tag
|
|
103
|
+
|
|
104
|
+
def _existing_tag(self, tag: Dict[str, Any]) -> Dict[str, Any]:
|
|
105
|
+
"""
|
|
106
|
+
Update an existing tag.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
tag: Incoming tag data.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
The updated stored tag.
|
|
113
|
+
"""
|
|
114
|
+
current = self._tags[tag[self.unique_identifier]]
|
|
115
|
+
|
|
116
|
+
current["count"] += 1
|
|
117
|
+
current["timestamp"] = datetime.now()
|
|
118
|
+
|
|
119
|
+
new_rssi = tag.get("rssi")
|
|
120
|
+
if new_rssi is not None:
|
|
121
|
+
old_rssi = current.get("rssi")
|
|
122
|
+
if old_rssi is None or abs(new_rssi) < abs(old_rssi):
|
|
123
|
+
current["rssi"] = new_rssi
|
|
124
|
+
current["ant"] = tag.get("ant")
|
|
125
|
+
|
|
126
|
+
return current
|
|
127
|
+
|
|
128
|
+
def get_all(self) -> list[Dict[str, Any]]:
|
|
129
|
+
"""
|
|
130
|
+
Retrieve all stored tags.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
A list of tag dictionaries.
|
|
134
|
+
"""
|
|
135
|
+
with self._lock:
|
|
136
|
+
return list(self._tags.values())
|
|
137
|
+
|
|
138
|
+
def get_by_identifier(self, identifier_value: str, identifier_type: str = "epc") -> Optional[Dict[str, Any]]:
|
|
139
|
+
"""
|
|
140
|
+
Retrieve a tag by its identifier.
|
|
141
|
+
Args:
|
|
142
|
+
identifier_value: The value of the identifier (EPC or TID).
|
|
143
|
+
identifier_type: The type of identifier ("epc" or "tid").
|
|
144
|
+
Returns:
|
|
145
|
+
The tag dictionary if found, otherwise None.
|
|
146
|
+
"""
|
|
147
|
+
if identifier_type not in ("epc", "tid"):
|
|
148
|
+
identifier_type = "epc"
|
|
149
|
+
|
|
150
|
+
if self.unique_identifier == identifier_type:
|
|
151
|
+
return self._tags.get(identifier_value)
|
|
152
|
+
|
|
153
|
+
for tag in self._tags.values():
|
|
154
|
+
if tag.get(identifier_type) == identifier_value:
|
|
155
|
+
return tag
|
|
156
|
+
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
def clear(self) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Remove all stored tags.
|
|
162
|
+
"""
|
|
163
|
+
with self._lock:
|
|
164
|
+
self._tags.clear()
|
|
165
|
+
|
|
166
|
+
def remove_tags_before_timestamp(self, timestamp: datetime) -> None:
|
|
167
|
+
"""
|
|
168
|
+
Remove tags older than a given timestamp.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
timestamp: Minimum timestamp to keep.
|
|
172
|
+
"""
|
|
173
|
+
with self._lock:
|
|
174
|
+
self._tags = {k: v for k, v in self._tags.items() if v.get("timestamp") and v["timestamp"] >= timestamp}
|
|
175
|
+
|
|
176
|
+
def remove_tags_by_device(self, device: str) -> None:
|
|
177
|
+
"""
|
|
178
|
+
Remove all tags associated with a specific device.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
device: Device identifier.
|
|
182
|
+
"""
|
|
183
|
+
with self._lock:
|
|
184
|
+
self._tags = {k: v for k, v in self._tags.items() if v.get("device") != device}
|
|
185
|
+
|
|
186
|
+
def get_tid_from_epc(self, epc: str) -> Optional[str]:
|
|
187
|
+
"""
|
|
188
|
+
Retrieve the TID associated with a given EPC.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
epc: EPC value.
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
The TID if found, otherwise None.
|
|
195
|
+
"""
|
|
196
|
+
with self._lock:
|
|
197
|
+
tag = self._tags.get(epc)
|
|
198
|
+
if tag:
|
|
199
|
+
return tag.get("tid")
|
|
200
|
+
return None
|
|
201
|
+
|
|
202
|
+
def get_epcs(self) -> list[str]:
|
|
203
|
+
"""
|
|
204
|
+
Retrieve a list of all stored EPCs.
|
|
205
|
+
|
|
206
|
+
Returns:
|
|
207
|
+
A list of EPC strings.
|
|
208
|
+
"""
|
|
209
|
+
with self._lock:
|
|
210
|
+
return [tag["epc"] for tag in self._tags.values() if "epc" in tag]
|
|
211
|
+
|
|
212
|
+
def get_gtin_counts(self) -> Dict[str, int]:
|
|
213
|
+
"""
|
|
214
|
+
Retrieve counts of tags grouped by GTIN.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
A dictionary mapping GTINs to their respective counts.
|
|
218
|
+
"""
|
|
219
|
+
gtin_counts: Dict[str, int] = {}
|
|
220
|
+
with self._lock:
|
|
221
|
+
for tag in self._tags.values():
|
|
222
|
+
gtin = tag.get("gtin")
|
|
223
|
+
if gtin is None:
|
|
224
|
+
gtin = "UNKNOWN"
|
|
225
|
+
gtin_counts[gtin] = gtin_counts.get(gtin, 0) + 1
|
|
226
|
+
|
|
227
|
+
return gtin_counts
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: smartx-rfid
|
|
3
|
-
Version:
|
|
3
|
+
Version: 1.1.6
|
|
4
4
|
Summary: SmartX RFID library
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -23,7 +23,6 @@ Requires-Dist: pydantic (>=2.12.5,<3.0.0)
|
|
|
23
23
|
Requires-Dist: pyepc (==0.5.0)
|
|
24
24
|
Requires-Dist: pyserial (==3.5)
|
|
25
25
|
Requires-Dist: pyserial-asyncio (==0.6)
|
|
26
|
-
Requires-Dist: ruff (>=0.14.11,<0.15.0)
|
|
27
26
|
Project-URL: Documentation, https://github.com/ghpascon/smartx_rfid#readme
|
|
28
27
|
Project-URL: Homepage, https://github.com/ghpascon/smartx_rfid
|
|
29
28
|
Project-URL: Repository, https://github.com/ghpascon/smartx_rfid
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
smartx_rfid/__init__.py,sha256=XCdKdP8o5LT3VjJtWOuFHJzztc0FXt3Ms89qsx0p6sM,282
|
|
2
|
+
smartx_rfid/devices/RFID/R700_IOT/_main.py,sha256=VL2yDBCXp8Avb3PXKZyBNwtBRhwlLQb5TSs3aTUeC6w,7458
|
|
3
|
+
smartx_rfid/devices/RFID/R700_IOT/on_event.py,sha256=bG_33gY2ZHXquUbezXyMN0dyHO9SBwc36DPTc8iXc1U,799
|
|
4
|
+
smartx_rfid/devices/RFID/R700_IOT/reader_config_example.py,sha256=oi4vg3_zjYFDUeq1Y_d5InUx_Rq5M2CB5RDVkK3WCgU,2264
|
|
5
|
+
smartx_rfid/devices/RFID/R700_IOT/reader_helpers.py,sha256=8CvvYeKGplWqceVH5mEMN2_lA2Qgw7Wo1jpYuXLQlXo,5940
|
|
6
|
+
smartx_rfid/devices/RFID/R700_IOT/write_commands.py,sha256=GMROeq_daTScj4NZygF45UM9t0Pr3E6ugmrb7OlF73w,2284
|
|
7
|
+
smartx_rfid/devices/RFID/X714/_main.py,sha256=LP1X_w6Sy9m5kHLvbaRUNZbzQJU37fS8OIEWvOfrk7M,6371
|
|
8
|
+
smartx_rfid/devices/RFID/X714/ble_protocol.py,sha256=M3Cs66WX6oa_EPdMTLEjD-IRxAjpfrKzGj4hn1vxlxQ,6951
|
|
9
|
+
smartx_rfid/devices/RFID/X714/on_receive.py,sha256=Tr8KISrblB4fsLCgGTIi7TltXlyxMS8YMXLRhM9SVTg,2194
|
|
10
|
+
smartx_rfid/devices/RFID/X714/rfid.py,sha256=XgHl2evlN3tGB6hhCUYzpEy_jw2dyplxUcDYsuPec2Q,2285
|
|
11
|
+
smartx_rfid/devices/RFID/X714/serial_protocol.py,sha256=eOGM5sK35SxXfS9KjB8JQzYhR1qs4H3vw3-n3oPOjQQ,3681
|
|
12
|
+
smartx_rfid/devices/RFID/X714/tcp_protocol.py,sha256=GthynyN6YMl6R16nrAnlrDK61QnuZAOhYhPqycBCwJA,4804
|
|
13
|
+
smartx_rfid/devices/RFID/X714/write_commands.py,sha256=tKLTATb4vDkghpw5rQuJwcnPNkViT4753fwToNPfKh0,1304
|
|
14
|
+
smartx_rfid/devices/__init__.py,sha256=EVhtb-IBLpits8dr-I1ZYYuWHw9ddqiu-98myg-iyFg,251
|
|
15
|
+
smartx_rfid/devices/generic/SERIAL/_main.py,sha256=7rEgviwxfd4uWpWjVXsV0qnOAET-MxJux9PcTKiE8G8,8562
|
|
16
|
+
smartx_rfid/devices/generic/TCP/_main.py,sha256=DY9c9m2ZchvUJ9n2TtPENL2GqCYS4hvdjkCAe6yGXxM,2770
|
|
17
|
+
smartx_rfid/devices/generic/TCP/helpers.py,sha256=GQ_yIvmSlx_yZci6pVvZfdo1wyKcS5gtZh-VDwt3pGs,1552
|
|
18
|
+
smartx_rfid/schemas/events.py,sha256=0rdXXww6v1B50pGksNsGt5QhavwKpe1Jb_5f8o_SaVk,215
|
|
19
|
+
smartx_rfid/schemas/tag.py,sha256=iSQkWI4MhSSVEzWhezX6UG4KJJ-FUahrfj0Hnz-H_u0,2269
|
|
20
|
+
smartx_rfid/utils/__init__.py,sha256=lJA0KqFIFVDtietXGH9WwnklKkiTNvhIG6YJdDd11vk,72
|
|
21
|
+
smartx_rfid/utils/event.py,sha256=7QOdiSfiMscoAaTq4U3E3asYGSVnolr9OD3FSYGh6bg,469
|
|
22
|
+
smartx_rfid/utils/logger_manager.py,sha256=61HJ40wgX0hVwG4xW66IQ2jdJp8CFM_S0eoGzx8-FwU,5653
|
|
23
|
+
smartx_rfid/utils/path.py,sha256=7U619vOw4BXGAKbSVdA6ZIhpCN0f-dccVax9LCclbNc,3758
|
|
24
|
+
smartx_rfid/utils/tag_list.py,sha256=b_qx0-ZkjhzKiAuCM6uuBo3QpzbmB2U1Z3CNdStCyxc,6570
|
|
25
|
+
smartx_rfid-1.1.6.dist-info/METADATA,sha256=pFSEmk-GgfHWxJJ_68oIrl6xd3UfjeBwbG18n419K6M,2636
|
|
26
|
+
smartx_rfid-1.1.6.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
27
|
+
smartx_rfid-1.1.6.dist-info/licenses/LICENSE,sha256=npGJO3HuHM3JdFNEXv1jRmP_wRYq9-ujkqJPmLxrtEw,1080
|
|
28
|
+
smartx_rfid-1.1.6.dist-info/RECORD,,
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
smartx_rfid/__init__.py,sha256=XCdKdP8o5LT3VjJtWOuFHJzztc0FXt3Ms89qsx0p6sM,282
|
|
2
|
-
smartx_rfid/devices/RFID/R700_IOT/_main.py,sha256=xd9roD7ovnJAXLp6r7PtxYz98CgKO1t6gz3t4V0KIMU,6293
|
|
3
|
-
smartx_rfid/devices/RFID/R700_IOT/on_event.py,sha256=JmJ6RcGgX34b2gkuqyZ4G1LSDliRRALg_DdvenHOZVk,538
|
|
4
|
-
smartx_rfid/devices/RFID/R700_IOT/reader_config_example.py,sha256=oi4vg3_zjYFDUeq1Y_d5InUx_Rq5M2CB5RDVkK3WCgU,2264
|
|
5
|
-
smartx_rfid/devices/RFID/R700_IOT/reader_helpers.py,sha256=UC4ozq9FmuQtCK5Nv8Mjg4tZyuFetjx1eMWiw5yb0XY,5692
|
|
6
|
-
smartx_rfid/devices/RFID/R700_IOT/write_commands.py,sha256=d1GXBaBVaNqvPWitYNZ94uZJOy2wwbj-2uzCxWOflPo,2037
|
|
7
|
-
smartx_rfid/devices/RFID/X714/_main.py,sha256=ERMeQnbLevGXtZ-8Roykb67jbt53yacawibPkCs4eGo,4753
|
|
8
|
-
smartx_rfid/devices/RFID/X714/ble_protocol.py,sha256=M3Cs66WX6oa_EPdMTLEjD-IRxAjpfrKzGj4hn1vxlxQ,6951
|
|
9
|
-
smartx_rfid/devices/RFID/X714/on_receive.py,sha256=yjkxWL1ZGzcZKZXKwQ85cvl33fsjhXATOwg9QQBk-GQ,1641
|
|
10
|
-
smartx_rfid/devices/RFID/X714/rfid.py,sha256=W-8-ADYv9oYE9PHDEynQhHCXyVecDRfplWRIxb3cqts,1974
|
|
11
|
-
smartx_rfid/devices/RFID/X714/serial_protocol.py,sha256=eOGM5sK35SxXfS9KjB8JQzYhR1qs4H3vw3-n3oPOjQQ,3681
|
|
12
|
-
smartx_rfid/devices/RFID/X714/tcp_protocol.py,sha256=GthynyN6YMl6R16nrAnlrDK61QnuZAOhYhPqycBCwJA,4804
|
|
13
|
-
smartx_rfid/devices/RFID/X714/write_commands.py,sha256=gkfsQ--bijZWw0e1FP8Wb0qtFtexM4hP6HJz6y5hvOM,987
|
|
14
|
-
smartx_rfid/devices/__init__.py,sha256=EVhtb-IBLpits8dr-I1ZYYuWHw9ddqiu-98myg-iyFg,251
|
|
15
|
-
smartx_rfid/devices/generic/SERIAL/_main.py,sha256=BB4b6t6NI7tQDZH1ZAl-ZqSG8WH_HmGJIrIs5hIxpS8,8356
|
|
16
|
-
smartx_rfid/devices/generic/TCP/_main.py,sha256=BzpIiCxamtauQCa7GCldNlG_fhSLJoSxDs2lhGEc31M,2318
|
|
17
|
-
smartx_rfid/devices/generic/TCP/helpers.py,sha256=5ySy0Wv6D3q9AEV8kVLw1aMbe3kMDm4lFxP3KLIMMWM,1386
|
|
18
|
-
smartx_rfid/schemas/tag.py,sha256=gq38yKPqPBPaF-wx_0_lnhttz0neM_bob94puv-5Z7Y,2285
|
|
19
|
-
smartx_rfid/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
smartx_rfid/utils/event.py,sha256=7QOdiSfiMscoAaTq4U3E3asYGSVnolr9OD3FSYGh6bg,469
|
|
21
|
-
smartx_rfid-0.4.0.dist-info/METADATA,sha256=OPKyl9QnD1r2um-C9pwMXuKsMxEji8qv7xItz5T8MsQ,2676
|
|
22
|
-
smartx_rfid-0.4.0.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
23
|
-
smartx_rfid-0.4.0.dist-info/licenses/LICENSE,sha256=npGJO3HuHM3JdFNEXv1jRmP_wRYq9-ujkqJPmLxrtEw,1080
|
|
24
|
-
smartx_rfid-0.4.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|