1111 address and port.
1212'''
1313# region same code as other examples
14+ import copy
15+ import sys
16+ import xml .sax
17+ import xml .sax .handler
18+ import xml .sax .xmlreader # for static type hints, autocomplete in this case
19+
20+ from logging import getLogger
21+
1422from examples_settings import Settings # do 1st to fix path if no pip install
23+ from openlcb import precise_sleep
24+ from openlcb .xmldataprocessor import attrs_to_dict
25+ from openlcb .tcplink .tcpsocket import TcpSocket
1526settings = Settings ()
1627
1728if __name__ == "__main__" :
1829 settings .load_cli_args (docstring = __doc__ )
30+ logger = getLogger (__file__ )
31+ else :
32+ logger = getLogger (__name__ )
1933# endregion same code as other examples
2034
21- from openlcb .canbus .tcpsocket import TcpSocket
22-
23- from openlcb .canbus .canphysicallayergridconnect import (
35+ from openlcb .canbus .canphysicallayergridconnect import ( # noqa:E402
2436 CanPhysicalLayerGridConnect ,
2537)
26- from openlcb .canbus .canlink import CanLink
27- from openlcb .nodeid import NodeID
28- from openlcb .datagramservice import (
38+ from openlcb .canbus .canlink import CanLink # noqa:E402
39+ from openlcb .nodeid import NodeID # noqa:E402
40+ from openlcb .datagramservice import ( # noqa:E402
2941 DatagramService ,
3042)
31- from openlcb .memoryservice import (
43+ from openlcb .memoryservice import ( # noqa:E402
3244 MemoryReadMemo ,
3345 MemoryService ,
3446)
4254# farNodeID = "02.01.57.00.04.9C"
4355# endregion moved to settings
4456
45- s = TcpSocket ()
57+ sock = TcpSocket ()
4658# s.settimeout(30)
47- s .connect (settings ['host' ], settings ['port' ])
59+ sock .connect (settings ['host' ], settings ['port' ])
4860
4961
5062# print("RR, SR are raw socket interface receive and send;"
5163# " RL, SL are link interface; RM, SM are message interface")
5264
5365
54- def sendToSocket (string ):
55- # print(" SR: {}".format(string.strip()))
56- s .send (string )
66+ # def sendToSocket(frame: CanFrame):
67+ # string = frame.encodeAsString()
68+ # # print(" SR: {}".format(string.strip()))
69+ # sock.sendString(string)
70+ # physicalLayer.onFrameSent(frame)
5771
5872
5973def printFrame (frame ):
@@ -80,11 +94,10 @@ def printDatagram(memo):
8094 return False
8195
8296
83- canPhysicalLayerGridConnect = CanPhysicalLayerGridConnect (sendToSocket )
84- canPhysicalLayerGridConnect .registerFrameReceivedListener (printFrame )
97+ physicalLayer = CanPhysicalLayerGridConnect ()
98+ physicalLayer .registerFrameReceivedListener (printFrame )
8599
86- canLink = CanLink (NodeID (settings ['localNodeID' ]))
87- canLink .linkPhysicalLayer (canPhysicalLayerGridConnect )
100+ canLink = CanLink (physicalLayer , NodeID (settings ['localNodeID' ]))
88101canLink .registerMessageReceivedListener (printMessage )
89102
90103datagramService = DatagramService (canLink )
@@ -100,6 +113,9 @@ def printDatagram(memo):
100113
101114# callbacks to get results of memory read
102115
116+ complete_data = False
117+ read_failed = False
118+
103119
104120def memoryReadSuccess (memo ):
105121 """Handle a successful read
@@ -113,11 +129,16 @@ def memoryReadSuccess(memo):
113129 # print("successful memory read: {}".format(memo.data))
114130
115131 global resultingCDI
132+ global complete_data
116133
117134 # is this done?
118135 if len (memo .data ) == 64 and 0 not in memo .data :
119136 # save content
120137 resultingCDI += memo .data
138+ logger .debug (
139+ f"[{ memo .address } ] successful read"
140+ f" { MemoryService .arrayToString (memo .data , len (memo .data ))} "
141+ "; next = address + 64" )
121142 # update the address
122143 memo .address = memo .address + 64
123144 # and read again
@@ -139,12 +160,15 @@ def memoryReadSuccess(memo):
139160
140161 # and process that
141162 processXML (cdiString )
163+ complete_data = True
142164
143165 # done
144166
145167
146168def memoryReadFail (memo ):
169+ global read_failed
147170 print ("memory read failed: {}" .format (memo .data ))
171+ read_failed = True
148172
149173
150174#######################
@@ -157,58 +181,97 @@ def memoryReadFail(memo):
157181# in a row, we buffer up the characters until the `endElement`
158182# call is invoked to indicate the text is complete
159183
160- import xml .sax # noqa: E402
161-
162184
163185class MyHandler (xml .sax .handler .ContentHandler ):
164- """XML SAX callbacks in a handler object"""
165- def __init__ (self ):
166- self ._charBuffer = bytearray ()
167-
168- def startElement (self , name , attrs ):
169- """_summary_
170-
171- Args:
172- name (_type_): _description_
173- attrs (_type_): _description_
174- """
175- print ("Start: " , name )
176- if attrs is not None and attrs :
177- print (" Attributes: " , attrs .getNames ())
178-
179- def endElement (self , name ):
180- """_summary_
186+ """XML SAX callbacks in a handler object
187+
188+ Attributes:
189+ _chunks (list[str]): Collects chunks of data.
190+ This is implementation-specific, and not
191+ required if streaming (parser.feed).
192+ _tmp_address (int|None): Where we are in the memory space (starting
193+ at origin, and calculated using offset and/or size of start
194+ tags).
195+ _tmp_space (int|None): What space we are currently on.
196+ """
181197
182- Args:
183- name (_type_): _description_
184- """
185- print (name , "content:" , self ._flushCharBuffer ())
186- print ("End: " , name )
198+ def __init__ (self ):
199+ self ._chunks = []
200+ self .stack = []
201+ self .cursorCol = 0
202+ self ._tmp_space = None # type: int|None
203+ self ._tmp_address = None # type: int|None
204+
205+ def startElement (self , name : str , attrs : xml .sax .xmlreader .AttributesImpl ):
206+ """See xml.sax.handler.ContentHandler documentation."""
207+ self .stack .append (name )
208+ if self .cursorCol != 0 :
209+ self .print ()
210+ self .write (name )
211+ if attrs is not None and attrs :
212+ self .print (" {}" .format (attrs_to_dict (attrs )))
213+
214+ def endElement (self , name : str ):
215+ """See xml.sax.handler.ContentHandler documentation."""
216+ content = self ._flushCharBuffer ().strip ()
217+ if self .cursorCol != 0 :
218+ self .print ()
219+ if content :
220+ self .print ('/{} "{}"' .format (name , content ))
221+ else :
222+ self .print ('/{}' .format (name ))
223+ self .stack .pop ()
224+ # self.print("/", name)
187225 pass
188226
227+ def write (self , * args , ** kwargs ):
228+ args = list (args )
229+ if self .cursorCol == 0 :
230+ tab = len (self .stack )* " "
231+ self .cursorCol += len (tab )
232+ args .insert (0 , tab ) # prepend indent
233+ for arg in args :
234+ sys .stdout .write (arg )
235+ self .cursorCol += len (arg )
236+ sys .stdout .flush ()
237+
238+ def print (self , * args , ** kwargs ):
239+ if self .cursorCol == 0 : # No indent yet, so use write.
240+ self .write (* args , ** kwargs )
241+ print ()
242+ else :
243+ print (* args , ** kwargs )
244+ self .cursorCol = 0
245+
189246 def _flushCharBuffer (self ):
190247 """Decode the buffer, clear it, and return all content.
248+ See xml.sax.handler.ContentHandler documentation.
191249
192250 Returns:
193251 str: The content of the bytes buffer decoded as utf-8.
194252 """
195- s = self . _charBuffer . decode ( "utf-8" )
196- self ._charBuffer .clear ()
253+ s = '' . join ( self . _chunks )
254+ self ._chunks .clear ()
197255 return s
198256
199- def characters (self , data ):
200- """Received characters handler
257+ def characters (self , content : str ):
258+ """Received characters handler.
259+ See xml.sax.handler.ContentHandler documentation.
260+
201261 Args:
202262 data (Union[bytearray, bytes, list[int]]): any
203263 data (any type accepted by bytearray extend).
204264 """
205- self ._charBuffer .extend (data )
265+ if not isinstance (content , str ):
266+ raise TypeError ("Expected str, got {}"
267+ .format (type (content ).__name__ ))
268+ self ._chunks .append (content )
206269
207270
208271handler = MyHandler ()
209272
210273
211- def processXML (content ) :
274+ def processXML (content : str ) :
212275 """process the XML and invoke callbacks
213276
214277 Args:
@@ -218,15 +281,37 @@ def processXML(content) :
218281 # only called when there is a null terminator, which indicates the
219282 # last packet was reached for the requested read.
220283 # - See memoryReadSuccess comments for details.
284+ with open ("cached-cdi.xml" , 'w' ) as stream :
285+ # NOTE: Actual caching should key by all SNIP info that could
286+ # affect CDI/FDI: manufacturer, model, and version. Without
287+ # all 3 being present in SNIP, the cache may be incorrect.
288+ stream .write (content )
221289 xml .sax .parseString (content , handler )
222290 print ("\n Parser done" )
223291
224292
225293#######################
226294
227295# have the socket layer report up to bring the link layer up and get an alias
228- # print(" SL : link up")
229- canPhysicalLayerGridConnect .physicalLayerUp ()
296+ print (" QUEUE frames : link up..." )
297+ physicalLayer .physicalLayerUp ()
298+ print (" QUEUED frames : link up...waiting..." )
299+ while canLink .pollState () != CanLink .State .Permitted :
300+ # provides incoming data to physicalLayer & sends queued:
301+ physicalLayer .receiveAll (sock , verbose = True )
302+ physicalLayer .sendAll (sock )
303+
304+ if canLink .getState () == CanLink .State .WaitForAliases :
305+ # physicalLayer.receiveAll(sock, verbose=True)
306+ physicalLayer .sendAll (sock )
307+ # ^ prevent assertion error below, proceed to send.
308+ if canLink .pollState () == CanLink .State .Permitted :
309+ break
310+ assert canLink .getWaitForAliasResponseStart () is not None , \
311+ ("openlcb didn't send the 7,6,5,4 CID frames (state={})"
312+ .format (canLink .getState ()))
313+ precise_sleep (.02 )
314+ print (" SENT frames : link up" )
230315
231316
232317def memoryRead ():
@@ -236,8 +321,16 @@ def memoryRead():
236321 to AME
237322 """
238323 import time
239- time .sleep (1 )
240-
324+ time .sleep (.21 )
325+ # ^ 200ms: See section 6.2.1 of CAN Frame Transfer Standard
326+ # (CanLink.State.Permitted will only occur after that, but waiting
327+ # now will reduce output & delays below in this example).
328+ while canLink .getState () != CanLink .State .Permitted :
329+ print ("Waiting for connection sequence to complete..." )
330+ # This delay could be .2 (per alias collision), but longer to
331+ # reduce console messages:
332+ time .sleep (.5 )
333+ print ("Requesting memory read. Please wait..." )
241334 # read 64 bytes from the CDI space starting at address zero
242335 memMemo = MemoryReadMemo (NodeID (settings ['farNodeID' ]), 64 , 0xFF , 0 ,
243336 memoryReadFail , memoryReadSuccess )
@@ -247,10 +340,30 @@ def memoryRead():
247340import threading # noqa E402
248341thread = threading .Thread (target = memoryRead )
249342thread .start ()
250-
343+ previous_nodes = copy . deepcopy ( canLink . nodeIdToAlias )
251344# process resulting activity
252- while True :
253- received = s .receive ()
254- # print(" RR: {}".format(received.strip()))
255- # pass to link processor
256- canPhysicalLayerGridConnect .receiveString (received )
345+ print ()
346+ print ("This example will exit on failure or complete data." )
347+ while not complete_data and not read_failed :
348+ # In this example, requests are initiate by the
349+ # memoryRead thread, and receiveAll actually
350+ # receives the data from the requested memory space (CDI in this
351+ # case) and offset (incremental position in the file/data,
352+ # incremented by this example's memoryReadSuccess handler).
353+ count = 0
354+ count += physicalLayer .receiveAll (sock )
355+ count += physicalLayer .sendAll (sock )
356+ if canLink .nodeIdToAlias != previous_nodes :
357+ print ("nodeIdToAlias updated: {}" .format (canLink .nodeIdToAlias ))
358+ if count < 1 :
359+ precise_sleep (.01 )
360+ # else skip sleep to avoid latency (port already delayed)
361+ if canLink .nodeIdToAlias != previous_nodes :
362+ previous_nodes = copy .deepcopy (canLink .nodeIdToAlias )
363+
364+ physicalLayer .physicalLayerDown ()
365+
366+ if read_failed :
367+ print ("Read complete (FAILED)" )
368+ else :
369+ print ("Read complete (OK)" )
0 commit comments