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 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() )
@@ -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()