Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
95d942a
Move static methods to new Convert class to avoid circular imports an…
Poikilos Mar 18, 2026
10fe25a
Handle CDIVar size as max for "string" and add tests for edge cases.
Poikilos Mar 18, 2026
9b29e37
Fix: Handle "." at end of nodeid range prefix.
Poikilos Apr 22, 2026
adccc26
Fix: Handle memo using new OO-defined onStatusMemo (formerly _onEleme…
Poikilos Apr 22, 2026
4725497
Enforce onStartDownload via OO.
Poikilos Apr 22, 2026
ae1d1cf
Add high-level (size-aware if set manually) DataProcessor progress. A…
Poikilos Apr 22, 2026
7192c26
Fix: Provide size to CDIVar constructor. Add CDIVar serialization and…
Poikilos Apr 22, 2026
921f518
Save the memo tree in XMLDataProcessor (formerly had to be collected …
Poikilos Apr 23, 2026
db12c3d
Add load feature (Load cached CDI XML file). Make caching configurabl…
Poikilos Apr 23, 2026
03ebb93
Fix: Set size 8 for eventid. Enforce valid 'action' sizes. Add expand…
Poikilos Apr 23, 2026
1682c76
Make a DataProcessorMemo superclass for parsing messages that are not…
Poikilos Apr 25, 2026
f54ee8c
Improve emit_cast.
Poikilos Apr 25, 2026
3ac3776
Fix replication recursion. Fix: Only set CDIMemo address during repli…
Poikilos Apr 25, 2026
a7d0cd6
Fix: Correctly separate tail of XML--prevents formatting from modifyi…
Poikilos Apr 27, 2026
d705a20
Fix: Correctly handle origin and offset. Fix: Correctly preserve XML …
Poikilos Apr 27, 2026
de593f9
only act on datagram replies matched to our requests
bobjacobsen Apr 30, 2026
45783bd
Improve hr_repr. Separate cacheFileName code.
Poikilos Apr 30, 2026
34a17cc
Make default extension specific to DataProcessor subclass.
Poikilos Apr 30, 2026
2feca63
Make missing name issue more clear.
Poikilos May 1, 2026
3c737fb
Merge pull request #10 from bobjacobsen/only-our-datagram-replies
Poikilos May 1, 2026
7c9a191
Add import used for type hint.
Poikilos May 1, 2026
e55fa78
Check if rejectedReply should be discarded as well (follow-up to simi…
Poikilos May 1, 2026
87ffcd2
Implement stream modes for Memory Configuration.
Poikilos May 1, 2026
1772370
Rename spaceDecode to serializeSpace for clarity, and add opposite me…
Poikilos May 1, 2026
b5a54dc
Allow length request reply to have arbitrary size.
Poikilos May 1, 2026
0074507
Fix error code deserialization (order of operation issue; result now …
Poikilos May 1, 2026
1f9afcd
Add debug output for unmatched datagrams.
Poikilos May 1, 2026
c4f69bd
Track whether CDI was loaded from cache (file). Add assert to clarify…
Poikilos May 5, 2026
c2df185
Fix: Respect cache setting (Only save CDI file in that case).
Poikilos May 5, 2026
8b687db
Fix non-subscripable type (Change to PEP8 commented type hint).
Poikilos May 11, 2026
bcef09b
Add clear option such as for reconnect if client code doesn't want to…
Poikilos May 11, 2026
1d6b3ff
(NOOP) Fix some type hints and a docstring.
Poikilos May 19, 2026
d8b0680
Begin implementing local node memory (FIXME: responding to memory con…
Poikilos May 19, 2026
82326de
(NOOP) Rename expanded* to replicated* for consistency.
Poikilos May 19, 2026
1a1e10c
Remove lint.
Poikilos May 20, 2026
52ffae2
Fix: Don't miss parsing if realtime and also loaded from CDI file/str…
Poikilos May 21, 2026
8d6ae23
Implement more dunder methods (comparison operators, str conversion) …
Poikilos May 21, 2026
a9fd767
Remove lint.
Poikilos May 21, 2026
cd9bac5
Move spaces to new MemoryManager class for reuse.
Poikilos May 21, 2026
4a3283c
Make LocalNode a MemoryManager subclass so it can be inserted into Me…
Poikilos May 22, 2026
9569133
Move bitfields to MemoryConfigurationHeader class and MemorySpaceInde…
Poikilos May 22, 2026
9693bea
(NOOP) Rename MemoryManager to StoragePool for clarity and terseness;…
Poikilos May 27, 2026
d40d408
Rename module to match new StoragePool class name.
Poikilos May 27, 2026
572137e
Add "set" method to StoragePool and related tests. Move classes to su…
Poikilos May 27, 2026
1a90cdc
Add set and get number methods to StoragePool.
Poikilos May 28, 2026
e6cbf68
Implement minimums by size as per CDI Standard.
Poikilos May 28, 2026
87cc7f7
Fix: Don't load CDI twice.
Poikilos May 28, 2026
009f53d
Fix: Compare int to CDI float (more Pythonic).
Poikilos May 28, 2026
c8bbb4f
Use simplified CDIVar construction for `default`.
Poikilos May 28, 2026
c67dc4f
Set CDI memory space on loadCDI*.
Poikilos May 28, 2026
e7aca7f
Implement Get Address Space Information Reply. Fix: MCOpMasks.Default…
Poikilos May 28, 2026
853e73f
(NOOP) Systematize Memory Configuration bitfield constants (Make them…
Poikilos May 29, 2026
e658489
(NOOP) Group Memory Configuration operations correctly and add a rela…
Poikilos May 29, 2026
2fcc13a
(NOOP) Rename StorageSpace to Segment and StoragePool to MemoryManage…
Poikilos May 29, 2026
cd3466d
(NOOP) Rename test file using new class name.
Poikilos May 29, 2026
33934c2
(NOOP) Rename submodule using new class name.
Poikilos May 29, 2026
34abc9e
Accept and process Memory Read command (FIXME: test this).
Poikilos May 29, 2026
8441d0f
Fix circular import.
Poikilos May 31, 2026
d52bc4f
Fix: min and max type.
Poikilos May 31, 2026
24d0929
Fix: wait for far node alias before using farNodeID. (NOOP) Move exam…
Poikilos May 31, 2026
525e9f8
Move memoryRead to reusable MemoryReadJob class.
Poikilos May 31, 2026
f42443e
Fix local memory read (Encode and decode space index correctly; Pad w…
Poikilos Jun 1, 2026
3368a1e
(NOOP) Rename getStorage to getSegment.
Poikilos Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 48 additions & 116 deletions examples/example_cdi_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
# region same code as other examples
import copy
import sys
import time
from typing import Union
import xml.sax
import xml.sax.handler
import xml.sax.xmlreader # for static type hints, autocomplete in this case
Expand All @@ -21,6 +23,11 @@

from examples_settings import Settings # do 1st to fix path if no pip install
from openlcb import precise_sleep
from openlcb.dataprocessor import DataFormat
from openlcb.dataprocessormemo import DataProcessorMemo
from openlcb.memoryreadjob import MemoryReadJob
from openlcb.convert import Convert
from openlcb.memoryspace import MemorySpace
from openlcb.xmldataprocessor import attrs_to_dict
from openlcb.tcplink.tcpsocket import TcpSocket
settings = Settings()
Expand Down Expand Up @@ -107,70 +114,6 @@ def printDatagram(memo):

memoryService = MemoryService(datagramService)


# accumulate the CDI information
resultingCDI = bytearray()

# callbacks to get results of memory read

complete_data = False
read_failed = False


def memoryReadSuccess(memo):
"""Handle a successful read
Invoked when the memory read successfully returns,
this queues a new read until the entire CDI has been
returned. At that point, it invokes the XML processing below.

Args:
memo (MemoryReadMemo): Successful MemoryReadMemo
"""
# print("successful memory read: {}".format(memo.data))

global resultingCDI
global complete_data

# is this done?
if len(memo.data) == 64 and 0 not in memo.data:
# save content
resultingCDI += memo.data
logger.debug(
f"[{memo.address}] successful read"
f" {MemoryService.arrayToString(memo.data, len(memo.data))}"
"; next = address + 64")
# update the address
memo.address = memo.address+64
# and read again
memoryService.requestMemoryRead(memo)
# The last packet is not yet reached, so don't parse (However,
# parser.feed could be called for realtime processing).
else :
# and we're done!
# save content
resultingCDI += memo.data
# concert resultingCDI to a string up to 1st zero
cdiString = ""
null_i = resultingCDI.find(b'\0')
terminate_i = len(resultingCDI)
if null_i > -1:
terminate_i = min(null_i, terminate_i)
cdiString = resultingCDI[:terminate_i].decode("utf-8")
# print (cdiString)

# and process that
processXML(cdiString)
complete_data = True

# done


def memoryReadFail(memo):
global read_failed
print("memory read failed: {}".format(memo.data))
read_failed = True


#######################
# The XML parsing section.
#
Expand All @@ -189,10 +132,8 @@ class MyHandler(xml.sax.handler.ContentHandler):
_chunks (list[str]): Collects chunks of data.
This is implementation-specific, and not
required if streaming (parser.feed).
_tmp_address (int|None): Where we are in the memory space (starting
at origin, and calculated using offset and/or size of start
tags).
_tmp_space (int|None): What space we are currently on.
_tmp_address (int|None): For sanity check, not actual address.
See replicatedTree docstring.
"""

def __init__(self):
Expand Down Expand Up @@ -270,26 +211,6 @@ def characters(self, content: str):

handler = MyHandler()


def processXML(content: str) :
"""process the XML and invoke callbacks

Args:
content (str): Raw XML data
"""
# NOTE: The data is complete in this example since processXML is
# only called when there is a null terminator, which indicates the
# last packet was reached for the requested read.
# - See memoryReadSuccess comments for details.
with open("cached-cdi.xml", 'w') as stream:
# NOTE: Actual caching should key by all SNIP info that could
# affect CDI/FDI: manufacturer, model, and version. Without
# all 3 being present in SNIP, the cache may be incorrect.
stream.write(content)
xml.sax.parseString(content, handler)
print("\nParser done")


#######################

# have the socket layer report up to bring the link layer up and get an alias
Expand All @@ -314,37 +235,48 @@ def processXML(content: str) :
print(" SENT frames : link up")


def memoryRead():
"""Create and send a read datagram.
This is a read of 20 bytes from the start of CDI space.
We will fire it on a separate thread to give time for other nodes to reply
to AME
"""
import time
time.sleep(.21)
# ^ 200ms: See section 6.2.1 of CAN Frame Transfer Standard
# (CanLink.State.Permitted will only occur after that, but waiting
# now will reduce output & delays below in this example).
while canLink.getState() != CanLink.State.Permitted:
print("Waiting for connection sequence to complete...")
# This delay could be .2 (per alias collision), but longer to
# reduce console messages:
time.sleep(.5)
print("Requesting memory read. Please wait...")
# read 64 bytes from the CDI space starting at address zero
memMemo = MemoryReadMemo(NodeID(settings['farNodeID']), 64, 0xFF, 0,
memoryReadFail, memoryReadSuccess)
memoryService.requestMemoryRead(memMemo)


import threading # noqa E402
thread = threading.Thread(target=memoryRead)
thread.start()
job = MemoryReadJob(memoryService)


def statusCallback(memo: DataProcessorMemo):
if memo.status:
print(memo.status)


farNodeID = NodeID(settings['farNodeID'])

time.sleep(.21)
# ^ 200ms: See section 6.2.1 of CAN Frame Transfer Standard
# (CanLink.State.Permitted will only occur after that, but waiting
# now will reduce output & delays below in this example).
while canLink.getState() != CanLink.State.Permitted:
print("Waiting for connection sequence to complete...")
# This delay could be .2 (per alias collision), but longer to
# reduce console messages:
time.sleep(.5)

print(f"CanLink state={canLink.getState()}")

waited = 0
delaySec = 1
while farNodeID not in canLink.nodeIdToAlias:
# canLink.pollState()
physicalLayer.receiveAll(sock)
physicalLayer.sendAll(sock)
time.sleep(delaySec)
waited += delaySec
print(f"Connected nodes: {canLink.nodeIdToAlias}")
print(f"Waiting for {farNodeID} ({waited}s)...")

job.readMemory(canLink, farNodeID, MemorySpace.CDI,
handler=handler,
callback=statusCallback)

previous_nodes = copy.deepcopy(canLink.nodeIdToAlias)
# process resulting activity
print()
print("This example will exit on failure or complete data.")
while not complete_data and not read_failed:
while not job.completeData and not job.failed:
# In this example, requests are initiate by the
# memoryRead thread, and receiveAll actually
# receives the data from the requested memory space (CDI in this
Expand All @@ -363,7 +295,7 @@ def memoryRead():

physicalLayer.physicalLayerDown()

if read_failed:
if job.failed:
print("Read complete (FAILED)")
else:
print("Read complete (OK)")
6 changes: 5 additions & 1 deletion examples/example_node_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ def printMessage(message):
print("RM: {} from {}".format(message, message.source))


canLink = CanLink(physicalLayer, NodeID(settings['localNodeID']))
localNodeID = NodeID(settings['localNodeID'])
print()
print(f"[example_node_memory_implementation] localNodeID: {localNodeID}")

canLink = CanLink(physicalLayer, localNodeID)
canLink.registerMessageReceivedListener(printMessage)

datagramService = DatagramService(canLink)
Expand Down
Loading
Loading