pyllrp 3.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,17 @@
1
+
2
+ Copyright (C) 2013-2019 Edward Sitarski
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5
+ and associated documentation files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or
11
+ substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
pyllrp-3.1.0/PKG-INFO ADDED
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.1
2
+ Name: pyllrp
3
+ Version: 3.1.0
4
+ Summary: A complete implementation of LLRP (Low Level Reader Protocol) in pure Python.
5
+ Home-page: http://www.sites.google.com/site/crossmgrsoftware/
6
+ Author: Edward Sitarski
7
+ Author-email: Edward Sitarski <edward.sitarski@gmail.com>
8
+ License:
9
+ Copyright (C) 2013-2019 Edward Sitarski
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
12
+ and associated documentation files (the "Software"), to deal in the Software without
13
+ restriction, including without limitation the rights to use, copy, modify, merge, publish,
14
+ distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
15
+ Software is furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all copies or
18
+ substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
21
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
23
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25
+
26
+ Project-URL: Homepage, https://github.com/esitarski/pyllrp
27
+ Project-URL: Repository, https://github.com/esitarski/pyllrp
28
+ Project-URL: Issues, https://github.com/esitarski/pyllrp/issues
29
+ Keywords: LLRP,RFID,Impinj Reader
30
+ Classifier: Development Status :: 5 - Production/Stable
31
+ Classifier: Intended Audience :: Developers
32
+ Classifier: Topic :: System :: Hardware :: Hardware Drivers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Programming Language :: Python :: 3.8
35
+ Classifier: Programming Language :: Python :: 3.9
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Requires-Python: >=3.8
40
+ Description-Content-Type: text/plain
41
+ Requires-Dist: bitstring>=3.1.1
42
+
43
+
44
+ ======
45
+ pyllrp
46
+ ======
47
+
48
+ A pure Python implementation of LLRP (Low Level Reader Protocol)
49
+ used to communicate with LLRP-compliant RFID readers including Impinj, Alien and ThingMagic.
50
+ See the LLRP spec ) for details:
51
+ https://www.gs1.org/sites/default/files/docs/epc/llrp_1_0_1-standard-20070813.pdf
52
+
53
+ pyllrp enables quick-and-easy scripting in portable pure Python to
54
+ create LLRP applications.
55
+
56
+ It is used extensively in CrossMgr, RaceDB, TagReadWrite for RFID and race timing.
57
+
58
+ The implementation is 100% complete. All LLRP features and full Impinj custom extensions are
59
+ supported.
60
+
61
+ * Message and Parameters are full Python classes.
62
+ * Full validation of all LLRP Messages and Parameters including data types, data ranges, parameter sequence and count.
63
+ * Full support for enumerated values - no hardcoded values.
64
+ * "Impossible" to pass incorrect or malformed LLRP messages to the reader.
65
+
66
+ See TinyExample.py for how to use.
67
+
68
+ A reader connection manager is also included that can connect to a reader, transact commands,
69
+ then start/stop a thread to listen for tag reads.
70
+ A message handler can be configured to respond to any reader message.
71
+ See wxExample.py for a simple method to show reader messages in
72
+ a wxPython application with a Queue (requires wxPython install).
73
+
74
+ The module also supports reading and writing messages in XML format.
75
+
76
+ Unlike other implementations, pyllrp generates the interfaces from the LLRP XML spec itself.
77
+ It is 100% compliant with the LLRP spec and Impinj extensions.
78
+
79
+ pyllrp is also 100% pedantic regarding LLRP Messages and Parameters.
80
+ For example, pyllrp is strict about bool and ints: passing 1/0 for True/False doesn't work.
81
+ The names of all fields must be specified, with the exception of Parameters which have only one field.
82
+ In this case, the single parameter is taken to be the value.
83
+
84
+ How to use it:
85
+
86
+ The only files you need are pyllrp.py and llrpdef.py.
87
+ TinyExample.py shows how to formulate LLRP specs, messages and parameters.
88
+ To run TinyExample, from the top pyllrp directory, enter "python3 -m pyllrp.TinyExample".
89
+
90
+
91
+ How it works:
92
+
93
+ ParseDef.py reads the LLRP XML spec files and generates the llrpdef.py file.
94
+ During this process, it translates the LLRP data types into bitstream representation and processes the XML into Python-friendly structures.
95
+ ParseDef must be run every time the XML files change.
96
+
97
+ At run time, pyllrp.py imports llrpdef and does a 1-time augmentation on the Messages, Parameters and Enums into classes.
98
+ It adds the binary pack/unpack, validation and human-readable formating functions.
99
+ Startup time is minimal due to the llrpdef.py pre-processing.
100
+
101
+ Stuff to do:
102
+
103
+ Because the classes are created dynamically, python linters (eg. flake8) do not understand pyllrp classes.
104
+ The only way I can think of to migitate this is to code-generate the full signatures for the class functions,
105
+ but this would be messier than dynamically creating the classes from the llrpdef.py info.
@@ -0,0 +1,63 @@
1
+
2
+ ======
3
+ pyllrp
4
+ ======
5
+
6
+ A pure Python implementation of LLRP (Low Level Reader Protocol)
7
+ used to communicate with LLRP-compliant RFID readers including Impinj, Alien and ThingMagic.
8
+ See the LLRP spec ) for details:
9
+ https://www.gs1.org/sites/default/files/docs/epc/llrp_1_0_1-standard-20070813.pdf
10
+
11
+ pyllrp enables quick-and-easy scripting in portable pure Python to
12
+ create LLRP applications.
13
+
14
+ It is used extensively in CrossMgr, RaceDB, TagReadWrite for RFID and race timing.
15
+
16
+ The implementation is 100% complete. All LLRP features and full Impinj custom extensions are
17
+ supported.
18
+
19
+ * Message and Parameters are full Python classes.
20
+ * Full validation of all LLRP Messages and Parameters including data types, data ranges, parameter sequence and count.
21
+ * Full support for enumerated values - no hardcoded values.
22
+ * "Impossible" to pass incorrect or malformed LLRP messages to the reader.
23
+
24
+ See TinyExample.py for how to use.
25
+
26
+ A reader connection manager is also included that can connect to a reader, transact commands,
27
+ then start/stop a thread to listen for tag reads.
28
+ A message handler can be configured to respond to any reader message.
29
+ See wxExample.py for a simple method to show reader messages in
30
+ a wxPython application with a Queue (requires wxPython install).
31
+
32
+ The module also supports reading and writing messages in XML format.
33
+
34
+ Unlike other implementations, pyllrp generates the interfaces from the LLRP XML spec itself.
35
+ It is 100% compliant with the LLRP spec and Impinj extensions.
36
+
37
+ pyllrp is also 100% pedantic regarding LLRP Messages and Parameters.
38
+ For example, pyllrp is strict about bool and ints: passing 1/0 for True/False doesn't work.
39
+ The names of all fields must be specified, with the exception of Parameters which have only one field.
40
+ In this case, the single parameter is taken to be the value.
41
+
42
+ How to use it:
43
+
44
+ The only files you need are pyllrp.py and llrpdef.py.
45
+ TinyExample.py shows how to formulate LLRP specs, messages and parameters.
46
+ To run TinyExample, from the top pyllrp directory, enter "python3 -m pyllrp.TinyExample".
47
+
48
+
49
+ How it works:
50
+
51
+ ParseDef.py reads the LLRP XML spec files and generates the llrpdef.py file.
52
+ During this process, it translates the LLRP data types into bitstream representation and processes the XML into Python-friendly structures.
53
+ ParseDef must be run every time the XML files change.
54
+
55
+ At run time, pyllrp.py imports llrpdef and does a 1-time augmentation on the Messages, Parameters and Enums into classes.
56
+ It adds the binary pack/unpack, validation and human-readable formating functions.
57
+ Startup time is minimal due to the llrpdef.py pre-processing.
58
+
59
+ Stuff to do:
60
+
61
+ Because the classes are created dynamically, python linters (eg. flake8) do not understand pyllrp classes.
62
+ The only way I can think of to migitate this is to code-generate the full signatures for the class functions,
63
+ but this would be messier than dynamically creating the classes from the llrpdef.py info.
@@ -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()
@@ -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' )