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.
- smartx_rfid/__init__.py +9 -0
- smartx_rfid/devices/RFID/R700_IOT/_main.py +167 -0
- smartx_rfid/devices/RFID/R700_IOT/on_event.py +17 -0
- smartx_rfid/devices/RFID/R700_IOT/reader_config_example.py +69 -0
- smartx_rfid/devices/RFID/R700_IOT/reader_helpers.py +136 -0
- smartx_rfid/devices/RFID/R700_IOT/write_commands.py +60 -0
- smartx_rfid/devices/RFID/X714/_main.py +146 -0
- smartx_rfid/devices/RFID/X714/ble_protocol.py +159 -0
- smartx_rfid/devices/RFID/X714/on_receive.py +54 -0
- smartx_rfid/devices/RFID/X714/rfid.py +73 -0
- smartx_rfid/devices/RFID/X714/serial_protocol.py +89 -0
- smartx_rfid/devices/RFID/X714/tcp_protocol.py +127 -0
- smartx_rfid/devices/RFID/X714/write_commands.py +25 -0
- smartx_rfid/devices/__init__.py +8 -0
- smartx_rfid/devices/generic/SERIAL/_main.py +237 -0
- smartx_rfid/devices/generic/TCP/_main.py +72 -0
- smartx_rfid/devices/generic/TCP/helpers.py +41 -0
- smartx_rfid/schemas/tag.py +55 -0
- smartx_rfid/utils/__init__.py +0 -0
- smartx_rfid/utils/event.py +15 -0
- smartx_rfid-0.4.0.dist-info/METADATA +84 -0
- smartx_rfid-0.4.0.dist-info/RECORD +24 -0
- smartx_rfid-0.4.0.dist-info/WHEEL +4 -0
- smartx_rfid-0.4.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import sys
|
|
4
|
+
import threading
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from bleak import BleakClient, BleakScanner
|
|
8
|
+
|
|
9
|
+
if sys.platform == "win32":
|
|
10
|
+
from bleak.backends.winrt.util import allow_sta
|
|
11
|
+
else:
|
|
12
|
+
allow_sta = None
|
|
13
|
+
from bleak.exc import BleakError
|
|
14
|
+
|
|
15
|
+
if allow_sta:
|
|
16
|
+
allow_sta()
|
|
17
|
+
|
|
18
|
+
# ---------------- Settings ----------------
|
|
19
|
+
SERVICE_UUID = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"
|
|
20
|
+
CHARACTERISTIC_RX = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" # Write (ESP32 receives)
|
|
21
|
+
CHARACTERISTIC_TX = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E" # Notify (ESP32 sends)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class BLEProtocol:
|
|
25
|
+
def init_ble_vars(self):
|
|
26
|
+
self.client_ble: Optional[BleakClient] = None
|
|
27
|
+
self.client_ble_lock = asyncio.Lock()
|
|
28
|
+
self.connected_ble_event = asyncio.Event()
|
|
29
|
+
self.ble_stop = False
|
|
30
|
+
self.notify_enabled = False
|
|
31
|
+
|
|
32
|
+
# ---------------- Utilities ----------------
|
|
33
|
+
async def write_ble(self, data: bytes, verbose: bool = False) -> bool:
|
|
34
|
+
"""Send data via BLE with connection check and lock."""
|
|
35
|
+
if not self.client_ble or not self.client_ble.is_connected:
|
|
36
|
+
logging.warning(f"{self.name} - ⚠️ BLE client not connected")
|
|
37
|
+
return False
|
|
38
|
+
async with self.client_ble_lock:
|
|
39
|
+
try:
|
|
40
|
+
await self.client_ble.write_gatt_char(CHARACTERISTIC_RX, data)
|
|
41
|
+
if verbose:
|
|
42
|
+
logging.info(f"{self.name} - [BLE TX] {data}")
|
|
43
|
+
return True
|
|
44
|
+
except Exception as e:
|
|
45
|
+
logging.warning(f"{self.name} - [BLE Write Error] {e}")
|
|
46
|
+
return False
|
|
47
|
+
|
|
48
|
+
async def scan_for_device(self) -> Optional[str]:
|
|
49
|
+
"""Scan for devices whose name starts with the defined prefix."""
|
|
50
|
+
while not self.ble_stop:
|
|
51
|
+
logging.info(f"{self.name} - 🔍 Scanning BLE devices...")
|
|
52
|
+
try:
|
|
53
|
+
devices = await BleakScanner.discover(timeout=5.0)
|
|
54
|
+
for d in devices:
|
|
55
|
+
if d.name and d.name.startswith(self.ble_name):
|
|
56
|
+
logging.info(f"{self.name} - ✅ Device found: {d.address} ({d.name})")
|
|
57
|
+
return d.address
|
|
58
|
+
logging.warning(f"{self.name} - ❌ Device not found, retrying in 3s...")
|
|
59
|
+
except Exception as e:
|
|
60
|
+
logging.warning(f"{self.name} - [Scan Error] {e}")
|
|
61
|
+
await asyncio.sleep(self.reconnection_time)
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
# ---------------- Main Connection ----------------
|
|
65
|
+
async def connect_and_run(self):
|
|
66
|
+
"""Main BLE connection and operation loop."""
|
|
67
|
+
while not self.ble_stop:
|
|
68
|
+
try:
|
|
69
|
+
# Se já estava conectado antes, emite o evento de desconexão
|
|
70
|
+
if self.is_connected:
|
|
71
|
+
self.is_connected = False
|
|
72
|
+
self.on_event(self.name, "connection", False)
|
|
73
|
+
|
|
74
|
+
# Escolhe o endereço conforme o modo
|
|
75
|
+
if self.is_auto:
|
|
76
|
+
address = await self.scan_for_device()
|
|
77
|
+
if not address:
|
|
78
|
+
continue
|
|
79
|
+
else:
|
|
80
|
+
address = self.connection # Usa o MAC address fixo
|
|
81
|
+
logging.info(f"{self.name} - 🔗 Using fixed BLE address: {address}")
|
|
82
|
+
|
|
83
|
+
logging.info(f"{self.name} - Attempting to connect to {address}...")
|
|
84
|
+
client = BleakClient(address)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
await asyncio.wait_for(client.connect(), timeout=5.0)
|
|
88
|
+
except asyncio.TimeoutError:
|
|
89
|
+
logging.warning(f"{self.name} - ⏰ Connection attempt timed out")
|
|
90
|
+
await asyncio.sleep(self.reconnection_time)
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
if not client.is_connected:
|
|
94
|
+
logging.warning(f"{self.name} - ❌ Failed to connect.")
|
|
95
|
+
await asyncio.sleep(self.reconnection_time)
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
async with client:
|
|
99
|
+
logging.info(f"{self.name} - 🔗 Connected to device")
|
|
100
|
+
self.client_ble = client
|
|
101
|
+
self.connected_ble_event.set()
|
|
102
|
+
|
|
103
|
+
# Notification callback
|
|
104
|
+
def handle_notification(sender, data: bytearray):
|
|
105
|
+
decoded = data.decode(errors="ignore")
|
|
106
|
+
self.on_receive(decoded)
|
|
107
|
+
|
|
108
|
+
# Habilita notificações automaticamente
|
|
109
|
+
self.notify_enabled = False
|
|
110
|
+
for service in client.services:
|
|
111
|
+
for char in service.characteristics:
|
|
112
|
+
if "notify" in char.properties:
|
|
113
|
+
try:
|
|
114
|
+
await client.start_notify(char.uuid, handle_notification)
|
|
115
|
+
self.is_connected = True
|
|
116
|
+
self.on_event(self.name, "connection", True)
|
|
117
|
+
logging.info(f"{self.name} - ✅ BLE connection successfully established.")
|
|
118
|
+
self.config_reader()
|
|
119
|
+
self.notify_enabled = True
|
|
120
|
+
except Exception as e:
|
|
121
|
+
logging.warning(f"{self.name} - [Notify Error] {char.uuid}: {e}")
|
|
122
|
+
if not self.notify_enabled:
|
|
123
|
+
logging.warning(f"{self.name} - ⚠️ No characteristics with notify property found!")
|
|
124
|
+
# Loop principal de manutenção da conexão
|
|
125
|
+
last_ping = 0
|
|
126
|
+
while client.is_connected and not self.ble_stop:
|
|
127
|
+
now = asyncio.get_event_loop().time()
|
|
128
|
+
if now - last_ping >= 5:
|
|
129
|
+
await self.write_ble(b"#ping")
|
|
130
|
+
last_ping = now
|
|
131
|
+
await asyncio.sleep(1)
|
|
132
|
+
|
|
133
|
+
logging.info(f"{self.name} - 🔌 Disconnected from device.")
|
|
134
|
+
|
|
135
|
+
except BleakError as e:
|
|
136
|
+
logging.warning(f"{self.name} - [BLE Error] {e}")
|
|
137
|
+
await asyncio.sleep(self.reconnection_time)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
logging.warning(f"{self.name} - [Unexpected BLE Error] {e}")
|
|
140
|
+
await asyncio.sleep(self.reconnection_time)
|
|
141
|
+
finally:
|
|
142
|
+
self.connected_ble_event.clear()
|
|
143
|
+
self.client_ble = None
|
|
144
|
+
|
|
145
|
+
# ---------------- Thread Wrapper ----------------
|
|
146
|
+
def connect_ble(self):
|
|
147
|
+
"""Run BLE loop in a separate thread (ideal for FastAPI)."""
|
|
148
|
+
|
|
149
|
+
def run_loop():
|
|
150
|
+
loop = asyncio.new_event_loop()
|
|
151
|
+
asyncio.set_event_loop(loop)
|
|
152
|
+
loop.run_until_complete(self.connect_and_run())
|
|
153
|
+
|
|
154
|
+
threading.Thread(target=run_loop, daemon=True).start()
|
|
155
|
+
|
|
156
|
+
def stop(self):
|
|
157
|
+
"""Request BLE loop stop."""
|
|
158
|
+
logging.info(f"{self.name} - 🛑 Stopping BLE loop...")
|
|
159
|
+
self.ble_stop = True
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from smartx_rfid.schemas.tag import TagSchema
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OnReceive:
|
|
6
|
+
def on_receive(self, data, verbose: bool = False):
|
|
7
|
+
if not isinstance(data, str):
|
|
8
|
+
data = data.decode(errors="ignore")
|
|
9
|
+
data = data.replace("\r", "").replace("\n", "")
|
|
10
|
+
data = data.lower()
|
|
11
|
+
if verbose:
|
|
12
|
+
self.on_event(self.name, "receive", data)
|
|
13
|
+
|
|
14
|
+
if data.startswith("#read:"):
|
|
15
|
+
self.is_reading = data.endswith("on")
|
|
16
|
+
if self.is_reading:
|
|
17
|
+
self.on_start()
|
|
18
|
+
else:
|
|
19
|
+
self.on_stop()
|
|
20
|
+
|
|
21
|
+
elif data.startswith("#t+@"):
|
|
22
|
+
tag = data[4:]
|
|
23
|
+
epc, tid, ant, rssi = tag.split("|")
|
|
24
|
+
current_tag = {
|
|
25
|
+
"epc": epc,
|
|
26
|
+
"tid": tid,
|
|
27
|
+
"ant": int(ant),
|
|
28
|
+
"rssi": int(rssi) * (-1),
|
|
29
|
+
}
|
|
30
|
+
self.on_tag(current_tag)
|
|
31
|
+
|
|
32
|
+
elif len(data) == 24:
|
|
33
|
+
self.on_tag(data)
|
|
34
|
+
|
|
35
|
+
elif data.startswith("#set_cmd:"):
|
|
36
|
+
logging.info(f"{self.name} - CONFIG -> {data[data.index(':') + 1 :]}")
|
|
37
|
+
|
|
38
|
+
elif data == "#tags_cleared":
|
|
39
|
+
self.on_event(self.name, "tags_cleared", True)
|
|
40
|
+
|
|
41
|
+
def on_start(self):
|
|
42
|
+
self.clear_tags()
|
|
43
|
+
self.on_event(self.name, "reading", True)
|
|
44
|
+
|
|
45
|
+
def on_stop(self):
|
|
46
|
+
self.on_event(self.name, "reading", False)
|
|
47
|
+
|
|
48
|
+
def on_tag(self, tag: dict):
|
|
49
|
+
try:
|
|
50
|
+
tag_data = TagSchema(**tag)
|
|
51
|
+
tag = tag_data.model_dump()
|
|
52
|
+
self.on_event(self.name, "tag", tag)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logging.error(f"{self.name} - Invalid tag data: {e}")
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RfidCommands:
|
|
5
|
+
def start_inventory(self):
|
|
6
|
+
self.write("#READ:ON")
|
|
7
|
+
|
|
8
|
+
def stop_inventory(self):
|
|
9
|
+
self.write("#READ:OFF")
|
|
10
|
+
|
|
11
|
+
def clear_tags(self):
|
|
12
|
+
self.write("#CLEAR")
|
|
13
|
+
|
|
14
|
+
def config_reader(self):
|
|
15
|
+
set_cmd = "#set_cmd:"
|
|
16
|
+
|
|
17
|
+
# ANTENNAS
|
|
18
|
+
antennas = self.ant_dict
|
|
19
|
+
for antenna in antennas:
|
|
20
|
+
ant = antennas.get(antenna)
|
|
21
|
+
ant_cmd = f"|set_ant:{antenna},{ant.get('active')},{ant.get('power')},{abs(ant.get('rssi'))}"
|
|
22
|
+
set_cmd += ant_cmd
|
|
23
|
+
|
|
24
|
+
# SESSION
|
|
25
|
+
set_cmd += f"|SESSION:{self.session}"
|
|
26
|
+
|
|
27
|
+
# START_READING
|
|
28
|
+
set_cmd += f"|START_READING:{self.start_reading}"
|
|
29
|
+
|
|
30
|
+
# GPI_START
|
|
31
|
+
set_cmd += f"|GPI_START:{self.gpi_start}"
|
|
32
|
+
|
|
33
|
+
# IGNORE_READ
|
|
34
|
+
set_cmd += f"|IGNORE_READ:{self.ignore_read}"
|
|
35
|
+
|
|
36
|
+
# ALWAYS_SEND
|
|
37
|
+
set_cmd += f"|ALWAYS_SEND:{self.always_send}"
|
|
38
|
+
|
|
39
|
+
# SIMPLE_SEND
|
|
40
|
+
set_cmd += f"|SIMPLE_SEND:{self.simple_send}"
|
|
41
|
+
|
|
42
|
+
# KEYBOARD
|
|
43
|
+
set_cmd += f"|KEYBOARD:{self.keyboard}"
|
|
44
|
+
|
|
45
|
+
# BUZZER
|
|
46
|
+
set_cmd += f"|BUZZER:{self.buzzer}"
|
|
47
|
+
|
|
48
|
+
# DECODE_GTIN
|
|
49
|
+
set_cmd += f"|DECODE_GTIN:{self.decode_gtin}"
|
|
50
|
+
|
|
51
|
+
set_cmd = set_cmd.lower()
|
|
52
|
+
set_cmd = set_cmd.replace("true", "on").replace("false", "off")
|
|
53
|
+
self.write(set_cmd)
|
|
54
|
+
|
|
55
|
+
# OTHER CONFIG
|
|
56
|
+
self.write(f"#hotspot:{'on' if self.hotspot else 'off'}")
|
|
57
|
+
self.write(f"#prefix:{self.prefix}")
|
|
58
|
+
if self.protected_inventory_password is not None:
|
|
59
|
+
self.write(f"#protected_inventory:on;{self.protected_inventory_password}")
|
|
60
|
+
else:
|
|
61
|
+
self.write("#protected_inventory:off")
|
|
62
|
+
|
|
63
|
+
# Start Reading
|
|
64
|
+
if self.start_reading:
|
|
65
|
+
self.start_inventory()
|
|
66
|
+
else:
|
|
67
|
+
self.stop_inventory()
|
|
68
|
+
|
|
69
|
+
async def auto_clear(self):
|
|
70
|
+
while True:
|
|
71
|
+
await asyncio.sleep(30)
|
|
72
|
+
if self.is_connected:
|
|
73
|
+
self.clear_tags()
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
import serial.tools.list_ports
|
|
5
|
+
import serial_asyncio
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SerialProtocol(asyncio.Protocol):
|
|
9
|
+
def connection_made(self, transport):
|
|
10
|
+
self.transport = transport
|
|
11
|
+
self.is_connected = True
|
|
12
|
+
logging.info(f"{self.name} - ✅ Serial connection successfully established.")
|
|
13
|
+
self.on_connected()
|
|
14
|
+
|
|
15
|
+
def data_received(self, data):
|
|
16
|
+
self.rx_buffer += data
|
|
17
|
+
|
|
18
|
+
while b"\n" in self.rx_buffer:
|
|
19
|
+
idx = self.rx_buffer.index(b"\n")
|
|
20
|
+
packet = self.rx_buffer[:idx]
|
|
21
|
+
self.rx_buffer = self.rx_buffer[idx + 1 :]
|
|
22
|
+
self.on_receive(packet)
|
|
23
|
+
|
|
24
|
+
def connection_lost(self, exc):
|
|
25
|
+
logging.warning(f"{self.name} - ⚠️ Serial connection lost.")
|
|
26
|
+
self.transport = None
|
|
27
|
+
self.is_connected = False
|
|
28
|
+
self.on_event(self.name, "connection", False)
|
|
29
|
+
|
|
30
|
+
if self.on_con_lost:
|
|
31
|
+
self.on_con_lost.set()
|
|
32
|
+
|
|
33
|
+
def write_serial(self, to_send, verbose=True):
|
|
34
|
+
if self.transport:
|
|
35
|
+
if verbose:
|
|
36
|
+
logging.info(f"{self.name} - 📤 Sending: {to_send}")
|
|
37
|
+
if isinstance(to_send, str):
|
|
38
|
+
to_send += "\n"
|
|
39
|
+
to_send = to_send.encode() # convert string to bytes
|
|
40
|
+
self.transport.write(to_send)
|
|
41
|
+
else:
|
|
42
|
+
logging.warning(f"{self.name} - ❌ Send attempt failed: connection not established.")
|
|
43
|
+
|
|
44
|
+
async def connect_serial(self):
|
|
45
|
+
"""Serial connection/reconnection loop"""
|
|
46
|
+
loop = asyncio.get_running_loop()
|
|
47
|
+
|
|
48
|
+
asyncio.create_task(self.auto_clear())
|
|
49
|
+
|
|
50
|
+
while True:
|
|
51
|
+
self.on_con_lost = asyncio.Event()
|
|
52
|
+
|
|
53
|
+
# If AUTO mode, try to detect port by VID/PID
|
|
54
|
+
if self.is_auto:
|
|
55
|
+
logging.info(f"{self.name} - 🔍 Auto-detecting port by VID={self.vid:04x} and PID={self.pid:04x}...")
|
|
56
|
+
ports = serial.tools.list_ports.comports()
|
|
57
|
+
found_port = None
|
|
58
|
+
for p in ports:
|
|
59
|
+
# p.vid and p.pid are integers (e.g. 0x0001 == 1 decimal)
|
|
60
|
+
if p.vid == self.vid and p.pid == self.pid:
|
|
61
|
+
found_port = p.device
|
|
62
|
+
logging.info(f"{self.name} - ✅ Detected port: {found_port}")
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
if found_port is None:
|
|
66
|
+
logging.info(f"{self.name} - ⚠️ No port with VID={self.vid} and PID={self.pid} found.")
|
|
67
|
+
logging.info(f"{self.name} - ⏳ Retrying in {self.reconnection_time} seconds...")
|
|
68
|
+
await asyncio.sleep(self.reconnection_time)
|
|
69
|
+
continue # try to detect again in next loop
|
|
70
|
+
else:
|
|
71
|
+
self.connection = found_port
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
logging.info(f"{self.name} - 🔌 Trying to connect to {self.connection} at {self.baudrate} bps...")
|
|
75
|
+
await serial_asyncio.create_serial_connection(
|
|
76
|
+
loop, lambda: self, self.connection, baudrate=self.baudrate
|
|
77
|
+
)
|
|
78
|
+
logging.info(f"{self.name} - 🟢 Successfully connected.")
|
|
79
|
+
await self.on_con_lost.wait()
|
|
80
|
+
logging.info(f"{self.name} - 🔄 Connection lost. Attempting to reconnect...")
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logging.warning(f"{self.name} - ❌ Connection error: {e}")
|
|
83
|
+
|
|
84
|
+
# If in AUTO mode, reset port to "AUTO" to force detection next loop
|
|
85
|
+
if self.is_auto:
|
|
86
|
+
self.connection = "AUTO"
|
|
87
|
+
|
|
88
|
+
logging.info(f"{self.name} - ⏳ Waiting {self.reconnection_time} seconds before retrying...")
|
|
89
|
+
await asyncio.sleep(self.reconnection_time)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import socket
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TCPHelpers:
|
|
7
|
+
async def monitor_connection(self):
|
|
8
|
+
while self.is_connected:
|
|
9
|
+
await asyncio.sleep(self.reconnection_time)
|
|
10
|
+
if (self.writer and self.writer.is_closing()) or (self.reader and self.reader.at_eof()):
|
|
11
|
+
self.is_connected = False
|
|
12
|
+
logging.info(f"{self.name} - [DISCONNECTED] Socket closed.")
|
|
13
|
+
break
|
|
14
|
+
|
|
15
|
+
await self.write_tcp("ping", verbose=False)
|
|
16
|
+
|
|
17
|
+
async def receive_data_tcp(self):
|
|
18
|
+
buffer = ""
|
|
19
|
+
try:
|
|
20
|
+
while True:
|
|
21
|
+
try:
|
|
22
|
+
data = await asyncio.wait_for(self.reader.read(1024), timeout=0.1)
|
|
23
|
+
except asyncio.TimeoutError:
|
|
24
|
+
# Timeout: process what's in the buffer as a command
|
|
25
|
+
if buffer:
|
|
26
|
+
self.on_receive(buffer.strip())
|
|
27
|
+
buffer = ""
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
if not data:
|
|
31
|
+
raise ConnectionError("Connection lost")
|
|
32
|
+
|
|
33
|
+
buffer += data.decode(errors="ignore")
|
|
34
|
+
|
|
35
|
+
while "\n" in buffer:
|
|
36
|
+
line, buffer = buffer.split("\n", 1)
|
|
37
|
+
self.on_receive(line.strip())
|
|
38
|
+
|
|
39
|
+
except Exception as e:
|
|
40
|
+
if self.is_connected:
|
|
41
|
+
self.is_connected = False
|
|
42
|
+
logging.warning(f"[RECEIVE ERROR] {e}")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class TCPProtocol(TCPHelpers):
|
|
46
|
+
async def connect_tcp(self, ip, port):
|
|
47
|
+
while True:
|
|
48
|
+
try:
|
|
49
|
+
logging.info(f"Connecting: {self.name} - {ip}:{port}")
|
|
50
|
+
|
|
51
|
+
# Verifica IP antes (evita travar no DNS)
|
|
52
|
+
try:
|
|
53
|
+
resolved_ip = socket.gethostbyname(ip)
|
|
54
|
+
except OSError:
|
|
55
|
+
raise ValueError(f"Invalid IP address: {ip}")
|
|
56
|
+
|
|
57
|
+
# Tenta abrir conexão com timeout real
|
|
58
|
+
connect_task = asyncio.open_connection(resolved_ip, port)
|
|
59
|
+
self.reader, self.writer = await asyncio.wait_for(connect_task, timeout=3)
|
|
60
|
+
|
|
61
|
+
self.is_connected = True
|
|
62
|
+
self.on_connected()
|
|
63
|
+
logging.info(f"✅ [CONNECTED] {self.name} - {ip}:{port}")
|
|
64
|
+
|
|
65
|
+
# Cria tasks de leitura e monitoramento
|
|
66
|
+
tasks = [
|
|
67
|
+
asyncio.create_task(self.receive_data_tcp()),
|
|
68
|
+
asyncio.create_task(self.monitor_connection()),
|
|
69
|
+
asyncio.create_task(self.periodic_ping(10)),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
# Espera até que uma delas finalize
|
|
73
|
+
done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
|
74
|
+
|
|
75
|
+
# Cancela o resto
|
|
76
|
+
for t in pending:
|
|
77
|
+
t.cancel()
|
|
78
|
+
|
|
79
|
+
self.is_connected = False
|
|
80
|
+
self.on_event(self.name, "connection", False)
|
|
81
|
+
logging.info(f"🔌 [DISCONNECTED] {self.name} - Reconnecting...")
|
|
82
|
+
|
|
83
|
+
except asyncio.TimeoutError:
|
|
84
|
+
logging.warning(f"⏱️ [TIMEOUT] {self.name} - No response from {ip}:{port}")
|
|
85
|
+
continue
|
|
86
|
+
except ValueError as e:
|
|
87
|
+
logging.warning(f"❌ [INVALID IP] {self.name}: {e}")
|
|
88
|
+
continue
|
|
89
|
+
except OSError as e:
|
|
90
|
+
logging.warning(f"💥 [NETWORK ERROR] {self.name}: {e}")
|
|
91
|
+
continue
|
|
92
|
+
except Exception as e:
|
|
93
|
+
logging.warning(f"❌ [UNEXPECTED ERROR] {self.name}: {e}")
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
# Garante desconexão limpa
|
|
97
|
+
if self.writer:
|
|
98
|
+
try:
|
|
99
|
+
self.writer.close()
|
|
100
|
+
await self.writer.wait_closed()
|
|
101
|
+
except Exception:
|
|
102
|
+
pass
|
|
103
|
+
self.writer = None
|
|
104
|
+
self.reader = None
|
|
105
|
+
self.is_connected = False
|
|
106
|
+
|
|
107
|
+
logging.info(f"🔁 Retrying {self.name} in {self.reconnection_time}s...")
|
|
108
|
+
await asyncio.sleep(self.reconnection_time)
|
|
109
|
+
|
|
110
|
+
async def write_tcp(self, data: str, verbose: bool = True):
|
|
111
|
+
if self.is_connected and self.writer:
|
|
112
|
+
try:
|
|
113
|
+
data = data + "\n"
|
|
114
|
+
self.writer.write(data.encode())
|
|
115
|
+
await self.writer.drain()
|
|
116
|
+
if verbose:
|
|
117
|
+
logging.info(f"{self.name} - [SENT] {data.strip()}")
|
|
118
|
+
except Exception as e:
|
|
119
|
+
logging.warning(f"{self.name} - [SEND ERROR] {e}")
|
|
120
|
+
if self.is_connected:
|
|
121
|
+
self.is_connected = False
|
|
122
|
+
self.on_event(self.name, "connection", False)
|
|
123
|
+
|
|
124
|
+
async def periodic_ping(self, interval: int):
|
|
125
|
+
while self.is_connected:
|
|
126
|
+
await asyncio.sleep(interval)
|
|
127
|
+
await self.write_tcp("ping", verbose=False)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from smartx_rfid.schemas.tag import WriteTagValidator
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class WriteCommands:
|
|
6
|
+
def write_epc(self, target_identifier: str | None, target_value: str | None, new_epc: str, password: str):
|
|
7
|
+
try:
|
|
8
|
+
validated_tag = WriteTagValidator(
|
|
9
|
+
target_identifier=target_identifier,
|
|
10
|
+
target_value=target_value,
|
|
11
|
+
new_epc=new_epc,
|
|
12
|
+
password=password,
|
|
13
|
+
)
|
|
14
|
+
except Exception as e:
|
|
15
|
+
logging.warning(f"{self.name} - {e}")
|
|
16
|
+
return
|
|
17
|
+
identifier = validated_tag.target_identifier
|
|
18
|
+
value = validated_tag.target_value
|
|
19
|
+
epc = validated_tag.new_epc
|
|
20
|
+
password = validated_tag.password
|
|
21
|
+
logging.info(f"{self.name} - Writing EPC: {epc} (Current: {identifier}={value})")
|
|
22
|
+
if identifier is None:
|
|
23
|
+
self.write(f"#WRITE:{epc};{password}", False)
|
|
24
|
+
else:
|
|
25
|
+
self.write(f"#WRITE:{epc};{password};{identifier};{value}", False)
|