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.
- smartx_rfid/__init__.py +1 -1
- smartx_rfid/clients/rchlo.py +9 -0
- smartx_rfid/db/__init__.py +1 -0
- smartx_rfid/db/_main.py +432 -0
- smartx_rfid/devices/RFID/R700_IOT/_main.py +0 -4
- smartx_rfid/devices/RFID/X714/on_receive.py +0 -3
- smartx_rfid/devices/RFID/X714/rfid.py +4 -4
- smartx_rfid/parser/__init__.py +1 -0
- smartx_rfid/parser/main.py +27 -0
- smartx_rfid/parser/rfid_tag_parser/__init__.py +15 -0
- smartx_rfid/parser/rfid_tag_parser/exceptions.py +15 -0
- smartx_rfid/parser/rfid_tag_parser/tag_tid_parser.py +674 -0
- smartx_rfid/schemas/events.py +2 -1
- smartx_rfid/schemas/tag.py +4 -4
- smartx_rfid/utils/__init__.py +1 -0
- smartx_rfid/utils/path.py +2 -2
- smartx_rfid/utils/regex.py +9 -0
- smartx_rfid/utils/tag_list.py +25 -9
- smartx_rfid/webhook/__init__.py +2 -0
- smartx_rfid/webhook/_main.py +153 -0
- smartx_rfid/webhook/xtrack.py +30 -0
- smartx_rfid-1.8.0.dist-info/METADATA +344 -0
- smartx_rfid-1.8.0.dist-info/RECORD +40 -0
- {smartx_rfid-1.1.6.dist-info → smartx_rfid-1.8.0.dist-info}/WHEEL +1 -1
- smartx_rfid-1.1.6.dist-info/METADATA +0 -83
- smartx_rfid-1.1.6.dist-info/RECORD +0 -28
- {smartx_rfid-1.1.6.dist-info → smartx_rfid-1.8.0.dist-info}/licenses/LICENSE +0 -0
smartx_rfid/schemas/tag.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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()
|
smartx_rfid/utils/__init__.py
CHANGED
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:
|
smartx_rfid/utils/tag_list.py
CHANGED
|
@@ -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.
|
|
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
|
-
"
|
|
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,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
|
+

|
|
37
|
+

|
|
38
|
+

|
|
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
|
+
|