smartx-rfid 1.1.6__py3-none-any.whl → 1.8.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.
@@ -1,6 +1,6 @@
1
- import re
2
1
  from typing import Optional
3
2
  from pydantic import BaseModel, Field, field_validator
3
+ from smartx_rfid.utils import regex_hex
4
4
 
5
5
 
6
6
  class TagSchema(BaseModel):
@@ -15,7 +15,7 @@ class TagSchema(BaseModel):
15
15
  return v
16
16
  if len(v) != 24:
17
17
  raise ValueError(f"{field} must have exactly 24 characters")
18
- if not re.fullmatch(r"[0-9a-fA-F]{24}", v):
18
+ if not regex_hex(v, 24):
19
19
  raise ValueError(f"{field} must contain only hexadecimal characters (0-9, a-f)")
20
20
  return v.lower()
21
21
 
@@ -42,7 +42,7 @@ class WriteTagValidator(BaseModel):
42
42
 
43
43
  if len(v) != 24:
44
44
  raise ValueError(f"{field} must have exactly 24 characters")
45
- if not re.fullmatch(r"[0-9a-fA-F]{24}", v):
45
+ if not regex_hex(v, 24):
46
46
  raise ValueError(f"{field} must contain only hexadecimal characters (0-9, a-f)")
47
47
  return v.lower()
48
48
 
@@ -50,6 +50,6 @@ class WriteTagValidator(BaseModel):
50
50
  def validate_password_length_and_hex(cls, v, field):
51
51
  if len(v) != 8:
52
52
  raise ValueError(f"{field} must have exactly 8 characters")
53
- if not re.fullmatch(r"[0-9a-fA-F]{8}", v):
53
+ if not regex_hex(v, 8):
54
54
  raise ValueError(f"{field} must contain only hexadecimal characters (0-9, a-f)")
55
55
  return v.lower()
@@ -1,2 +1,3 @@
1
+ from .regex import regex_hex
1
2
  from .tag_list import TagList
2
3
  from .logger_manager import LoggerManager
smartx_rfid/utils/path.py CHANGED
@@ -84,10 +84,10 @@ def include_all_routers(current_path: str, app) -> None:
84
84
  logging.info(f"✅ Route loaded: {relative_path}")
85
85
 
86
86
  else:
87
- logging.warning(f"⚠️ File {current_path} does not define a 'router'")
87
+ logging.warning(f"⚠️ File {current_path}/{file_path.name} does not define a 'router'")
88
88
 
89
89
  except Exception as e:
90
- logging.error(f"❌ Error loading {current_path}: {e}", exc_info=True)
90
+ logging.error(f"❌ Error loading {current_path}/{file_path.name}: {e}", exc_info=True)
91
91
 
92
92
 
93
93
  def load_file(file_path: str | Path) -> str:
@@ -0,0 +1,9 @@
1
+ import re
2
+
3
+
4
+ def regex_hex(value: str, length: int = 24) -> bool:
5
+ """
6
+ Validate if the given value is a valid EPC (24 hexadecimal characters).
7
+ """
8
+ pattern = rf"^[0-9A-Fa-f]{{{length}}}$"
9
+ return bool(re.match(pattern, value))
@@ -3,6 +3,7 @@ from datetime import datetime
3
3
  from threading import Lock
4
4
  import logging
5
5
  from pyepc import SGTIN
6
+ from smartx_rfid.schemas.tag import TagSchema
6
7
 
7
8
 
8
9
  class TagList:
@@ -13,7 +14,7 @@ class TagList:
13
14
  Each tag is uniquely identified by either EPC or TID.
14
15
  """
15
16
 
16
- def __init__(self, unique_identifier: Literal["epc", "tid"] = "epc"):
17
+ def __init__(self, unique_identifier: Literal["epc", "tid"] = "epc", prefix: str | list | None = None):
17
18
  """
18
19
  Initialize the tag list.
19
20
 
@@ -27,6 +28,10 @@ class TagList:
27
28
  self._tags: Dict[str, Dict[str, Any]] = {}
28
29
  self._lock = Lock()
29
30
 
31
+ if isinstance(prefix, str):
32
+ prefix = [prefix]
33
+ self.prefix: list | None = prefix
34
+
30
35
  def __len__(self) -> int:
