smartx-rfid 1.0.0__tar.gz
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-1.0.0/LICENSE +21 -0
- smartx_rfid-1.0.0/PKG-INFO +83 -0
- smartx_rfid-1.0.0/README.md +52 -0
- smartx_rfid-1.0.0/pyproject.toml +67 -0
- smartx_rfid-1.0.0/src/smartx_rfid/__init__.py +9 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/R700_IOT/_main.py +202 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/R700_IOT/on_event.py +26 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/R700_IOT/reader_config_example.py +69 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/R700_IOT/reader_helpers.py +146 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/R700_IOT/write_commands.py +70 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/X714/_main.py +186 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/X714/ble_protocol.py +159 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/X714/on_receive.py +74 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/X714/rfid.py +79 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/X714/serial_protocol.py +89 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/X714/tcp_protocol.py +127 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/RFID/X714/write_commands.py +35 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/__init__.py +8 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/generic/SERIAL/_main.py +246 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/generic/TCP/_main.py +90 -0
- smartx_rfid-1.0.0/src/smartx_rfid/devices/generic/TCP/helpers.py +45 -0
- smartx_rfid-1.0.0/src/smartx_rfid/schemas/tag.py +55 -0
- smartx_rfid-1.0.0/src/smartx_rfid/utils/__init__.py +2 -0
- smartx_rfid-1.0.0/src/smartx_rfid/utils/event.py +15 -0
- smartx_rfid-1.0.0/src/smartx_rfid/utils/logger_manager.py +167 -0
- smartx_rfid-1.0.0/src/smartx_rfid/utils/path.py +110 -0
- smartx_rfid-1.0.0/src/smartx_rfid/utils/tag_list.py +192 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gabriel Henrique Pascon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smartx-rfid
|
|
3
|
+
Version: 1.0.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: pydantic (>=2.12.5,<3.0.0)
|
|
23
|
+
Requires-Dist: pyepc (==0.5.0)
|
|
24
|
+
Requires-Dist: pyserial (==3.5)
|
|
25
|
+
Requires-Dist: pyserial-asyncio (==0.6)
|
|
26
|
+
Project-URL: Documentation, https://github.com/ghpascon/smartx_rfid#readme
|
|
27
|
+
Project-URL: Homepage, https://github.com/ghpascon/smartx_rfid
|
|
28
|
+
Project-URL: Repository, https://github.com/ghpascon/smartx_rfid
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# SmartX RFID
|
|
32
|
+
|
|
33
|
+
The official SmartX RFID Python library for seamless integration with RFID systems and devices.
|
|
34
|
+
|
|
35
|
+
## Overview
|
|
36
|
+
|
|
37
|
+
SmartX RFID is a comprehensive Python package designed to provide easy-to-use interfaces for RFID operations and device management. This library serves as the foundation for building robust RFID applications.
|
|
38
|
+
|
|
39
|
+
## Features (Current & Planned)
|
|
40
|
+
|
|
41
|
+
- **Device Communication**: Asynchronous serial communication with RFID devices
|
|
42
|
+
- **Auto-Detection**: Automatic port detection for USB devices by VID/PID
|
|
43
|
+
- **Connection Management**: Automatic reconnection and error handling
|
|
44
|
+
- **External Device Support**: Interface with various RFID readers and writers *(coming soon)*
|
|
45
|
+
- **Tag Operations**: Read, write, and manage RFID tags *(coming soon)*
|
|
46
|
+
- **Protocol Support**: Multiple RFID protocols and standards *(coming soon)*
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install smartx-rfid
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Quick Start
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
from smartx_rfid.devices import SERIAL
|
|
58
|
+
import asyncio
|
|
59
|
+
|
|
60
|
+
async def main():
|
|
61
|
+
device = SERIAL(name="MyRFIDDevice")
|
|
62
|
+
await device.connect()
|
|
63
|
+
|
|
64
|
+
asyncio.run(main())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Development Status
|
|
68
|
+
|
|
69
|
+
This library is actively under development. Current focus areas include:
|
|
70
|
+
|
|
71
|
+
- Core device communication protocols
|
|
72
|
+
- External device integration
|
|
73
|
+
- Enhanced error handling
|
|
74
|
+
- Comprehensive documentation
|
|
75
|
+
|
|
76
|
+
## License
|
|
77
|
+
|
|
78
|
+
MIT License
|
|
79
|
+
|
|
80
|
+
## Support
|
|
81
|
+
|
|
82
|
+
For issues and support, please visit our [GitHub repository](https://github.com/ghpascon/smartx_rfid).
|
|
83
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# SmartX RFID
|
|
2
|
+
|
|
3
|
+
The official SmartX RFID Python library for seamless integration with RFID systems and devices.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
SmartX RFID is a comprehensive Python package designed to provide easy-to-use interfaces for RFID operations and device management. This library serves as the foundation for building robust RFID applications.
|
|
8
|
+
|
|
9
|
+
## Features (Current & Planned)
|
|
10
|
+
|
|
11
|
+
- **Device Communication**: Asynchronous serial communication with RFID devices
|
|
12
|
+
- **Auto-Detection**: Automatic port detection for USB devices by VID/PID
|
|
13
|
+
- **Connection Management**: Automatic reconnection and error handling
|
|
14
|
+
- **External Device Support**: Interface with various RFID readers and writers *(coming soon)*
|
|
15
|
+
- **Tag Operations**: Read, write, and manage RFID tags *(coming soon)*
|
|
16
|
+
- **Protocol Support**: Multiple RFID protocols and standards *(coming soon)*
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install smartx-rfid
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from smartx_rfid.devices import SERIAL
|
|
28
|
+
import asyncio
|
|
29
|
+
|
|
30
|
+
async def main():
|
|
31
|
+
device = SERIAL(name="MyRFIDDevice")
|
|
32
|
+
await device.connect()
|
|
33
|
+
|
|
34
|
+
asyncio.run(main())
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Development Status
|
|
38
|
+
|
|
39
|
+
This library is actively under development. Current focus areas include:
|
|
40
|
+
|
|
41
|
+
- Core device communication protocols
|
|
42
|
+
- External device integration
|
|
43
|
+
- Enhanced error handling
|
|
44
|
+
- Comprehensive documentation
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
MIT License
|
|
49
|
+
|
|
50
|
+
## Support
|
|
51
|
+
|
|
52
|
+
For issues and support, please visit our [GitHub repository](https://github.com/ghpascon/smartx_rfid).
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "smartx-rfid"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "SmartX RFID library"
|
|
5
|
+
authors = ["Gabriel Henrique Pascon <gh.pascon@gmail.com>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
readme = "README.md"
|
|
8
|
+
homepage = "https://github.com/ghpascon/smartx_rfid"
|
|
9
|
+
repository = "https://github.com/ghpascon/smartx_rfid"
|
|
10
|
+
documentation = "https://github.com/ghpascon/smartx_rfid#readme"
|
|
11
|
+
keywords = ["python", "library", "RFID", "smartx", "packaging"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Operating System :: OS Independent",
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
18
|
+
]
|
|
19
|
+
packages = [{include = "smartx_rfid", from = "src"}]
|
|
20
|
+
|
|
21
|
+
[tool.poetry.dependencies]
|
|
22
|
+
python = "^3.11"
|
|
23
|
+
pyserial-asyncio = "0.6"
|
|
24
|
+
pyserial = "3.5"
|
|
25
|
+
pyepc = "0.5.0"
|
|
26
|
+
httpx = "0.28.1"
|
|
27
|
+
bleak = "^1.1.1"
|
|
28
|
+
pydantic = "^2.12.5"
|
|
29
|
+
|
|
30
|
+
[tool.poetry.group.dev.dependencies]
|
|
31
|
+
pytest = "^7.4.0"
|
|
32
|
+
pytest-cov = "^4.1.0"
|
|
33
|
+
pytest-asyncio = "^0.21.0"
|
|
34
|
+
twine = "^4.0.0"
|
|
35
|
+
ruff = "^0.14.11"
|
|
36
|
+
|
|
37
|
+
[tool.poetry.group.build.dependencies]
|
|
38
|
+
build = "^1.0.0"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["poetry-core>=1.0.0"]
|
|
42
|
+
build-backend = "poetry.core.masonry.api"
|
|
43
|
+
|
|
44
|
+
[tool.ruff]
|
|
45
|
+
line-length = 120
|
|
46
|
+
target-version = "py311"
|
|
47
|
+
exclude = ["build", "dist", ".venv", "__pycache__"] # pastas a ignorar
|
|
48
|
+
|
|
49
|
+
# Por arquivo
|
|
50
|
+
[tool.ruff.lint.per-file-ignores]
|
|
51
|
+
"__init__.py" = ["F401"] # permite imports não usados em __init__.py para re-exportações
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
python_files = ["test_*.py", "*_test.py"]
|
|
56
|
+
python_classes = ["Test*"]
|
|
57
|
+
python_functions = ["test_*"]
|
|
58
|
+
addopts = [
|
|
59
|
+
"--strict-markers",
|
|
60
|
+
"--strict-config",
|
|
61
|
+
"--verbose",
|
|
62
|
+
]
|
|
63
|
+
markers = [
|
|
64
|
+
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
|
|
65
|
+
"integration: marks tests as integration tests",
|
|
66
|
+
"asyncio: marks tests as asyncio tests",
|
|
67
|
+
]
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"""publish_lib_ghp - A simple Python library demonstrating how to publish packages to PyPI."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import version
|
|
4
|
+
|
|
5
|
+
try:
|
|
6
|
+
__version__ = version("publish-lib-example")
|
|
7
|
+
except Exception:
|
|
8
|
+
# Fallback for development environments
|
|
9
|
+
__version__ = "0.0.0-dev"
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import httpx
|
|
5
|
+
|
|
6
|
+
from smartx_rfid.schemas.tag import WriteTagValidator
|
|
7
|
+
from smartx_rfid.utils.event import on_event
|
|
8
|
+
|
|
9
|
+
from .on_event import OnEvent
|
|
10
|
+
from .reader_helpers import ReaderHelpers
|
|
11
|
+
from .write_commands import WriteCommands
|
|
12
|
+
from .reader_config_example import R700_IOT_config_example
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class R700_IOT(OnEvent, ReaderHelpers, WriteCommands):
|
|
16
|
+
"""Impinj R700 RFID reader using HTTP REST API."""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
# READER CONFIG
|
|
21
|
+
reading_config: dict,
|
|
22
|
+
name: str = "R700",
|
|
23
|
+
# CONNECTION
|
|
24
|
+
ip: str = "192.168.1.101", # Example hotsname: impinj-14-46-36
|
|
25
|
+
username: str = "root",
|
|
26
|
+
password: str = "impinj",
|
|
27
|
+
start_reading: bool = False,
|
|
28
|
+
# Firmware Version
|
|
29
|
+
firmware_version: str = "8.4.1",
|
|
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
|
+
"""
|
|
43
|
+
self.name = name
|
|
44
|
+
self.device_type = "rfid"
|
|
45
|
+
|
|
46
|
+
self.ip = ip
|
|
47
|
+
self.username = username
|
|
48
|
+
self.password = password
|
|
49
|
+
|
|
50
|
+
self.start_reading = start_reading
|
|
51
|
+
|
|
52
|
+
# URL AND ENDPOINTS
|
|
53
|
+
self.urlBase = f"https://{self.ip}/api/v1"
|
|
54
|
+
self.endpoint_interface = f"{self.urlBase}/system/rfid/interface"
|
|
55
|
+
self.check_version_endpoint = f"{self.urlBase}/system/image"
|
|
56
|
+
self.endpoint_start = f"{self.urlBase}/profiles/inventory/start"
|
|
57
|
+
self.endpoint_stop = f"{self.urlBase}/profiles/stop"
|
|
58
|
+
self.endpointDataStream = f"{self.urlBase}/data/stream"
|
|
59
|
+
self.endpoint_gpo = f"{self.urlBase}/device/gpos"
|
|
60
|
+
self.endpoint_write = f"{self.urlBase}/profiles/inventory/tag-access"
|
|
61
|
+
|
|
62
|
+
self.interface_config = {"rfidInterface": "rest"}
|
|
63
|
+
self.auth = httpx.BasicAuth(self.username, self.password)
|
|
64
|
+
|
|
65
|
+
self.tags_to_write = {}
|
|
66
|
+
|
|
67
|
+
self.is_connected = False
|
|
68
|
+
self.is_reading = False
|
|
69
|
+
self._stop_connection = False
|
|
70
|
+
|
|
71
|
+
self.firmware_version = firmware_version
|
|
72
|
+
|
|
73
|
+
self.reading_config = reading_config
|
|
74
|
+
self.config_example = R700_IOT_config_example
|
|
75
|
+
|
|
76
|
+
self.on_event = on_event
|
|
77
|
+
|
|
78
|
+
async def disconnect(self):
|
|
79
|
+
"""Safely disconnect from reader and stop reading."""
|
|
80
|
+
"""Desconecta o reader de forma segura."""
|
|
81
|
+
logging.info(f"{self.name} 🔌 Disconnecting reader")
|
|
82
|
+
self._stop_connection = True
|
|
83
|
+
|
|
84
|
+
if self.is_reading:
|
|
85
|
+
try:
|
|
86
|
+
async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=5.0) as session:
|
|
87
|
+
await self.stop_inventory(session)
|
|
88
|
+
except Exception as e:
|
|
89
|
+
logging.warning(f"{self.name} ⚠️ Error stopping inventory during disconnect: {e}")
|
|
90
|
+
|
|
91
|
+
self.is_connected = False
|
|
92
|
+
self.is_reading = False
|
|
93
|
+
self.on_event(self.name, "reading", False)
|
|
94
|
+
self.on_event(self.name, "connection", False)
|
|
95
|
+
|
|
96
|
+
async def connect(self):
|
|
97
|
+
"""Connect to R700 reader and start tag reading."""
|
|
98
|
+
self._stop_connection = False
|
|
99
|
+
while not self._stop_connection:
|
|
100
|
+
async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=10.0) as session:
|
|
101
|
+
if self.is_connected:
|
|
102
|
+
self.on_event(self.name, "connection", False)
|
|
103
|
+
|
|
104
|
+
self.is_connected = False
|
|
105
|
+
self.is_reading = False
|
|
106
|
+
|
|
107
|
+
# Configure interface
|
|
108
|
+
success = await self.configure_interface(session)
|
|
109
|
+
if not success:
|
|
110
|
+
logging.warning(f"{self.name} - Failed to configure interface")
|
|
111
|
+
await asyncio.sleep(1)
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
# Check firmware version
|
|
115
|
+
success = await self.check_firmware_version(session)
|
|
116
|
+
if not success:
|
|
117
|
+
logging.warning(
|
|
118
|
+
f"{self.name} - Incompatible firmware version. Update R700 firmware to {self.firmware_version}."
|
|
119
|
+
)
|
|
120
|
+
await asyncio.sleep(5)
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
# Stop any ongoing profiles
|
|
124
|
+
success = await self.stop_inventory(session)
|
|
125
|
+
if not success:
|
|
126
|
+
logging.warning(f"{self.name} - Failed to stop profiles")
|
|
127
|
+
await asyncio.sleep(1)
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
if self.start_reading or self.reading_config.get("startTriggers"):
|
|
131
|
+
success = await self.start_inventory(session)
|
|
132
|
+
if not success:
|
|
133
|
+
logging.warning(f"{self.name} - Failed to start inventory")
|
|
134
|
+
await asyncio.sleep(1)
|
|
135
|
+
continue
|
|
136
|
+
if self.start_reading:
|
|
137
|
+
self.is_reading = True
|
|
138
|
+
self.on_event(self.name, "reading", True)
|
|
139
|
+
|
|
140
|
+
# Clear GPO states
|
|
141
|
+
for i in range(1, 4):
|
|
142
|
+
asyncio.create_task(self.write_gpo(pin=i, state=False))
|
|
143
|
+
|
|
144
|
+
self.is_connected = True
|
|
145
|
+
self.on_event(self.name, "connection", True)
|
|
146
|
+
await self.get_tag_list(session)
|
|
147
|
+
|
|
148
|
+
async def clear_tags(self):
|
|
149
|
+
"""Clear all stored tags from memory."""
|
|
150
|
+
self.tags = {}
|
|
151
|
+
|
|
152
|
+
async def write_gpo(
|
|
153
|
+
self,
|
|
154
|
+
pin: int = 1,
|
|
155
|
+
state: bool | str = True,
|
|
156
|
+
control: str = "static",
|
|
157
|
+
time: int = 1000,
|
|
158
|
+
*args,
|
|
159
|
+
**kwargs,
|
|
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
|
+
"""
|
|
170
|
+
gpo_command = await self.get_gpo_command(pin=pin, state=state, control=control, time=time)
|
|
171
|
+
try:
|
|
172
|
+
async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=10.0) as session:
|
|
173
|
+
await self.post_to_reader(session, self.endpoint_gpo, payload=gpo_command, method="put")
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logging.warning(f"{self.name} - Failed to set GPO: {e}")
|
|
176
|
+
|
|
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
|
+
"""
|
|
187
|
+
"""
|
|
188
|
+
Writes a new EPC (Electronic Product Code) to RFID tags.
|
|
189
|
+
"""
|
|
190
|
+
try:
|
|
191
|
+
validated_tag = WriteTagValidator(
|
|
192
|
+
target_identifier=target_identifier,
|
|
193
|
+
target_value=target_value,
|
|
194
|
+
new_epc=new_epc,
|
|
195
|
+
password=password,
|
|
196
|
+
)
|
|
197
|
+
logging.info(
|
|
198
|
+
f"{self.name} - Writing EPC: {validated_tag.new_epc} (Current: {validated_tag.target_identifier}={validated_tag.target_value})"
|
|
199
|
+
)
|
|
200
|
+
asyncio.create_task(self.send_write_command(validated_tag.model_dump()))
|
|
201
|
+
except Exception as e:
|
|
202
|
+
logging.warning(f"{self.name} - Write validation error: {e}")
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class OnEvent:
|
|
2
|
+
"""Handle R700 reader events."""
|
|
3
|
+
|
|
4
|
+
async def on_start(self):
|
|
5
|
+
"""Called when reader starts reading tags."""
|
|
6
|
+
self.is_reading = True
|
|
7
|
+
self.on_event(self.name, "reading", True)
|
|
8
|
+
|
|
9
|
+
async def on_stop(self):
|
|
10
|
+
"""Called when reader stops reading tags."""
|
|
11
|
+
self.is_reading = False
|
|
12
|
+
self.on_event(self.name, "reading", False)
|
|
13
|
+
|
|
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
|
+
"""
|
|
20
|
+
current_tag = {
|
|
21
|
+
"epc": tag.get("epcHex"),
|
|
22
|
+
"tid": tag.get("tidHex"),
|
|
23
|
+
"ant": tag.get("antennaPort"),
|
|
24
|
+
"rssi": int(tag.get("peakRssiCdbm", 0) / 100),
|
|
25
|
+
}
|
|
26
|
+
self.on_event(self.name, "tag", current_tag)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
R700_IOT_config_example = {
|
|
2
|
+
"antennaConfigs": [
|
|
3
|
+
{
|
|
4
|
+
"antennaPort": 1,
|
|
5
|
+
"estimatedTagPopulation": 16,
|
|
6
|
+
"fastId": "enabled",
|
|
7
|
+
"inventorySearchMode": "dual-target",
|
|
8
|
+
"inventorySession": 1,
|
|
9
|
+
"receiveSensitivityDbm": -80,
|
|
10
|
+
"rfMode": 4,
|
|
11
|
+
"transmitPowerCdbm": 3300,
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"antennaPort": 2,
|
|
15
|
+
"estimatedTagPopulation": 16,
|
|
16
|
+
"fastId": "enabled",
|
|
17
|
+
"inventorySearchMode": "dual-target",
|
|
18
|
+
"inventorySession": 1,
|
|
19
|
+
"receiveSensitivityDbm": -80,
|
|
20
|
+
"rfMode": 4,
|
|
21
|
+
"transmitPowerCdbm": 3300,
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"antennaPort": 3,
|
|
25
|
+
"estimatedTagPopulation": 16,
|
|
26
|
+
"fastId": "enabled",
|
|
27
|
+
"inventorySearchMode": "dual-target",
|
|
28
|
+
"inventorySession": 1,
|
|
29
|
+
"receiveSensitivityDbm": -80,
|
|
30
|
+
"rfMode": 4,
|
|
31
|
+
"transmitPowerCdbm": 3300,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"antennaPort": 4,
|
|
35
|
+
"estimatedTagPopulation": 16,
|
|
36
|
+
"fastId": "enabled",
|
|
37
|
+
"inventorySearchMode": "dual-target",
|
|
38
|
+
"inventorySession": 1,
|
|
39
|
+
"receiveSensitivityDbm": -80,
|
|
40
|
+
"rfMode": 4,
|
|
41
|
+
"transmitPowerCdbm": 3300,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
"startTriggers": [{"gpiTransitionEvent": {"gpi": 1, "transition": "high-to-low"}}],
|
|
45
|
+
"stopTriggers": [{"gpiTransitionEvent": {"gpi": 1, "transition": "low-to-high"}}],
|
|
46
|
+
"eventConfig": {
|
|
47
|
+
"common": {"hostname": "disabled"},
|
|
48
|
+
"tagInventory": {
|
|
49
|
+
"epc": "disabled",
|
|
50
|
+
"epcHex": "enabled",
|
|
51
|
+
"xpcHex": "disabled",
|
|
52
|
+
"tid": "disabled",
|
|
53
|
+
"tidHex": "enabled",
|
|
54
|
+
"antennaPort": "enabled",
|
|
55
|
+
"transmitPowerCdbm": "disabled",
|
|
56
|
+
"peakRssiCdbm": "enabled",
|
|
57
|
+
"frequency": "disabled",
|
|
58
|
+
"pc": "disabled",
|
|
59
|
+
"lastSeenTime": "disabled",
|
|
60
|
+
"phaseAngle": "disabled",
|
|
61
|
+
"tagReporting": {
|
|
62
|
+
"reportingIntervalSeconds": 1,
|
|
63
|
+
"tagCacheSize": 2048,
|
|
64
|
+
"antennaIdentifier": "antennaPort",
|
|
65
|
+
"tagIdentifier": "epc",
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ReaderHelpers:
|
|
9
|
+
"""Helper methods for R700 reader management."""
|
|
10
|
+
|
|
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
|
+
"""
|
|
20
|
+
endpoint = self.check_version_endpoint
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
if session is None:
|
|
24
|
+
async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=5.0) as client:
|
|
25
|
+
response = await client.get(endpoint)
|
|
26
|
+
else:
|
|
27
|
+
response = await session.get(endpoint)
|
|
28
|
+
|
|
29
|
+
if response.status_code != 200:
|
|
30
|
+
logging.warning(f"{self.name} - Failed to get firmware version: {response.status_code}")
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
version_info = response.json()
|
|
34
|
+
firmware_version = version_info.get("primaryFirmware", "Unknown")
|
|
35
|
+
return firmware_version.startswith(self.firmware_version)
|
|
36
|
+
|
|
37
|
+
except Exception as e:
|
|
38
|
+
logging.warning(f"{self.name} - Error GET {endpoint}: {e}")
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
async def configure_interface(self, session):
|
|
42
|
+
return await self.post_to_reader(
|
|
43
|
+
session,
|
|
44
|
+
self.endpoint_interface,
|
|
45
|
+
payload=self.interface_config,
|
|
46
|
+
method="put",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def stop_inventory(self, session=None):
|
|
50
|
+
return await self.post_to_reader(session, self.endpoint_stop)
|
|
51
|
+
|
|
52
|
+
async def start_inventory(self, session=None):
|
|
53
|
+
return await self.post_to_reader(session, self.endpoint_start, payload=self.reading_config)
|
|
54
|
+
|
|
55
|
+
async def post_to_reader(self, session, endpoint, payload=None, method="post", timeout=3):
|
|
56
|
+
try:
|
|
57
|
+
if session is None:
|
|
58
|
+
async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=timeout) as client:
|
|
59
|
+
return await self.post_to_reader(client, endpoint, payload, method, timeout)
|
|
60
|
+
|
|
61
|
+
if method == "post":
|
|
62
|
+
response = await session.post(endpoint, json=payload, timeout=timeout)
|
|
63
|
+
if response.status_code != 204:
|
|
64
|
+
logging.warning(f"{self.name} - POST {endpoint} failed: {response.status_code}")
|
|
65
|
+
|
|
66
|
+
elif method == "put":
|
|
67
|
+
response = await session.put(endpoint, json=payload, timeout=timeout)
|
|
68
|
+
if response.status_code != 204:
|
|
69
|
+
logging.warning(f"{self.name} - PUT {endpoint} failed: {response.status_code}")
|
|
70
|
+
|
|
71
|
+
return response.status_code == 204
|
|
72
|
+
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logging.warning(f"{self.name} - Error posting to {endpoint}: {e}")
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
async def get_tag_list(self, session):
|
|
78
|
+
try:
|
|
79
|
+
async with session.stream("GET", self.endpointDataStream, timeout=None) as response:
|
|
80
|
+
if response.status_code != 200:
|
|
81
|
+
logging.warning(f"{self.name} - Failed to connect to data stream: {response.status_code}")
|
|
82
|
+
return
|
|
83
|
+
|
|
84
|
+
logging.info(f"{self.name} - Connected to data stream.")
|
|
85
|
+
|
|
86
|
+
async for line in response.aiter_lines():
|
|
87
|
+
try:
|
|
88
|
+
string = line.strip()
|
|
89
|
+
if not string:
|
|
90
|
+
continue
|
|
91
|
+
jsonEvent = json.loads(string)
|
|
92
|
+
|
|
93
|
+
if "inventoryStatusEvent" in jsonEvent:
|
|
94
|
+
status = jsonEvent["inventoryStatusEvent"]["inventoryStatus"]
|
|
95
|
+
if status == "running":
|
|
96
|
+
asyncio.create_task(self.on_start())
|
|
97
|
+
else:
|
|
98
|
+
asyncio.create_task(self.on_stop())
|
|
99
|
+
elif "tagInventoryEvent" in jsonEvent:
|
|
100
|
+
tagEvent = jsonEvent["tagInventoryEvent"]
|
|
101
|
+
asyncio.create_task(self.on_tag(tagEvent))
|
|
102
|
+
|
|
103
|
+
except (json.JSONDecodeError, UnicodeDecodeError) as parse_error:
|
|
104
|
+
logging.warning(f"{self.name} - Failed to parse event: {parse_error}")
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logging.warning(f"{self.name} - Unexpected error: {e}")
|
|
107
|
+
|
|
108
|
+
async def get_gpo_command(
|
|
109
|
+
self, pin: int = 1, state: bool | str = True, control: str = "static", time: int = 1000
|
|
110
|
+
) -> dict:
|
|
111
|
+
"""
|
|
112
|
+
Gera o payload de configuração de GPO para o leitor RFID.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
pin (int): Número do pino GPO a ser configurado. Default é 1.
|
|
116
|
+
state (bool | str): Estado do pino. Pode ser:
|
|
117
|
+
- True ou "high" → alto
|
|
118
|
+
- False ou "low" → baixo
|
|
119
|
+
control ("static" | "pulsed"): Tipo de controle do pino.
|
|
120
|
+
- "static": mantém o estado
|
|
121
|
+
- "pulsed": envia pulso por tempo definido
|
|
122
|
+
time (int): Duração do pulso em milissegundos. Apenas usado se control="pulsed". Default 1000ms.
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
dict: Payload compatível com a API do leitor RFID para configurar GPO.
|
|
126
|
+
|
|
127
|
+
Example:
|
|
128
|
+
gpo_cmd = await self.get_gpo_command(pin=2, state=True, control="pulsed", time=500)
|
|
129
|
+
"""
|
|
130
|
+
# Normaliza o estado
|
|
131
|
+
state = "high" if state is True else "low" if state is False else str(state)
|
|
132
|
+
|
|
133
|
+
if control == "static":
|
|
134
|
+
gpo_command = {"gpoConfigurations": [{"gpo": pin, "state": state, "control": control}]}
|
|
135
|
+
elif control == "pulsed":
|
|
136
|
+
gpo_command = {
|
|
137
|
+
"gpoConfigurations": [
|
|
138
|
+
{
|
|
139
|
+
"gpo": pin,
|
|
140
|
+
"state": state,
|
|
141
|
+
"pulseDurationMilliseconds": time,
|
|
142
|
+
"control": control,
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
}
|
|
146
|
+
return gpo_command
|