smartx-rfid 0.4.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.
@@ -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,167 @@
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
+ def __init__(
17
+ self,
18
+ # READER CONFIG
19
+ reading_config: dict,
20
+ name: str = "R700",
21
+ # CONNECTION
22
+ ip: str = "192.168.1.101", # Example hotsname: impinj-14-46-36
23
+ username: str = "root",
24
+ password: str = "impinj",
25
+ start_reading: bool = False,
26
+ # Firmware Version
27
+ firmware_version: str = "8.4.1",
28
+ ):
29
+ self.name = name
30
+ self.device_type = "rfid"
31
+
32
+ self.ip = ip
33
+ self.username = username
34
+ self.password = password
35
+
36
+ self.start_reading = start_reading
37
+
38
+ # URL AND ENDPOINTS
39
+ self.urlBase = f"https://{self.ip}/api/v1"
40
+ self.endpoint_interface = f"{self.urlBase}/system/rfid/interface"
41
+ self.check_version_endpoint = f"{self.urlBase}/system/image"
42
+ self.endpoint_start = f"{self.urlBase}/profiles/inventory/start"
43
+ self.endpoint_stop = f"{self.urlBase}/profiles/stop"
44
+ self.endpointDataStream = f"{self.urlBase}/data/stream"
45
+ self.endpoint_gpo = f"{self.urlBase}/device/gpos"
46
+ self.endpoint_write = f"{self.urlBase}/profiles/inventory/tag-access"
47
+
48
+ self.interface_config = {"rfidInterface": "rest"}
49
+ self.auth = httpx.BasicAuth(self.username, self.password)
50
+
51
+ self.tags_to_write = {}
52
+
53
+ self.is_connected = False
54
+ self.is_reading = False
55
+ self._stop_connection = False
56
+
57
+ self.firmware_version = firmware_version
58
+
59
+ self.reading_config = reading_config
60
+ self.config_example = R700_IOT_config_example
61
+
62
+ self.on_event = on_event
63
+
64
+ async def disconnect(self):
65
+ """Desconecta o reader de forma segura."""
66
+ logging.info(f"{self.name} 🔌 Disconnecting reader")
67
+ self._stop_connection = True
68
+
69
+ if self.is_reading:
70
+ try:
71
+ async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=5.0) as session:
72
+ await self.stop_inventory(session)
73
+ except Exception as e:
74
+ logging.warning(f"{self.name} ⚠️ Error stopping inventory during disconnect: {e}")
75
+
76
+ self.is_connected = False
77
+ self.is_reading = False
78
+ self.on_event(self.name, "reading", False)
79
+ self.on_event(self.name, "connection", False)
80
+
81
+ async def connect(self):
82
+ self._stop_connection = False
83
+ while not self._stop_connection:
84
+ async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=10.0) as session:
85
+ if self.is_connected:
86
+ self.on_event(self.name, "connection", False)
87
+
88
+ self.is_connected = False
89
+ self.is_reading = False
90
+
91
+ # Configure interface
92
+ success = await self.configure_interface(session)
93
+ if not success:
94
+ logging.warning(f"{self.name} - Failed to configure interface")
95
+ await asyncio.sleep(1)
96
+ continue
97
+
98
+ # Check firmware version
99
+ success = await self.check_firmware_version(session)
100
+ if not success:
101
+ logging.warning(
102
+ f"{self.name} - Incompatible firmware version. Update R700 firmware to {self.firmware_version}."
103
+ )
104
+ await asyncio.sleep(5)
105
+ continue
106
+
107
+ # Stop any ongoing profiles
108
+ success = await self.stop_inventory(session)
109
+ if not success:
110
+ logging.warning(f"{self.name} - Failed to stop profiles")
111
+ await asyncio.sleep(1)
112
+ continue
113
+
114
+ if self.start_reading or self.reading_config.get("startTriggers"):
115
+ success = await self.start_inventory(session)
116
+ if not success:
117
+ logging.warning(f"{self.name} - Failed to start inventory")
118
+ await asyncio.sleep(1)
119
+ continue
120
+ if self.start_reading:
121
+ self.is_reading = True
122
+ self.on_event(self.name, "reading", True)
123
+
124
+ # Clear GPO states
125
+ for i in range(1, 4):
126
+ asyncio.create_task(self.write_gpo(pin=i, state=False))
127
+
128
+ self.is_connected = True
129
+ self.on_event(self.name, "connection", True)
130
+ await self.get_tag_list(session)
131
+
132
+ async def clear_tags(self):
133
+ self.tags = {}
134
+
135
+ async def write_gpo(
136
+ self,
137
+ pin: int = 1,
138
+ state: bool | str = True,
139
+ control: str = "static",
140
+ time: int = 1000,
141
+ *args,
142
+ **kwargs,
143
+ ):
144
+ gpo_command = await self.get_gpo_command(pin=pin, state=state, control=control, time=time)
145
+ try:
146
+ async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=10.0) as session:
147
+ await self.post_to_reader(session, self.endpoint_gpo, payload=gpo_command, method="put")
148
+ except Exception as e:
149
+ logging.warning(f"{self.name} - Failed to set GPO: {e}")
150
+
151
+ def write_epc(self, target_identifier: str | None, target_value: str | None, new_epc: str, password: str):
152
+ """
153
+ Writes a new EPC (Electronic Product Code) to RFID tags.
154
+ """
155
+ try:
156
+ validated_tag = WriteTagValidator(
157
+ target_identifier=target_identifier,
158
+ target_value=target_value,
159
+ new_epc=new_epc,
160
+ password=password,
161
+ )
162
+ logging.info(
163
+ f"{self.name} - Writing EPC: {validated_tag.new_epc} (Current: {validated_tag.target_identifier}={validated_tag.target_value})"
164
+ )
165
+ asyncio.create_task(self.send_write_command(validated_tag.model_dump()))
166
+ except Exception as e:
167
+ logging.warning(f"{self.name} - Write validation error: {e}")
@@ -0,0 +1,17 @@
1
+ class OnEvent:
2
+ async def on_start(self):
3
+ self.is_reading = True
4
+ self.on_event(self.name, "reading", True)
5
+
6
+ async def on_stop(self):
7
+ self.is_reading = False
8
+ self.on_event(self.name, "reading", False)
9
+
10
+ async def on_tag(self, tag):
11
+ current_tag = {
12
+ "epc": tag.get("epcHex"),
13
+ "tid": tag.get("tidHex"),
14
+ "ant": tag.get("antennaPort"),
15
+ "rssi": int(tag.get("peakRssiCdbm", 0) / 100),
16
+ }
17
+ 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,136 @@
1
+ import asyncio
2
+ import json
3
+ import logging
4
+
5
+ import httpx
6
+
7
+
8
+ class ReaderHelpers:
9
+ async def check_firmware_version(self, session: httpx.AsyncClient | None = None):
10
+ endpoint = self.check_version_endpoint
11
+
12
+ try:
13
+ if session is None:
14
+ async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=5.0) as client:
15
+ response = await client.get(endpoint)
16
+ else:
17
+ response = await session.get(endpoint)
18
+
19
+ if response.status_code != 200:
20
+ logging.warning(f"{self.name} - Failed to get firmware version: {response.status_code}")
21
+ return False
22
+
23
+ version_info = response.json()
24
+ firmware_version = version_info.get("primaryFirmware", "Unknown")
25
+ return firmware_version.startswith(self.firmware_version)
26
+
27
+ except Exception as e:
28
+ logging.warning(f"{self.name} - Error GET {endpoint}: {e}")
29
+ return False
30
+
31
+ async def configure_interface(self, session):
32
+ return await self.post_to_reader(
33
+ session,
34
+ self.endpoint_interface,
35
+ payload=self.interface_config,
36
+ method="put",
37
+ )
38
+
39
+ async def stop_inventory(self, session=None):
40
+ return await self.post_to_reader(session, self.endpoint_stop)
41
+
42
+ async def start_inventory(self, session=None):
43
+ return await self.post_to_reader(session, self.endpoint_start, payload=self.reading_config)
44
+
45
+ async def post_to_reader(self, session, endpoint, payload=None, method="post", timeout=3):
46
+ try:
47
+ if session is None:
48
+ async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=timeout) as client:
49
+ return await self.post_to_reader(client, endpoint, payload, method, timeout)
50
+
51
+ if method == "post":
52
+ response = await session.post(endpoint, json=payload, timeout=timeout)
53
+ if response.status_code != 204:
54
+ logging.warning(f"{self.name} - POST {endpoint} failed: {response.status_code}")
55
+
56
+ elif method == "put":
57
+ response = await session.put(endpoint, json=payload, timeout=timeout)
58
+ if response.status_code != 204:
59
+ logging.warning(f"{self.name} - PUT {endpoint} failed: {response.status_code}")
60
+
61
+ return response.status_code == 204
62
+
63
+ except Exception as e:
64
+ logging.warning(f"{self.name} - Error posting to {endpoint}: {e}")
65
+ return False
66
+
67
+ async def get_tag_list(self, session):
68
+ try:
69
+ async with session.stream("GET", self.endpointDataStream, timeout=None) as response:
70
+ if response.status_code != 200:
71
+ logging.warning(f"{self.name} - Failed to connect to data stream: {response.status_code}")
72
+ return
73
+
74
+ logging.info(f"{self.name} - Connected to data stream.")
75
+
76
+ async for line in response.aiter_lines():
77
+ try:
78
+ string = line.strip()
79
+ if not string:
80
+ continue
81
+ jsonEvent = json.loads(string)
82
+
83
+ if "inventoryStatusEvent" in jsonEvent:
84
+ status = jsonEvent["inventoryStatusEvent"]["inventoryStatus"]
85
+ if status == "running":
86
+ asyncio.create_task(self.on_start())
87
+ else:
88
+ asyncio.create_task(self.on_stop())
89
+ elif "tagInventoryEvent" in jsonEvent:
90
+ tagEvent = jsonEvent["tagInventoryEvent"]
91
+ asyncio.create_task(self.on_tag(tagEvent))
92
+
93
+ except (json.JSONDecodeError, UnicodeDecodeError) as parse_error:
94
+ logging.warning(f"{self.name} - Failed to parse event: {parse_error}")
95
+ except Exception as e:
96
+ logging.warning(f"{self.name} - Unexpected error: {e}")
97
+
98
+ async def get_gpo_command(
99
+ self, pin: int = 1, state: bool | str = True, control: str = "static", time: int = 1000
100
+ ) -> dict:
101
+ """
102
+ Gera o payload de configuração de GPO para o leitor RFID.
103
+
104
+ Args:
105
+ pin (int): Número do pino GPO a ser configurado. Default é 1.
106
+ state (bool | str): Estado do pino. Pode ser:
107
+ - True ou "high" → alto
108
+ - False ou "low" → baixo
109
+ control ("static" | "pulsed"): Tipo de controle do pino.
110
+ - "static": mantém o estado
111
+ - "pulsed": envia pulso por tempo definido
112
+ time (int): Duração do pulso em milissegundos. Apenas usado se control="pulsed". Default 1000ms.
113
+
114
+ Returns:
115
+ dict: Payload compatível com a API do leitor RFID para configurar GPO.
116
+
117
+ Example:
118
+ gpo_cmd = await self.get_gpo_command(pin=2, state=True, control="pulsed", time=500)
119
+ """
120
+ # Normaliza o estado
121
+ state = "high" if state is True else "low" if state is False else str(state)
122
+
123
+ if control == "static":
124
+ gpo_command = {"gpoConfigurations": [{"gpo": pin, "state": state, "control": control}]}
125
+ elif control == "pulsed":
126
+ gpo_command = {
127
+ "gpoConfigurations": [
128
+ {
129
+ "gpo": pin,
130
+ "state": state,
131
+ "pulseDurationMilliseconds": time,
132
+ "control": control,
133
+ }
134
+ ]
135
+ }
136
+ return gpo_command
@@ -0,0 +1,60 @@
1
+ import logging
2
+
3
+ import httpx
4
+
5
+
6
+ class WriteCommands:
7
+ async def get_write_cmd(self, tag):
8
+ identifier = tag.target_identifier
9
+ target = tag.target_value
10
+ epc = tag.new_epc
11
+ password = tag.password
12
+
13
+ return {
14
+ "accessCommands": [
15
+ {
16
+ "identifier": "1",
17
+ "blockWrite": {
18
+ "memoryBank": "epc",
19
+ "wordOffset": 2,
20
+ "dataHex": epc[0:8],
21
+ },
22
+ },
23
+ {
24
+ "identifier": "2",
25
+ "blockWrite": {
26
+ "memoryBank": "epc",
27
+ "wordOffset": 4,
28
+ "dataHex": epc[8:16],
29
+ },
30
+ },
31
+ {
32
+ "identifier": "3",
33
+ "blockWrite": {
34
+ "memoryBank": "epc",
35
+ "wordOffset": 6,
36
+ "dataHex": epc[16:24],
37
+ },
38
+ },
39
+ ],
40
+ "tagAccessPasswordHex": password,
41
+ "tagSelectors": [
42
+ {
43
+ "action": "include",
44
+ "tagMemoryBank": identifier if identifier else "epc",
45
+ "bitOffset": 0 if identifier == "tid" else 32,
46
+ "mask": target if target else "0",
47
+ "maskLength": 1 if identifier is None else 96,
48
+ }
49
+ ],
50
+ }
51
+
52
+ async def send_write_command(self, write_command):
53
+ if not isinstance(write_command, list):
54
+ write_command = [write_command]
55
+ payload = {"accessConfigurations": write_command}
56
+ try:
57
+ async with httpx.AsyncClient(auth=self.auth, verify=False, timeout=10.0) as session:
58
+ await self.post_to_reader(session, self.endpoint_write, payload=payload)
59
+ except Exception as e:
60
+ logging.warning(f"{self.name} - Failed to Write: {e}")
@@ -0,0 +1,146 @@
1
+ import asyncio
2
+ import logging
3
+
4
+ from typing import Callable
5
+
6
+ from .ble_protocol import BLEProtocol
7
+ from .on_receive import OnReceive
8
+ from .rfid import RfidCommands
9
+ from .serial_protocol import SerialProtocol
10
+ from .tcp_protocol import TCPProtocol
11
+ from .write_commands import WriteCommands
12
+
13
+ from smartx_rfid.utils.event import on_event
14
+
15
+ ant_default_config = {
16
+ "1": {"active": True, "power": 22, "rssi": -120},
17
+ "2": {"active": False, "power": 22, "rssi": -120},
18
+ "3": {"active": False, "power": 22, "rssi": -120},
19
+ "4": {"active": False, "power": 22, "rssi": -120},
20
+ }
21
+
22
+
23
+ class X714(SerialProtocol, OnReceive, RfidCommands, BLEProtocol, WriteCommands, TCPProtocol):
24
+ def __init__(
25
+ self,
26
+ name: str = "X714",
27
+ connection_type: str = "SERIAL", # SERIAL, BLE or TCP
28
+ # SERIAL
29
+ port: str = "AUTO", # AUTO or COMx / /dev/ttyX
30
+ baudrate: int = 115200,
31
+ vid: int = 1,
32
+ pid: int = 1,
33
+ # TCP
34
+ ip: str | None = "192.168.1.100",
35
+ tcp_port: int = 23,
36
+ # BLE
37
+ ble_name: str = "SMTX",
38
+ # GENERIC
39
+ buzzer: bool = False,
40
+ session: int = 1, # 0, 1, 2, 3
41
+ start_reading: bool = False,
42
+ gpi_start: bool = False,
43
+ ignore_read: bool = False,
44
+ always_send: bool = True,
45
+ simple_send: bool = False,
46
+ keyboard: bool = False,
47
+ decode_gtin: bool = False,
48
+ hotspot: bool = True,
49
+ reconnection_time: int = 3,
50
+ prefix: str = "",
51
+ protected_inventory_password: str | None = None,
52
+ # Antenna config
53
+ # If ant_dict is provided use it else use the other vars
54
+ ant_dict: dict | None = None,
55
+ active_ant: list[int] | None = [1],
56
+ read_power: int = 22,
57
+ read_rssi: int = -120,
58
+ ):
59
+ # Name
60
+ self.name = name
61
+ self.device_type = "rfid"
62
+
63
+ # CONNECTION TYPE
64
+ if connection_type in ["SERIAL", "BLE", "TCP"]:
65
+ self.connection_type = connection_type
66
+ else:
67
+ self.connection_type = "SERIAL"
68
+ logging.warning(f"[{self.name}] Invalid connection_type '{connection_type}' set to 'SERIAL'")
69
+
70
+ # SERIAL CONFIG
71
+ self.port = port
72
+ self.baudrate = baudrate
73
+ self.vid = vid
74
+ self.pid = pid
75
+ self.is_auto = self.port == "AUTO"
76
+
77
+ # TCP CONFIG
78
+ self.ip = ip
79
+ self.tcp_port = tcp_port
80
+
81
+ # BLE CONFIG
82
+ self.ble_name = ble_name
83
+ self.init_ble_vars()
84
+
85
+ # GENERIC CONFIG
86
+ self.buzzer = buzzer
87
+ if session not in [0, 1, 2, 3]:
88
+ session = 1
89
+ logging.warning(f"[{self.name}] Invalid session '{session}' set to '1'")
90
+ self.session = session
91
+ self.start_reading = start_reading
92
+ self.gpi_start = gpi_start
93
+ self.ignore_read = ignore_read
94
+ self.always_send = always_send
95
+ self.simple_send = simple_send
96
+ self.keyboard = keyboard
97
+ self.decode_gtin = decode_gtin
98
+ self.hotspot = hotspot
99
+ self.reconnection_time = reconnection_time
100
+ self.prefix = prefix
101
+ self.protected_inventory_password = protected_inventory_password
102
+
103
+ # ANTENNA CONFIG
104
+ if ant_dict is not None:
105
+ self.ant_dict = ant_dict
106
+ else:
107
+ self.ant_dict = ant_default_config
108
+ for ant in self.ant_dict.keys():
109
+ ant_num = int(ant)
110
+ if active_ant and ant_num in active_ant:
111
+ self.ant_dict[ant]["active"] = True
112
+ else:
113
+ self.ant_dict[ant]["active"] = False
114
+ self.ant_dict[ant]["power"] = read_power
115
+ self.ant_dict[ant]["rssi"] = read_rssi
116
+
117
+ self.transport = None
118
+ self.on_con_lost = None
119
+ self.rx_buffer = bytearray()
120
+ self.last_byte_time = None
121
+
122
+ self.is_connected = False
123
+ self.is_reading = False
124
+
125
+ self.on_event: Callable = on_event
126
+
127
+ def write(self, to_send, verbose=True):
128
+ if self.connection_type == "SERIAL":
129
+ self.write_serial(to_send, verbose)
130
+ elif self.connection_type == "BLE":
131
+ asyncio.create_task(self.write_ble(to_send.encode(), verbose))
132
+ else:
133
+ asyncio.create_task(self.write_tcp(to_send, verbose))
134
+
135
+ async def connect(self):
136
+ if self.connection_type == "SERIAL":
137
+ await self.connect_serial()
138
+ elif self.connection_type == "BLE":
139
+ await self.connect_ble()
140
+ else:
141
+ await self.connect_tcp(self.ip, self.tcp_port)
142
+
143
+ def on_connected(self):
144
+ """Callback chamado quando a conexão é estabelecida."""
145
+ self.config_reader()
146
+ self.on_event(self.name, "connected", True)