31
36
  """
32
37
  Return the number of stored tags.
@@ -55,20 +60,30 @@ class TagList:
55
60
  (False, None) if an error occurs;
56
61
  """
57
62
  try:
63
+ # Validate Tag
64
+ TagSchema.model_validate(tag)
65
+
58
66
  identifier_value = tag.get(self.unique_identifier)
59
67
  if not identifier_value:
60
68
  raise ValueError(f"Tag missing '{self.unique_identifier}'")
61
69
 
70
+ # Check Prefix
71
+ if self.prefix is not None:
72
+ epc = tag.get("epc")
73
+ if epc is None or not any(epc.startswith(p) for p in self.prefix):
74
+ return False, None
75
+
76
+ # handle tag
62
77
  with self._lock:
63
78
  if identifier_value not in self._tags:
64
79
  stored = self._new_tag(tag, device)
65
80
  return True, stored
66
81
  else:
67
- stored = self._existing_tag(tag)
82
+ stored = self._existing_tag(tag, device)
68
83
  return False, stored
69
84
 
70
85
  except Exception as e:
71
- logging.warning(f"[ TAG ERROR ] {e}")
86
+ logging.error(f"[ TAG ERROR ] {e}")
72
87
  return False, None
73
88
 
74
89
  def _new_tag(self, tag: Dict[str, Any], device: str) -> Dict[str, Any]:
@@ -90,18 +105,18 @@ class TagList:
90
105
  gtin = None
91
106
 
92
107
  stored_tag = {
93
- **tag,
94
- "device": device,
95
108
  "timestamp": now,
96
- "count": 1,
109
+ "device": device,
110
+ **tag,
97
111
  "gtin": gtin,
112
+ "count": 1,
98
113
  }
99
114
 
100
115
  self._tags[tag[self.unique_identifier]] = stored_tag
101
116
 
102
117
  return stored_tag
103
118
 
