pyllrp 3.1.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.
- pyllrp/AutoDetect.py +100 -0
- pyllrp/LLRPConnector.py +183 -0
- pyllrp/ParseDef.py +151 -0
- pyllrp/TagInventory.py +204 -0
- pyllrp/TagWriter.py +181 -0
- pyllrp/TinyExample.py +132 -0
- pyllrp/__init__.py +0 -0
- pyllrp/llrpdef.py +2578 -0
- pyllrp/pyllrp.py +1040 -0
- pyllrp/pypi.py +182 -0
- pyllrp-3.1.0.dist-info/METADATA +105 -0
- pyllrp-3.1.0.dist-info/RECORD +14 -0
- pyllrp-3.1.0.dist-info/WHEEL +5 -0
- pyllrp-3.1.0.dist-info/top_level.txt +1 -0
pyllrp/AutoDetect.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import socket
|
|
2
|
+
import threading
|
|
3
|
+
from queue import Queue, Empty
|
|
4
|
+
try:
|
|
5
|
+
from .pyllrp import UnpackMessageFromSocket, ConnectionAttemptEvent_Parameter, ConnectionAttemptStatusType
|
|
6
|
+
except ImportError:
|
|
7
|
+
from pyllrp import UnpackMessageFromSocket, ConnectionAttemptEvent_Parameter, ConnectionAttemptStatusType
|
|
8
|
+
|
|
9
|
+
def is_impinj_reader( ip, port ):
|
|
10
|
+
# Connect to the socket.
|
|
11
|
+
readerSocket = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
|
|
12
|
+
readerSocket.settimeout( 4.0 )
|
|
13
|
+
try:
|
|
14
|
+
readerSocket.connect( (ip, port) )
|
|
15
|
+
except Exception as e:
|
|
16
|
+
return False
|
|
17
|
+
|
|
18
|
+
# Get the reader's default response.
|
|
19
|
+
try:
|
|
20
|
+
response = UnpackMessageFromSocket( readerSocket )
|
|
21
|
+
except Exception as e:
|
|
22
|
+
return False
|
|
23
|
+
finally:
|
|
24
|
+
readerSocket.close()
|
|
25
|
+
readerSocket = None
|
|
26
|
+
|
|
27
|
+
# Check if the response is valid.
|
|
28
|
+
try:
|
|
29
|
+
connectionAttemptEvent = response.getFirstParameterByClass(ConnectionAttemptEvent_Parameter)
|
|
30
|
+
return connectionAttemptEvent and connectionAttemptEvent.Status == ConnectionAttemptStatusType.Success
|
|
31
|
+
except Exception as e:
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
def connection_tester(job_q, results_q, port):
|
|
35
|
+
while results_q.empty(): # Quit as soon as we detect a reader.
|
|
36
|
+
ip = job_q.get()
|
|
37
|
+
if ip is None:
|
|
38
|
+
break
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
if is_impinj_reader(ip, port):
|
|
42
|
+
results_q.put(ip)
|
|
43
|
+
except Exception as e:
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
def get_my_ip():
|
|
47
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
|
48
|
+
try:
|
|
49
|
+
# doesn't have to be reachable
|
|
50
|
+
s.connect(('10.255.255.255', 1))
|
|
51
|
+
IP = s.getsockname()[0]
|
|
52
|
+
except Exception as e:
|
|
53
|
+
IP = '127.0.0.1'
|
|
54
|
+
finally:
|
|
55
|
+
s.close()
|
|
56
|
+
return IP
|
|
57
|
+
|
|
58
|
+
def check_network(pool_size=255, port=5084):
|
|
59
|
+
""" Check the network for readers. """
|
|
60
|
+
""" Multithread the calls for performance. """
|
|
61
|
+
|
|
62
|
+
# get my IP and compose a base like 192.168.1.xxx
|
|
63
|
+
ip_parts = get_my_ip().split('.')
|
|
64
|
+
base_ip = ip_parts[0] + '.' + ip_parts[1] + '.' + ip_parts[2] + '.'
|
|
65
|
+
|
|
66
|
+
# prepare the jobs queue
|
|
67
|
+
jobs = Queue()
|
|
68
|
+
results = Queue()
|
|
69
|
+
|
|
70
|
+
pool = [threading.Thread(target=connection_tester, args=(jobs, results, port)) for i in range(pool_size)]
|
|
71
|
+
for p in pool:
|
|
72
|
+
p.start()
|
|
73
|
+
|
|
74
|
+
# cue the ping processes
|
|
75
|
+
my_low_order = int( ip_parts[3] )
|
|
76
|
+
low_orders = sorted( (i for i in range(1,255) if i != my_low_order), key=lambda v: abs(v-my_low_order) )
|
|
77
|
+
for i in low_orders:
|
|
78
|
+
jobs.put('{}{}'.format(base_ip, i))
|
|
79
|
+
|
|
80
|
+
for p in pool:
|
|
81
|
+
jobs.put(None)
|
|
82
|
+
|
|
83
|
+
for p in pool:
|
|
84
|
+
p.join()
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
return results.get(False)
|
|
88
|
+
except Empty:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
GetDefaultHost = get_my_ip
|
|
92
|
+
|
|
93
|
+
def findImpinjHost( impinjPort=5084 ):
|
|
94
|
+
return check_network(port=impinjPort)
|
|
95
|
+
|
|
96
|
+
def AutoDetect( impinjPort=5084 ):
|
|
97
|
+
return findImpinjHost(impinjPort), GetDefaultHost()
|
|
98
|
+
|
|
99
|
+
if __name__ == '__main__':
|
|
100
|
+
print( AutoDetect() )
|
pyllrp/LLRPConnector.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
from queue import Queue, Empty
|
|
3
|
+
import socket
|
|
4
|
+
import datetime
|
|
5
|
+
import threading
|
|
6
|
+
try:
|
|
7
|
+
from .pyllrp import *
|
|
8
|
+
except Exception as e:
|
|
9
|
+
from pyllrp import *
|
|
10
|
+
|
|
11
|
+
class LLRPConnector:
|
|
12
|
+
#--------------------------------------------------------------------------
|
|
13
|
+
#
|
|
14
|
+
# A simple LLRP reader connection manager.
|
|
15
|
+
#
|
|
16
|
+
# Supports connecting to the reader, transacting commands, message handlers,
|
|
17
|
+
# and asynchronous/synchronous monitoring of the reader socket.
|
|
18
|
+
#
|
|
19
|
+
def __init__( self ):
|
|
20
|
+
self.TimeoutSecs = 6 # Time for the reader to respond.
|
|
21
|
+
self._reset()
|
|
22
|
+
self.handlers = {}
|
|
23
|
+
|
|
24
|
+
def _reset( self ):
|
|
25
|
+
''' Reset all internal fields. '''
|
|
26
|
+
self.host = None
|
|
27
|
+
self.port = None
|
|
28
|
+
self.readerSocket = None
|
|
29
|
+
self.thread = None
|
|
30
|
+
self.shutdownQ = None # Used to shutdown the thread.
|
|
31
|
+
self.keepGoing = False
|
|
32
|
+
self.timeCorrection = None
|
|
33
|
+
|
|
34
|
+
def connect( self, host, port = 5084 ):
|
|
35
|
+
''' Connect to a reader. '''
|
|
36
|
+
self._reset()
|
|
37
|
+
self.readerSocket = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
|
|
38
|
+
|
|
39
|
+
# Set a timeout for the socket. This is also the maximum time it will take to shut down the listener.
|
|
40
|
+
self.readerSocket.settimeout( self.TimeoutSecs )
|
|
41
|
+
self.readerSocket.connect( (host, port) )
|
|
42
|
+
|
|
43
|
+
self.host = host
|
|
44
|
+
self.port = port
|
|
45
|
+
|
|
46
|
+
# Expecting READER_EVENT_NOTIFICATION message.
|
|
47
|
+
response = UnpackMessageFromSocket( self.readerSocket )
|
|
48
|
+
tNow = datetime.datetime.now() # Get the time here to minimize latency.
|
|
49
|
+
|
|
50
|
+
# Check if the connection succeeded.
|
|
51
|
+
connectionAttemptEvent = response.getFirstParameterByClass(ConnectionAttemptEvent_Parameter)
|
|
52
|
+
if connectionAttemptEvent and connectionAttemptEvent.Status != ConnectionAttemptStatusType.Success:
|
|
53
|
+
self.disconnect()
|
|
54
|
+
raise EnvironmentError(
|
|
55
|
+
connectionAttemptEvent.Status,
|
|
56
|
+
ConnectionAttemptStatusType.getName(connectionAttemptEvent.Status).replace('_',' ')
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
self.keepGoing = True
|
|
60
|
+
|
|
61
|
+
# Compute a correction between the reader's time and the computer's time.
|
|
62
|
+
self.timeCorrection = None
|
|
63
|
+
try:
|
|
64
|
+
microseconds = response.getFirstParameterByClass(UTCTimestamp_Parameter).Microseconds
|
|
65
|
+
readerTime = datetime.datetime.utcfromtimestamp( microseconds / 1000000.0 )
|
|
66
|
+
self.timeCorrection = tNow - readerTime
|
|
67
|
+
except Exception as e:
|
|
68
|
+
self.disconnect()
|
|
69
|
+
raise ValueError('Missing Timestamp: ' + response.__repr__())
|
|
70
|
+
|
|
71
|
+
return response
|
|
72
|
+
|
|
73
|
+
def tagTimeToComputerTime( self, tagTime ):
|
|
74
|
+
# Time is in microseconds from Jan 1, 1970.
|
|
75
|
+
return datetime.datetime.utcfromtimestamp( tagTime / 1000000.0 ) + self.timeCorrection
|
|
76
|
+
|
|
77
|
+
def disconnect( self ):
|
|
78
|
+
''' Disconnect from a reader. Also stops the listener. '''
|
|
79
|
+
self.timeCorrection = None
|
|
80
|
+
if not self.readerSocket:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
if self.isListening():
|
|
84
|
+
self.stopListener()
|
|
85
|
+
|
|
86
|
+
# Send the reader a disconnect message.
|
|
87
|
+
response = None
|
|
88
|
+
try:
|
|
89
|
+
response = self.transact( CLOSE_CONNECTION_Message() )
|
|
90
|
+
except Exception:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
self.readerSocket.close()
|
|
94
|
+
self.readerSocket = None
|
|
95
|
+
return response
|
|
96
|
+
|
|
97
|
+
def addHandler( self, messageClass, handlerFunc ):
|
|
98
|
+
''' Add a handler for a specific message type. '''
|
|
99
|
+
''' Support multiple handlers for the same message type. '''
|
|
100
|
+
self.handlers.setdefault( messageClass, [] ).append( handlerFunc )
|
|
101
|
+
|
|
102
|
+
def removeHandler( self, messageClass, handlerFunc = None ):
|
|
103
|
+
''' Remove a handler for a specific message type from the reader.
|
|
104
|
+
If handlerFunc is None, all handlers for the given messageClass will be removed.
|
|
105
|
+
If handlerFunc is not None, only the specific handler is removed.
|
|
106
|
+
'''
|
|
107
|
+
if handlerFunc is None:
|
|
108
|
+
try:
|
|
109
|
+
del self.handlers[messageClass]
|
|
110
|
+
except KeyError:
|
|
111
|
+
pass
|
|
112
|
+
else:
|
|
113
|
+
while True:
|
|
114
|
+
try:
|
|
115
|
+
self.handlers[messageClass].remove( handlerFunc )
|
|
116
|
+
except (KeyError, ValueError):
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
def removeAllHandlers( self ):
|
|
120
|
+
self.handlers = {}
|
|
121
|
+
|
|
122
|
+
def transact( self, message ):
|
|
123
|
+
''' Send a message to the reader and wait for the response. '''
|
|
124
|
+
assert not self.isListening(), 'Cannot perform transact() while listen thread is active. Stop it first with stopListener().'
|
|
125
|
+
message.send( self.readerSocket )
|
|
126
|
+
response = WaitForMessage( message.MessageID, self.readerSocket, self.callHandler )
|
|
127
|
+
return response
|
|
128
|
+
|
|
129
|
+
def checkKeepGoing( self ):
|
|
130
|
+
''' Check if we should continue the reader thread. '''
|
|
131
|
+
if not self.keepGoing:
|
|
132
|
+
return False
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
# Check the shutdown queue for a message. If there is one, shutdown.
|
|
136
|
+
self.shutdownQ.get( False )
|
|
137
|
+
self.shutdownQ.task_done()
|
|
138
|
+
self.keepGoing = False
|
|
139
|
+
return False
|
|
140
|
+
except (AttributeError, Empty):
|
|
141
|
+
return True
|
|
142
|
+
|
|
143
|
+
def callHandler( self, message ):
|
|
144
|
+
''' Call all the handlers for this message. '''
|
|
145
|
+
for cb in (self.handlers.get(message.__class__, None) or self.handlers.get('default', [])):
|
|
146
|
+
cb( self, message )
|
|
147
|
+
|
|
148
|
+
def listen( self ):
|
|
149
|
+
''' Listen for messages from the reader. '''
|
|
150
|
+
# Calling this by itself will block.
|
|
151
|
+
# Recommended usage is to use startListener and stopListener.
|
|
152
|
+
while self.checkKeepGoing():
|
|
153
|
+
try:
|
|
154
|
+
response = UnpackMessageFromSocket( self.readerSocket )
|
|
155
|
+
except socket.timeout:
|
|
156
|
+
continue
|
|
157
|
+
self.callHandler( response )
|
|
158
|
+
|
|
159
|
+
def startListener( self ):
|
|
160
|
+
''' Starts a thread to listen to the reader. '''
|
|
161
|
+
assert self.readerSocket, 'Cannot start listener without a successful connection.'
|
|
162
|
+
assert not self.thread, 'Listener is already running. Stop it first with stopListener().'
|
|
163
|
+
self.shutdownQ = Queue()
|
|
164
|
+
self.keepGoing = True
|
|
165
|
+
self.thread = threading.Thread( target = self.listen, name='LLRP Listener' )
|
|
166
|
+
self.thread.daemon = True
|
|
167
|
+
self.thread.start()
|
|
168
|
+
|
|
169
|
+
def stopListener( self ):
|
|
170
|
+
''' Stops the thread listening to the reader. '''
|
|
171
|
+
self.shutdownQ.put( 'Shutdown' )
|
|
172
|
+
self.thread.join() # Wait for the thread to terminate.
|
|
173
|
+
self.shutdownQ = None
|
|
174
|
+
self.thread = None
|
|
175
|
+
|
|
176
|
+
def isListening( self ):
|
|
177
|
+
return self.thread and self.thread.is_alive()
|
|
178
|
+
|
|
179
|
+
def __enter__( self ):
|
|
180
|
+
return self
|
|
181
|
+
|
|
182
|
+
def __exit__(self, type, value, traceback):
|
|
183
|
+
self.disconnect()
|
pyllrp/ParseDef.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#---------------------------------------------------------------------------
|
|
2
|
+
#
|
|
3
|
+
# Reformats the llrpdef.xml file into a Python-formatted description llrpdef.py
|
|
4
|
+
#
|
|
5
|
+
# The reason we so this is so we avoid a dependency on the xml parser in deployment.
|
|
6
|
+
# It is also much faster to use the .pyc file rather than parsing XML again
|
|
7
|
+
# on startup.
|
|
8
|
+
#
|
|
9
|
+
import sys
|
|
10
|
+
import pprint
|
|
11
|
+
from xml.dom.minidom import parse
|
|
12
|
+
|
|
13
|
+
# Map the xml field types to our types (bitstring standard, and our custom ones).
|
|
14
|
+
fieldMap = {
|
|
15
|
+
'u1': 'bool',
|
|
16
|
+
'u2': 'bits:2',
|
|
17
|
+
'u8': 'uintbe:8',
|
|
18
|
+
'u16': 'uintbe:16',
|
|
19
|
+
'u32': 'uintbe:32',
|
|
20
|
+
'u64': 'uintbe:64',
|
|
21
|
+
'u96': 'uintbe:96',
|
|
22
|
+
|
|
23
|
+
's8': 'intbe:8',
|
|
24
|
+
's16': 'intbe:16',
|
|
25
|
+
's32': 'intbe:32',
|
|
26
|
+
's64': 'intbe:64',
|
|
27
|
+
|
|
28
|
+
'utf8v': 'string',
|
|
29
|
+
|
|
30
|
+
'u1v': 'bitarray',
|
|
31
|
+
'u8v': 'array:8',
|
|
32
|
+
'u16v': 'array:16',
|
|
33
|
+
'u32v': 'array:32',
|
|
34
|
+
|
|
35
|
+
'bytesToEnd': 'bytesToEnd', # Used for the Custom message type.
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def toAscii( s ):
|
|
39
|
+
return s.encode('ascii', 'ignore').decode( 'ascii' )
|
|
40
|
+
|
|
41
|
+
def getEnum( e ):
|
|
42
|
+
Name = toAscii(e.attributes['name'].value)
|
|
43
|
+
Choices = [ (int(toAscii(c.attributes['value'].value)), toAscii(c.attributes['name'].value))
|
|
44
|
+
for c in e.childNodes if c.nodeName == 'entry' ]
|
|
45
|
+
return {'name':Name, 'choices':tuple(Choices) }
|
|
46
|
+
|
|
47
|
+
def getParameterMessage( n, isMessage ):
|
|
48
|
+
Name = toAscii(n.attributes['name'].value)
|
|
49
|
+
|
|
50
|
+
Fields = []
|
|
51
|
+
Parameters = []
|
|
52
|
+
|
|
53
|
+
sys.stdout.write( '{} {}\n'.format(Name, 'Message' if isMessage else 'Parameter') )
|
|
54
|
+
try:
|
|
55
|
+
TypeNum = int(n.attributes['typeNum'].value)
|
|
56
|
+
except KeyError:
|
|
57
|
+
# This is a custom parameter or message.
|
|
58
|
+
TypeNum = 1023 # Code for custom Message and Parameter.
|
|
59
|
+
Fields.append( {'name': 'VendorIdentifier', 'type': 'uintbe:32', 'default': vendors[toAscii(n.attributes['vendor'].value)]} )
|
|
60
|
+
if isMessage:
|
|
61
|
+
Fields.append( {'name': 'MessageSubtype', 'type': 'uintbe:8', 'default': int(n.attributes['subtype'].value)} )
|
|
62
|
+
else:
|
|
63
|
+
Fields.append( {'name': 'ParameterSubtype', 'type': 'uintbe:32', 'default': int(n.attributes['subtype'].value)} )
|
|
64
|
+
|
|
65
|
+
for c in n.childNodes:
|
|
66
|
+
if c.nodeName == 'field':
|
|
67
|
+
fieldInfo = { 'name': toAscii(c.attributes['name'].value), 'type': fieldMap[toAscii(c.attributes['type'].value)] }
|
|
68
|
+
try:
|
|
69
|
+
fieldInfo['enumeration'] = toAscii(c.attributes['enumeration'].value)
|
|
70
|
+
except Exception:
|
|
71
|
+
pass
|
|
72
|
+
try:
|
|
73
|
+
fieldInfo['format'] = toAscii(c.attributes['format'].value)
|
|
74
|
+
except Exception:
|
|
75
|
+
pass
|
|
76
|
+
Fields.append( fieldInfo )
|
|
77
|
+
elif c.nodeName == 'reserved':
|
|
78
|
+
bitCount = int(toAscii(c.attributes['bitCount'].value))
|
|
79
|
+
code = 'skip:%d' % bitCount
|
|
80
|
+
Fields.append( {'name':code, 'type':code} )
|
|
81
|
+
elif c.nodeName == 'parameter' or c.nodeName == 'choice':
|
|
82
|
+
repeat = toAscii(c.attributes['repeat'].value)
|
|
83
|
+
minMax = repeat.split('-')
|
|
84
|
+
if len(minMax) == 1:
|
|
85
|
+
rMin = rMax = int(repeat)
|
|
86
|
+
else:
|
|
87
|
+
rMin = int(minMax[0])
|
|
88
|
+
rMax = int(minMax[1]) if minMax[1] != 'N' else 99999
|
|
89
|
+
Parameter = toAscii(c.attributes['type'].value)
|
|
90
|
+
Parameters.append( {'parameter':Parameter, 'repeat': (rMin, rMax), 'choice': int(c.nodeName == 'choice')} )
|
|
91
|
+
pm = {'name':Name, 'typeNum':TypeNum}
|
|
92
|
+
if Fields:
|
|
93
|
+
pm['fields'] = tuple(Fields)
|
|
94
|
+
if Parameters:
|
|
95
|
+
pm['parameters'] = tuple(Parameters)
|
|
96
|
+
return pm
|
|
97
|
+
|
|
98
|
+
def getVendorCode( n ):
|
|
99
|
+
name = toAscii(n.attributes['name'].value)
|
|
100
|
+
id = int(n.attributes['vendorID'].value)
|
|
101
|
+
return (name, id)
|
|
102
|
+
|
|
103
|
+
def getChoiceDefinition( n ):
|
|
104
|
+
choices = {}
|
|
105
|
+
name = toAscii(n.attributes['name'].value)
|
|
106
|
+
for c in n.childNodes:
|
|
107
|
+
if c.nodeName == 'parameter':
|
|
108
|
+
choices[toAscii(c.attributes['type'].value)] = name
|
|
109
|
+
return choices
|
|
110
|
+
|
|
111
|
+
enums, parameters, messages = [], [], []
|
|
112
|
+
vendors = {}
|
|
113
|
+
choiceDefinitions = {}
|
|
114
|
+
|
|
115
|
+
llrpDefXml = '../xml/llrp-1x0-def.xml'
|
|
116
|
+
dom = parse( llrpDefXml )
|
|
117
|
+
|
|
118
|
+
vendors.update( dict(getVendorCode(v) for v in dom.getElementsByTagName('vendorDefinition')) )
|
|
119
|
+
enums = [getEnum(e) for e in dom.getElementsByTagName('enumerationDefinition')]
|
|
120
|
+
parameters = [getParameterMessage(p, False) for p in dom.getElementsByTagName('parameterDefinition')]
|
|
121
|
+
messages = [getParameterMessage(m, True) for m in dom.getElementsByTagName('messageDefinition')]
|
|
122
|
+
for c in dom.getElementsByTagName('choiceDefinition'):
|
|
123
|
+
choiceDefinitions.update( getChoiceDefinition(c) )
|
|
124
|
+
del choiceDefinitions['Custom']
|
|
125
|
+
|
|
126
|
+
dom.unlink()
|
|
127
|
+
dom = None
|
|
128
|
+
|
|
129
|
+
llrpCustomDefXml = '../xml/Impinjdef-1.18-private.xml'
|
|
130
|
+
dom = parse( llrpCustomDefXml )
|
|
131
|
+
|
|
132
|
+
vendors.update( dict(getVendorCode(v) for v in dom.getElementsByTagName('vendorDefinition')) )
|
|
133
|
+
enums.extend( getEnum(e) for e in dom.getElementsByTagName('customEnumerationDefinition') )
|
|
134
|
+
parameters.extend( getParameterMessage(p, False) for p in dom.getElementsByTagName('customParameterDefinition') )
|
|
135
|
+
messages.extend( getParameterMessage(m, True) for m in dom.getElementsByTagName('customMessageDefinition') )
|
|
136
|
+
for p in dom.getElementsByTagName('customParameterDefinition'):
|
|
137
|
+
choiceDefinitions[toAscii(p.attributes['name'].value)] = 'Custom'
|
|
138
|
+
|
|
139
|
+
with open('llrpdef.py', 'w', encoding='utf8') as fp:
|
|
140
|
+
fp.write( '#-----------------------------------------------------------\n' )
|
|
141
|
+
fp.write( '# DO NOT EDIT!\n' )
|
|
142
|
+
fp.write( '# MACHINE GENERATED by ParseDef.py\n' )
|
|
143
|
+
fp.write( '# XML sources:\n' )
|
|
144
|
+
fp.write( '# "{}"\n'.format(llrpDefXml) )
|
|
145
|
+
fp.write( '# "{}"\n'.format(llrpCustomDefXml) )
|
|
146
|
+
fp.write( '# see https://github.com/esitarski/pyllrp for details\n' )
|
|
147
|
+
fp.write( '#-----------------------------------------------------------\n' )
|
|
148
|
+
for n in ['vendors', 'enums', 'parameters', 'messages', 'choiceDefinitions']:
|
|
149
|
+
fp.write( '{}='.format(n) )
|
|
150
|
+
pprint.pprint( globals()[n], fp, compact=True )
|
|
151
|
+
fp.write( '\n' )
|
pyllrp/TagInventory.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
import sys
|
|
3
|
+
import time
|
|
4
|
+
import socket
|
|
5
|
+
try:
|
|
6
|
+
from .pyllrp import *
|
|
7
|
+
from .LLRPConnector import LLRPConnector
|
|
8
|
+
except Exception as e:
|
|
9
|
+
from pyllrp import *
|
|
10
|
+
from LLRPConnector import LLRPConnector
|
|
11
|
+
|
|
12
|
+
class TagInventory:
|
|
13
|
+
roSpecID = 123 # Arbitrary roSpecID.
|
|
14
|
+
inventoryParameterSpecID = 1234 # Arbitrary inventory parameter spec id.
|
|
15
|
+
readWaitMilliseconds = 100
|
|
16
|
+
|
|
17
|
+
def __init__( self, host='192.168.10.102', defaultAntennas=None, transmitPower=None, receiverSensitivity=None ):
|
|
18
|
+
self.host = host
|
|
19
|
+
self.connector = None
|
|
20
|
+
self.resetTagInventory()
|
|
21
|
+
self.defaultAntennas = defaultAntennas
|
|
22
|
+
self.transmitPower = transmitPower
|
|
23
|
+
self.receiverSensitivity = receiverSensitivity
|
|
24
|
+
|
|
25
|
+
def resetTagInventory( self ):
|
|
26
|
+
self.tagInventory = set()
|
|
27
|
+
self.tagDetail = []
|
|
28
|
+
self.otherMessages = []
|
|
29
|
+
|
|
30
|
+
def AccessReportHandler( self, connector, accessReport ):
|
|
31
|
+
for tag in accessReport.getTagData():
|
|
32
|
+
tag['Tag'] = HexFormatToStr( tag['EPC'] )
|
|
33
|
+
self.tagInventory.add( tag['Tag'] )
|
|
34
|
+
self.tagDetail.append( tag )
|
|
35
|
+
|
|
36
|
+
def DefaultHandler( self, connector, message ):
|
|
37
|
+
self.otherMessages.append( message )
|
|
38
|
+
|
|
39
|
+
def _getReaderConfigMessage( self ):
|
|
40
|
+
# Change receiver sensitivity (if specified). This value is RFID reader dependent.
|
|
41
|
+
receiverSensitivityParameter = []
|
|
42
|
+
if self.receiverSensitivity is not None:
|
|
43
|
+
receiverSensitivityParameter.append(
|
|
44
|
+
RFReceiver_Parameter(
|
|
45
|
+
ReceiverSensitivity = self.receiverSensitivity
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Change transmit power (if specified). This value is RFID reader dependent.
|
|
50
|
+
transmitPowerParameter = []
|
|
51
|
+
if self.transmitPower is not None:
|
|
52
|
+
transmitPowerParameter.append(
|
|
53
|
+
RFTransmitter_Parameter(
|
|
54
|
+
TransmitPower = self.transmitPower,
|
|
55
|
+
HopTableID = 1, # Ignored in non-frequency-hopping regulatory regions.
|
|
56
|
+
ChannelIndex = 1, # Ignored in non-frequency-hopping regulatory regions.
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return SET_READER_CONFIG_Message( Parameters = [
|
|
61
|
+
AntennaConfiguration_Parameter( AntennaID = 0, Parameters =
|
|
62
|
+
receiverSensitivityParameter +
|
|
63
|
+
transmitPowerParameter + [
|
|
64
|
+
C1G2InventoryCommand_Parameter( Parameters = [
|
|
65
|
+
C1G2SingulationControl_Parameter(
|
|
66
|
+
Session = 0,
|
|
67
|
+
TagPopulation = 100,
|
|
68
|
+
TagTransitTime = 3000,
|
|
69
|
+
),
|
|
70
|
+
] ),
|
|
71
|
+
] ),
|
|
72
|
+
] )
|
|
73
|
+
|
|
74
|
+
def Connect( self ):
|
|
75
|
+
# Create the reader connection.
|
|
76
|
+
self.connector = LLRPConnector()
|
|
77
|
+
|
|
78
|
+
# Connect to the reader.
|
|
79
|
+
try:
|
|
80
|
+
response = self.connector.connect( self.host )
|
|
81
|
+
except socket.timeout:
|
|
82
|
+
self.connector.disconnect()
|
|
83
|
+
raise
|
|
84
|
+
|
|
85
|
+
# Reset to factory defaults.
|
|
86
|
+
response = self.connector.transact( SET_READER_CONFIG_Message(ResetToFactoryDefault = True) )
|
|
87
|
+
assert response.success(), f'SET_READER_CONFIG ResetToFactorDefault fails\n{response}'
|
|
88
|
+
|
|
89
|
+
message = self._getReaderConfigMessage()
|
|
90
|
+
response = self.connector.transact( message )
|
|
91
|
+
assert response.success(), 'SET_READER_CONFIG Configuration fails:\n{}'.format(response)
|
|
92
|
+
|
|
93
|
+
def Disconnect( self ):
|
|
94
|
+
self.connector.disconnect()
|
|
95
|
+
self.connector = None
|
|
96
|
+
|
|
97
|
+
def GetROSpec( self, antennas = None ):
|
|
98
|
+
if antennas is not None:
|
|
99
|
+
if not isinstance(antennas, list):
|
|
100
|
+
antennas = [antennas]
|
|
101
|
+
else:
|
|
102
|
+
antennas = [0]
|
|
103
|
+
|
|
104
|
+
# Create an rospec that reports reads.
|
|
105
|
+
return ADD_ROSPEC_Message( Parameters = [
|
|
106
|
+
ROSpec_Parameter(
|
|
107
|
+
ROSpecID = self.roSpecID,
|
|
108
|
+
CurrentState = ROSpecState.Disabled,
|
|
109
|
+
Parameters = [
|
|
110
|
+
ROBoundarySpec_Parameter( # Configure boundary spec (start and stop triggers for the reader).
|
|
111
|
+
Parameters = [
|
|
112
|
+
ROSpecStartTrigger_Parameter(ROSpecStartTriggerType = ROSpecStartTriggerType.Immediate),
|
|
113
|
+
ROSpecStopTrigger_Parameter(ROSpecStopTriggerType = ROSpecStopTriggerType.Null),
|
|
114
|
+
]
|
|
115
|
+
), # ROBoundarySpec
|
|
116
|
+
AISpec_Parameter( # Antenna Inventory Spec (specifies which antennas and protocol to use)
|
|
117
|
+
AntennaIDs = antennas,
|
|
118
|
+
Parameters = [
|
|
119
|
+
AISpecStopTrigger_Parameter(
|
|
120
|
+
AISpecStopTriggerType = AISpecStopTriggerType.Tag_Observation,
|
|
121
|
+
Parameters = [
|
|
122
|
+
TagObservationTrigger_Parameter(
|
|
123
|
+
TriggerType = TagObservationTriggerType.N_Attempts_To_See_All_Tags_In_FOV_Or_Timeout,
|
|
124
|
+
NumberOfAttempts = 10,
|
|
125
|
+
Timeout = self.readWaitMilliseconds,
|
|
126
|
+
),
|
|
127
|
+
]
|
|
128
|
+
),
|
|
129
|
+
InventoryParameterSpec_Parameter(
|
|
130
|
+
InventoryParameterSpecID = self.inventoryParameterSpecID,
|
|
131
|
+
ProtocolID = AirProtocols.EPCGlobalClass1Gen2,
|
|
132
|
+
),
|
|
133
|
+
]
|
|
134
|
+
), # AISpec
|
|
135
|
+
ROReportSpec_Parameter( # Report spec (specifies how often and what to send from the reader)
|
|
136
|
+
ROReportTrigger = ROReportTriggerType.Upon_N_Tags_Or_End_Of_ROSpec,
|
|
137
|
+
N = 10000,
|
|
138
|
+
Parameters = [
|
|
139
|
+
TagReportContentSelector_Parameter(
|
|
140
|
+
EnableAntennaID = True,
|
|
141
|
+
EnableFirstSeenTimestamp = True,
|
|
142
|
+
EnablePeakRSSI = True,
|
|
143
|
+
),
|
|
144
|
+
]
|
|
145
|
+
), # ROReportSpec
|
|
146
|
+
]
|
|
147
|
+
), # ROSpec_Parameter
|
|
148
|
+
]
|
|
149
|
+
) # ADD_ROSPEC_Message
|
|
150
|
+
|
|
151
|
+
def _prolog( self, antennas = None ):
|
|
152
|
+
# Disable all the rospecs. This command may fail so we ignore the response.
|
|
153
|
+
response = self.connector.transact( DISABLE_ROSPEC_Message(ROSpecID = 0) )
|
|
154
|
+
# Delete our old rospec if it exists. This command might fail so we ignore the response.
|
|
155
|
+
response = self.connector.transact( DELETE_ROSPEC_Message(ROSpecID = self.roSpecID) )
|
|
156
|
+
|
|
157
|
+
# Add callbacks so we can record the tag reads and any other messages from the reader.
|
|
158
|
+
self.resetTagInventory()
|
|
159
|
+
self.connector.addHandler( RO_ACCESS_REPORT_Message, self.AccessReportHandler )
|
|
160
|
+
self.connector.addHandler( 'default', self.DefaultHandler )
|
|
161
|
+
|
|
162
|
+
# Add and enable our ROSpec
|
|
163
|
+
response = self.connector.transact( self.GetROSpec(antennas) )
|
|
164
|
+
assert response.success(), 'Add ROSpec Fails\n{}'.format(response)
|
|
165
|
+
|
|
166
|
+
def _execute( self ):
|
|
167
|
+
response = self.connector.transact( ENABLE_ROSPEC_Message(ROSpecID = self.roSpecID) )
|
|
168
|
+
assert response.success(), 'Enable ROSpec Fails\n{}'.format(response)
|
|
169
|
+
|
|
170
|
+
# Wait for the reader to do its work.
|
|
171
|
+
time.sleep( (1.5*self.readWaitMilliseconds) / 1000.0 )
|
|
172
|
+
|
|
173
|
+
response = self.connector.transact( DISABLE_ROSPEC_Message(ROSpecID = self.roSpecID) )
|
|
174
|
+
assert response.success(), 'Disable ROSpec Fails\n{}'.format(response)
|
|
175
|
+
|
|
176
|
+
def _epilog( self ):
|
|
177
|
+
# Cleanup.
|
|
178
|
+
response = self.connector.transact( DELETE_ROSPEC_Message(ROSpecID = self.roSpecID) )
|
|
179
|
+
assert response.success(), 'Delete ROSpec Fails\n{}'.format(response)
|
|
180
|
+
self.connector.removeAllHandlers()
|
|
181
|
+
|
|
182
|
+
def GetTagInventory( self, antennas = None ):
|
|
183
|
+
self._prolog( antennas if antennas is not None else self.defaultAntennas )
|
|
184
|
+
self._execute()
|
|
185
|
+
self._epilog()
|
|
186
|
+
return self.tagInventory, self.otherMessages
|
|
187
|
+
|
|
188
|
+
if __name__ == '__main__':
|
|
189
|
+
'''Read a tag inventory from the reader and shutdown.'''
|
|
190
|
+
print( '**** full power' )
|
|
191
|
+
host = '192.168.0.101'
|
|
192
|
+
ti = TagInventory( host )
|
|
193
|
+
ti.Connect()
|
|
194
|
+
tagInventory, otherMessages = ti.GetTagInventory()
|
|
195
|
+
print( '\n'.join(tagInventory) )
|
|
196
|
+
ti.Disconnect()
|
|
197
|
+
|
|
198
|
+
for p in range(1, 100, 10):
|
|
199
|
+
ti = TagInventory( host, transmitPower = p )
|
|
200
|
+
ti.Connect()
|
|
201
|
+
tagInventory, otherMessages = ti.GetTagInventory()
|
|
202
|
+
print( f'**** power={p}' )
|
|
203
|
+
print( '\n'.join(tagInventory) )
|
|
204
|
+
ti.Disconnect()
|