104
- def _existing_tag(self, tag: Dict[str, Any]) -> Dict[str, Any]:
119
+ def _existing_tag(self, tag: Dict[str, Any], device: str) -> Dict[str, Any]:
105
120
  """
106
121
  Update an existing tag.
107
122
 
@@ -122,6 +137,7 @@ class TagList:
122
137
  if old_rssi is None or abs(new_rssi) < abs(old_rssi):
123
138
  current["rssi"] = new_rssi
124
139
  current["ant"] = tag.get("ant")
140
+ current["device"] = device
125
141
 
126
142
  return current
127
143
 
@@ -149,11 +165,11 @@ class TagList:
149
165
 
150
166
  if self.unique_identifier == identifier_type:
151
167
  return self._tags.get(identifier_value)
152
-
168
+
153
169
  for tag in self._tags.values():
154
170
  if tag.get(identifier_type) == identifier_value:
155
171
  return tag
156
-
172
+
157
173
  return None
158
174
 
159
175
  def clear(self) -> None:
@@ -0,0 +1,2 @@
1
+ from ._main import WebhookManager
2
+ from .xtrack import WebhookXtrack
@@ -0,0 +1,153 @@
1
+ import asyncio
2
+ import logging
3
+ from typing import Optional, Dict, Any
4
+ from datetime import datetime, date
5
+ from decimal import Decimal
6
+
7
+ import httpx
8
+
9
+
10
+ class WebhookManager:
11
+ def __init__(
12
+ self,
13
+ url: str,
14
+ timeout: float = 5.0,
15
+ max_retries: int = 2,
16
+ ):
17
+ self.url = url
18
+ self.timeout = timeout
19
+ self.max_retries = max_retries
20
+
21
+ self.default_headers = {"Content-Type": "application/json", "User-Agent": "SmartX-Connector/1.0"}
22
+
23
+ def _make_serializable(self, obj: Any) -> Any:
24
+ """
25
+ Converts objects to JSON-serializable format.
26
+
27
+ Handles:
28
+ - datetime/date objects -> ISO format strings
29
+ - Decimal -> float
30
+ - Classes with __dict__ -> dict
31
+ - Sets -> lists
32
+ - Bytes -> string (decoded)
33
+ - Iterables -> lists
34
+
35
+ Args:
36
+ obj: Object to convert
37
+
38
+ Returns:
39
+ JSON-serializable version of the object
40
+ """
41
+ # Handle None
42
+ if obj is None:
43
+ return None
44
+
45
+ # Handle datetime and date
46
+ if isinstance(obj, (datetime, date)):
47
+ return obj.isoformat()
48
+
49
+ # Handle Decimal
50
+ if isinstance(obj, Decimal):
51
+ return float(obj)
52
+
53
+ # Handle bytes
54
+ if isinstance(obj, bytes):
55
+ try:
56
+ return obj.decode("utf-8")
57
+ except Exception:
58
+ return str(obj)
59
+
60
+ # Handle sets
61
+ if isinstance(obj, set):
62
+ return list(obj)
63
+
64
+ # Handle dictionaries (recursively)
65
+ if isinstance(obj, dict):
66
+ return {key: self._make_serializable(value) for key, value in obj.items()}
67
+
68
+ # Handle lists and tuples (recursively)
69
+ if isinstance(obj, (list, tuple)):
70
+ return [self._make_serializable(item) for item in obj]
71
+
72
+ # Handle objects with __dict__ (custom classes)
73
+ if hasattr(obj, "__dict__"):
74
+ return self._make_serializable(obj.__dict__)
75
+
76
+ # Handle primitive types (str, int, float, bool)
77
+ if isinstance(obj, (str, int, float, bool)):
78
+ return obj
79
+
80
+ # Last resort: convert to string
81
+ return str(obj)
82
+
83
+ async def post(
84
+ self, device: str, event_type: str, event_data: Any = None, headers: Optional[Dict[str, str]] = None
85
+ ) -> bool:
86
+ """
87
+ Sends data via POST to the configured webhook URL.
88
+
89
+ Args:
90
+ device: Name of the device sending the webhook
91
+ event_type: Type of event being sent
92
+ event_data: Data to be sent (will be converted to JSON)
93
+ headers: Optional headers for the request
94
+
95
+ Returns:
96
+ bool: True if sent successfully, False otherwise
97
+ """
98
+
99
+ if not self.url:
100
+ logging.warning("⚠️ WEBHOOK_URL not configured in settings")
101
+ return False
102
+
103
+ # Convert event_data to JSON-serializable format
104
+ serializable_event_data = self._make_serializable(event_data)
105
+
106
+ payload = {"device": device, "event_type": event_type, "event_data": serializable_event_data}
107
+
108
+ # Merge with custom headers if provided
109
+ if headers:
110
+ self.default_headers.update(headers)
111
+
112
+ retries = 0
113
+ last_error = None
114
+
115
+ while retries < self.max_retries:
116
+ try:
117
+ async with httpx.AsyncClient(timeout=self.timeout) as client:
118
+ response = await client.post(self.url, json=payload, headers=self.default_headers)
119
+
120
+ # Consider success if status 2xx
121
+ if response.status_code < 300:
122
+ logging.info(f"✅ Webhook sent successfully to {self.url} - Status: {response.status_code}")
123
+ return True
124
+ else:
125
+ logging.warning(
126
+ f"⚠️ Webhook failed - Status: {response.status_code} - Response: {response.text[:200]}"
127
+ )
128
+ return False
129
+
130
+ except httpx.TimeoutException:
131
+ last_error = f"Timeout after {self.timeout}s"
132
+ retries += 1
133
+ logging.warning(f"⏰ Webhook timeout (attempt {retries}/{self.max_retries})")
134
+
135
+ except httpx.ConnectError:
136
+ last_error = "Connection error"
137
+ retries += 1
138
+ logging.warning(f"🔌 Webhook connection error (attempt {retries}/{self.max_retries})")
139
+
140
+ except Exception as e:
141
+ last_error = str(e)
142
+ retries += 1
143
+ logging.error(f"❌ Webhook error (attempt {retries}/{self.max_retries}): {e}")
144
+
145
+ # Wait before retrying (exponential backoff)
146
+ if retries < self.max_retries:
147
+ wait_time = 2**retries # 2s, 4s, 8s...
148
+ logging.info(f"🔁 Retrying webhook in {wait_time}s...")
149
+ await asyncio.sleep(wait_time)
150
+
151
+ # If we reached here, all attempts failed
152
+ logging.error(f"❌ Webhook failed after {self.max_retries} attempts. Last error: {last_error}")
153
+ return False
@@ -0,0 +1,30 @@
1
+ import logging
2
+ import httpx
3
+
4
+
5
+ class WebhookXtrack:
6
+ def __init__(self, url: str, timeout: int = 5):
7
+ self.url = url
8
+ self.timeout = timeout
9
+
10
+ async def post(self, tag: dict):
11
+ try:
12
+ device = tag.get("device", "unknown")
13
+ ant = tag.get("ant", "1")
14
+ epc = tag.get("epc", None)
15
+ if epc is None:
16
+ raise Exception("EPC is required")
17
+ payload = f"""<msg>
18
+ <command>ReportRead</command>
19
+ <data>EVENT=|DEVICENAME={device}|ANTENNANAME={ant}|TAGID={epc}|</data>
20
+ <cmpl>STATE=|DATA1=|DATA2=|DATA3=|DATA4=|DATA5=|</cmpl>
21
+ </msg>"""
22
+ async with httpx.AsyncClient() as client:
23
+ await client.post(
24
+ self.url,
25
+ content=payload,
26
+ headers={"Content-Type": "application/xml"},
27
+ timeout=self.timeout,
28
+ )
29
+ except Exception as e:
30
+ logging.info(f"Error Xtrack: {e}")
@@ -0,0 +1,344 @@
1
+ Metadata-Version: 2.4
2
+ Name: smartx-rfid
3
+ Version: 1.8.0
4
+ Summary: SmartX RFID library
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: python,library,RFID,smartx,packaging
8
+ Author: Gabriel Henrique Pascon
9
+ Author-email: gh.pascon@gmail.com
10
+ Requires-Python: >=3.11,<4.0
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Dist: bleak (>=1.1.1,<2.0.0)
21
+ Requires-Dist: httpx (==0.28.1)
22
+ Requires-Dist: psycopg2 (>=2.9.11,<3.0.0)
23
+ Requires-Dist: pydantic (>=2.12.5,<3.0.0)
24
+ Requires-Dist: pyepc (==0.5.0)
25
+ Requires-Dist: pymysql (==1.1.1)
26
+ Requires-Dist: pyserial (==3.5)
27
+ Requires-Dist: pyserial-asyncio (==0.6)
28
+ Requires-Dist: sqlalchemy (==2.0.29)
29
+ Project-URL: Documentation, https://github.com/ghpascon/smartx_rfid#readme
30
+ Project-URL: Homepage, https://github.com/ghpascon/smartx_rfid
31
+ Project-URL: Repository, https://github.com/ghpascon/smartx_rfid
32
+ Description-Content-Type: text/markdown
33
+
34
+ # SmartX RFID
35
+
36
+ ![Python Version](https://img.shields.io/badge/python-3.11+-blue.svg)
37
+ ![Version](https://img.shields.io/badge/version-1.5.0-green.svg)
38
+ ![License](https://img.shields.io/badge/license-MIT-blue.svg)
39
+
40
+ Python library for RFID device integration and data management.
41
+
42
+ ## Installation
43
+
44
+ ```bash
45
+ pip install smartx-rfid
46
+ ```
47
+
48
+ ## Quick Start
49
+
50
+ ```python
51
+ from smartx_rfid.devices import X714
52
+ import asyncio
53
+
54
+ async def on_tag_read(name: str, tag_data: dict):
55
+ print(f"Tag: {tag_data['epc']} | RSSI: {tag_data['rssi']}dBm")
56
+
57
+ async def main():
58
+ reader = X714(name="RFID Reader", start_reading=True)
59
+ reader.on_event = lambda name, event_type, data: (
60
+ asyncio.create_task(on_tag_read(name, data))
61
+ if event_type == "tag" else None
62
+ )
63
+
64
+ await reader.connect()
65
+
66
+ while True:
67
+ await asyncio.sleep(1)
68
+
69
+ asyncio.run(main())
70
+ ```
71
+
72
+ ## Features
73
+
74
+ ### Supported Devices
75
+ - **X714 RFID Reader** - Serial, TCP, Bluetooth LE connections
76
+ - **R700 IOT** - HTTP REST API integration
77
+ - **Generic Serial/TCP** - Custom protocol support
78
+
79
+ ### Core Components
80
+ - **Device Management** - Async communication with auto-reconnection
81
+ - **Database Integration** - SQLAlchemy with multiple database support
82
+ - **Webhook System** - HTTP notifications with retry logic
83
+ - **Tag Management** - Thread-safe tag list with deduplication
84
+
85
+ ## Device Examples
86
+
87
+ ### X714 RFID Reader
88
+
89
+ ```python
90
+ from smartx_rfid.devices import X714
91
+
92
+ # Serial connection (auto-detect)
93
+ reader = X714(name="X714-Serial")
94
+
95
+ # TCP connection
96
+ reader = X714(
97
+ name="X714-TCP",
98
+ connection_type="TCP",
99
+ ip="192.168.1.100"
100
+ )
101
+
102
+ # Bluetooth LE
103
+ reader = X714(
104
+ name="X714-BLE",
105
+ connection_type="BLE"
106
+ )
107
+
108
+ def on_event(name: str, event_type: str, data: dict):
109
+ if event_type == "tag":
110
+ print(f"EPC: {data['epc']}, Antenna: {data['ant']}")
111
+
112
+ reader.on_event = on_event
113
+ await reader.connect()
114
+ ```
115
+
116
+ ### R700 IOT Reader
117
+
118
+ ```python
119
+ from smartx_rfid.devices import R700_IOT, R700_IOT_config_example
120
+
121
+ reader = R700_IOT(
122
+ name="R700-Reader",
123
+ ip="192.168.1.200",
124
+ config=R700_IOT_config_example
125
+ )
126
+
127
+ reader.on_event = on_event
128
+ await reader.connect()
129
+ ```
130
+
131
+ ## Database Integration
132
+
133
+ ```python
134
+ from smartx_rfid.db import DatabaseManager
135
+ from sqlalchemy import Column, String, Float, Integer, DateTime
136
+ from sqlalchemy.orm import DeclarativeBase
137
+ from datetime import datetime
138
+
139
+ class Base(DeclarativeBase):
140
+ pass
141
+
142
+ class TagModel(Base):
143
+ __tablename__ = 'rfid_tags'
144
+
145
+ id = Column(Integer, primary_key=True)
146
+ epc = Column(String(50), unique=True, nullable=False)
147
+ tid = Column(String(50))
148
+ ant = Column(Integer)
149
+ rssi = Column(Float)
150
+ created_at = Column(DateTime, default=datetime.utcnow)
151
+
152
+ # Initialize database
153
+ db = DatabaseManager("sqlite:///rfid_tags.db")
154
+ db.register_models(TagModel)
155
+ db.create_tables()
156
+
157
+ # Use with sessions
158
+ with db.get_session() as session:
159
+ tag = TagModel(epc="E200001175000001", ant=1, rssi=-45.2)
160
+ session.add(tag)
161
+
162
+ # Raw SQL queries
163
+ results = db.execute_query_fetchall(
164
+ "SELECT * FROM rfid_tags WHERE rssi > :threshold",
165
+ params={"threshold": -50}
166
+ )
167
+ ```
168
+
169
+ ### Supported Databases
170
+ - PostgreSQL: `postgresql://user:pass@localhost/db`
171
+ - MySQL: `mysql+pymysql://user:pass@localhost/db`
172
+ - SQLite: `sqlite:///path/to/database.db`
173
+
174
+ ## Webhook Integration
175
+
176
+ ```python
177
+ from smartx_rfid.webhook import WebhookManager
178
+
179
+ webhook = WebhookManager("https://api.example.com/rfid-events")
180
+
181
+ # Send tag data
182
+ success = await webhook.post("device_01", "tag_read", {
183
+ "epc": "E200001175000001",
184
+ "rssi": -45.2,
185
+ "antenna": 1,
186
+ "timestamp": "2026-01-15T10:30:00Z"
187
+ })
188
+
189
+ if success:
190
+ print("Webhook sent successfully")
191
+ ```
192
+
193
+ ## Tag Management
194
+
195
+ ```python
196
+ from smartx_rfid.utils import TagList
197
+
198
+ # Create thread-safe tag list
199
+ tags = TagList(unique_identifier="epc")
200
+
201
+ def on_tag(device: str, tag_data: dict):
202
+ new_tag, tag = tags.add(tag_data, device=device)
203
+
204
+ if new_tag:
205
+ print(f"New tag: {tag['epc']}")
206
+ # Add custom data
207
+ tag['product_name'] = "Widget ABC"
208
+ else:
209
+ print(f"Existing tag: {tag['epc']}")
210
+
211
+ # Use with device events
212
+ reader.on_event = lambda name, event_type, data: (
213
+ on_tag(name, data) if event_type == "tag" else None
214
+ )
215
+ ```
216
+
217
+ ## Complete Integration Example
218
+
219
+ ```python
220
+ import asyncio
221
+ from smartx_rfid.devices import X714
222
+ from smartx_rfid.db import DatabaseManager
223
+ from smartx_rfid.webhook import WebhookManager
224
+ from smartx_rfid.utils import TagList
225
+
226
+ async def rfid_system():
227
+ # Initialize components
228
+ reader = X714(name="Production-Scanner", start_reading=True)
229
+ db = DatabaseManager("postgresql://localhost/rfid_production")
230
+ webhook = WebhookManager("https://api.internal.com/rfid")
231
+ tags = TagList()
232
+
233
+ async def process_tag(name: str, tag_data: dict):
234
+ # Check if new tag
235
+ new_tag, tag = tags.add(tag_data, device=name)
236
+
237
+ if new_tag:
238
+ # Save to database
239
+ with db.get_session() as session:
240
+ session.add(TagModel(**tag_data))
241
+
242
+ # Send notification
243
+ await webhook.post(name, "new_tag", tag_data)
244
+ print(f"New tag processed: {tag_data['epc']}")
245
+
246
+ reader.on_event = lambda n, t, d: (
247
+ asyncio.create_task(process_tag(n, d)) if t == "tag" else None
248
+ )
249
+
250
+ await reader.connect()
251
+
252
+ asyncio.run(rfid_system())
253
+ ```
254
+
255
+ ## Configuration
256
+
257
+ ### Device Configuration
258
+ ```python
259
+ # High-performance settings
260
+ reader = X714(
261
+ name="FastScanner",
262
+ read_power=30, # Max power
263
+ session=2, # Session config
264
+ read_interval=100 # Fast scanning
265
+ )
266
+
267
+ # Database with connection pooling
268
+ db = DatabaseManager(
269
+ database_url="postgresql://user:pass@localhost/db",
270
+ pool_size=10,
271
+ max_overflow=20,
272
+ echo=True # Enable SQL logging
273
+ )
274
+ ```
275
+
276
+ ### Logging Setup
277
+ ```python
278
+ import logging
279
+
280
+ logging.basicConfig(
281
+ level=logging.INFO,
282
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
283
+ )
284
+ ```
285
+
286
+ ## API Reference
287
+
288
+ ### Core Modules
289
+ - `smartx_rfid.devices` - Device communication classes
290
+ - `smartx_rfid.db` - Database management
291
+ - `smartx_rfid.webhook` - HTTP notification system
292
+ - `smartx_rfid.utils` - Utility classes and helpers
293
+
294
+ ### Event System
295
+ All devices use a consistent event callback system:
296
+
297
+ ```python
298
+ def on_event(device_name: str, event_type: str, event_data: dict):
299
+ """
300
+ Event types:
301
+ - "connected": Device connected successfully
302
+ - "disconnected": Device disconnected
303
+ - "tag": RFID tag detected
304
+ - "error": Error occurred
305
+ """
306
+ pass
307
+
308
+ device.on_event = on_event
309
+ ```
310
+
311
+ ## Examples
312
+
313
+ The `examples/` directory contains working examples for all supported devices and features:
314
+
315
+ ```
316
+ examples/
317
+ ├── devices/
318
+ │ ├── RFID/ # X714, R700_IOT examples
319
+ │ └── generic/ # Serial, TCP examples
320
+ ├── db/ # Database integration examples
321
+ └── utils/ # Tag management examples
322
+ ```
323
+
324
+ Run examples:
325
+ ```bash
326
+ python examples/devices/RFID/X714_SERIAL.py
327
+ python examples/db/showcase.py
328
+ ```
329
+
330
+ ## Requirements
331
+
332
+ - Python 3.11+
333
+ - Dependencies automatically installed with pip
334
+
335
+ ## License
336
+
337
+ MIT License
338
+
339
+ ## Support
340
+
341
+ - **Repository**: [https://github.com/ghpascon/smartx_rfid](https://github.com/ghpascon/smartx_rfid)
342
+ - **Issues**: [GitHub Issues](https://github.com/ghpascon/smartx_rfid/issues)
343
+ - **Email**: [gh.pascon@gmail.com](mailto:gh.pascon@gmail.com)
344
+