Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)

## [Unreleased] - 2026-09-14

### Changed
- Rename `DatagramWriteMemo` to **`DatagramSendMemo`** and `DatagramReadMemo` to **`DatagramReceiveMemo`** to distinguish meaning of read/write from the higher network layers (For example, in "DatagramWriteMemo" "Write" had two different meanings--one for port flow direction, one for request type, as it could be associated with a MemoryReadMemo. Now a MemoryReadMemo is associated with a "DatagramSendMemo" denoting it was requested by us but we may be still requesting a read such as in that case).
2 changes: 1 addition & 1 deletion examples/example_cdi_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def printDatagram(memo):
"""A call-back for when datagrams received

Args:
DatagramReadMemo: The datagram object
DatagramReceiveMemo: The datagram object

Returns:
bool: Always False (True would mean we sent a reply to the datagram,
Expand Down
12 changes: 6 additions & 6 deletions examples/example_datagram_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
from openlcb.nodeid import NodeID # noqa:E402
from openlcb.datagramservice import ( # noqa:E402
DatagramService,
DatagramWriteMemo,
DatagramSendMemo,
)

# specify connection information
Expand Down Expand Up @@ -74,15 +74,15 @@ def printMessage(message):


# create a call-back for replies to write datagram
def writeCallBackCheck(memo):
def sendCallBackCheck(memo):
print("Write complete call back")


def datagramReceiver(memo):
"""A call-back for when datagrams received

Args:
DatagramReadMemo: The datagram object
DatagramReceiveMemo: The datagram object

Returns:
bool: Always True (means we sent the reply to this datagram)
Expand Down Expand Up @@ -118,12 +118,12 @@ def datagramWrite():
import time
time.sleep(1)

writeMemo = DatagramWriteMemo(
sendMemo = DatagramSendMemo(
NodeID(settings['farNodeID']),
bytearray([0x20, 0x43, 0x00, 0x00, 0x00, 0x00, 0x14]),
writeCallBackCheck
sendCallBackCheck
)
datagramService.sendDatagram(writeMemo)
datagramService.sendDatagram(sendMemo)


thread = threading.Thread(target=datagramWrite)
Expand Down
6 changes: 3 additions & 3 deletions examples/example_memory_length_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
from openlcb.canbus.canlink import CanLink # noqa: E402
from openlcb.nodeid import NodeID # noqa: E402
from openlcb.datagramservice import ( # noqa: E402
# DatagramWriteMemo,
# DatagramReadMemo,
# DatagramSendMemo,
# DatagramReceiveMemo,
DatagramService,
)
from openlcb.memoryservice import ( # noqa: E402
Expand Down Expand Up @@ -82,7 +82,7 @@ def printDatagram(memo):
"""create a call-back to print datagram contents when received

Args:
memo (DatagramReadMemo): The datagram received
memo (DatagramReceiveMemo): The datagram received

Returns:
bool: Always False (True would mean we sent a reply to this datagram,
Expand Down
6 changes: 3 additions & 3 deletions examples/example_memory_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
from openlcb.canbus.canlink import CanLink # noqa: E402
from openlcb.nodeid import NodeID # noqa: E402
from openlcb.datagramservice import ( # noqa: E402
# DatagramWriteMemo,
# DatagramReadMemo,
# DatagramSendMemo,
# DatagramReceiveMemo,
DatagramService,
)
from openlcb.memoryservice import ( # noqa: E402
Expand Down Expand Up @@ -83,7 +83,7 @@ def printDatagram(memo):
"""create a call-back to print datagram contents when received

Args:
memo (DatagramReadMemo): The datagram received
memo (DatagramReceiveMemo): The datagram received

Returns:
bool: Always False (True would mean we sent a reply to this datagram,
Expand Down
2 changes: 1 addition & 1 deletion examples/example_node_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def printDatagram(memo):
"""create a call-back to print datagram contents when received

Args:
memo (DatagramReadMemo): The datagram received
memo (DatagramReceiveMemo): The datagram received

Returns:
bool: Always False (True would mean we sent a reply to the datagram,
Expand Down
6 changes: 3 additions & 3 deletions examples/example_node_memory_implementation.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
)
from openlcb.canbus.canlink import CanLink # noqa: E402
from openlcb.nodeid import NodeID # noqa: E402
from openlcb.datagramservice import DatagramReadMemo, DatagramService # noqa: E402, E501
from openlcb.datagramservice import DatagramReceiveMemo, DatagramService # noqa: E402, E501
from openlcb.memoryservice import MemoryService # noqa: E402
from openlcb.message import Message # noqa: E402
from openlcb.mti import MTI # noqa: E402
Expand Down Expand Up @@ -151,11 +151,11 @@ def printMessage(message: Message):
assert_xml(cdi)


def handleDatagram(memo: DatagramReadMemo):
def handleDatagram(memo: DatagramReceiveMemo):
"""create a call-back to print datagram contents when received

Args:
memo (DatagramReadMemo): The datagram received
memo (DatagramReceiveMemo): The datagram received

Returns:
bool: Always False (True would mean we sent a reply to the datagram,
Expand Down
70 changes: 40 additions & 30 deletions openlcb/datagramservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
Provide a service interface for reading and writing Datagrams.

Writes to remote node:
- Create a ``DatagramWriteMemo`` and submit via ``sendDatagram(_:)``
- Create a ``DatagramSendMemo`` and submit via ``sendDatagram(_:)``
- Get an OK or NotOK callback

Reads from remote node:
Expand Down Expand Up @@ -40,12 +40,12 @@


def defaultIgnoreReply(memo: Union[Any, None]):
# ^ DatagramWriteMemo is the type, but that is not defined yet
# ^ DatagramSendMemo is the type, but that is not defined yet
'''default handling of reply does nothing'''
pass


class DatagramWriteMemo:
class DatagramSendMemo:
'''Immutable memo carrying write request and two reply callbacks
(In this context "Write" means sent to other node, even if
associated with a MemoryReadMemo).
Expand All @@ -54,7 +54,7 @@ class DatagramWriteMemo:
def __init__(self, destID: NodeID, data,
okReply=defaultIgnoreReply,
rejectedReply=defaultIgnoreReply):
# type: (NodeID, bytearray, Callable[[Union[DatagramWriteMemo, None]], None], Callable[[Union[DatagramWriteMemo, None]], None]) -> None # noqa: E501
# type: (NodeID, bytearray, Callable[[Union[DatagramSendMemo, None]], None], Callable[[Union[DatagramSendMemo, None]], None]) -> None # noqa: E501
assert isinstance(destID, NodeID)
self.destID = destID
# NOTE: No srcID since always from this node ("Write" means send
Expand All @@ -63,8 +63,8 @@ def __init__(self, destID: NodeID, data,
raise TypeError("Expected bytearray (formerly list[int]), got {}"
.format(type(data).__name__))
self.data: bytearray = data
self.okReply: Callable[[Union[DatagramWriteMemo, None]], None] = okReply # noqa: E501
self.rejectedReply: Callable[[Union[DatagramWriteMemo, None]], None] = rejectedReply # noqa: E501
self.okReply: Callable[[Union[DatagramSendMemo, None]], None] = okReply # noqa: E501
self.rejectedReply: Callable[[Union[DatagramSendMemo, None]], None] = rejectedReply # noqa: E501

def __eq__(lhs, rhs):
if lhs.destID != rhs.destID:
Expand All @@ -74,7 +74,7 @@ def __eq__(lhs, rhs):
return True


class DatagramReadMemo:
class DatagramReceiveMemo:
'''Immutable memo carrying read result
(In this context "Read" means received from other node,
*not* associated with a MemoryReadMemo which, however, may be what
Expand All @@ -100,6 +100,16 @@ class DatagramService:
Args:
linkLayer (CanLink): Could actually be any link layer such as
LinkMockLayer (for testing) or CanLink.

Attributes:
pendingSendMemos: (formerly pendingWriteMemos) These are written
to the port, but a MemorySendMemo may be associated with a
MemoryReadMemo since the device instantiating the service is
making the request; or may be used to send replies as well
(See where MemoryService instantiates any MemorySendMemo to
respond to space info request especially when the node
instantiating MemoryService is a node other than a
Configuration Tool).
"""

class ProtocolID(Enum):
Expand All @@ -117,9 +127,9 @@ class ProtocolID(Enum):
def __init__(self, linkLayer: LinkLayer):
self.linkLayer: LinkLayer = linkLayer
self.quiesced: bool = False
self.currentOutstandingMemo: Union[DatagramWriteMemo, None] = None # noqa: E501
self.pendingWriteMemos: List[DatagramWriteMemo] = []
self._datagramReceivedListeners: List[Callable[[DatagramReadMemo], bool]] = [] # noqa: E501
self.currentOutstandingMemo: Union[DatagramSendMemo, None] = None # noqa: E501
self.pendingSendMemos: List[DatagramSendMemo] = []
self._datagramReceivedListeners: List[Callable[[DatagramReceiveMemo], bool]] = [] # noqa: E501

def datagramType(self, data: Union[bytearray, List[int]]):
"""Determine the protocol type of the content of the datagram.
Expand Down Expand Up @@ -154,42 +164,42 @@ def checkDestID(self, message, nodeID: NodeID):
assert isinstance(nodeID, NodeID)
return message.destination == nodeID

def sendDatagram(self, memo: DatagramWriteMemo):
'''Queue a ``DatagramWriteMemo`` to send a datagram to another node
def sendDatagram(self, memo: DatagramSendMemo):
'''Queue a ``DatagramSendMemo`` to send a datagram to another node
on the network.
'''
# Make a record of memo for reply
self.pendingWriteMemos.append(memo)
self.pendingSendMemos.append(memo)

# can only have one outstanding at a time, so check it there was
# already one there.
if len(self.pendingWriteMemos) == 1:
if len(self.pendingSendMemos) == 1:
self.sendDatagramMessage(memo)

def sendDatagramMessage(self, memo: DatagramWriteMemo):
def sendDatagramMessage(self, memo: DatagramSendMemo):
'''Send datagram message'''
message = Message(MTI.Datagram, self.linkLayer.localNodeID,
memo.destID, memo.data)
self.linkLayer.sendMessage(message)
self.currentOutstandingMemo = memo

def registerDatagramReceivedListener(
self, listener: Callable[[DatagramReadMemo], bool]):
self, listener: Callable[[DatagramReceiveMemo], bool]):
'''Register a listener to be notified when each datagram arrives.

One and only one listener should reply positively or negatively to the
datagram and return true.

Args:
listener (Callable): A function that accepts a DatagramReadMemo
listener (Callable): A function that accepts a DatagramReceiveMemo
as an argument.
'''
logger.debug(
"REGISTERING registerDatagramReceivedListener listener"
f" {len(self._datagramReceivedListeners) + 1}")
self._datagramReceivedListeners.append(listener)

def fireDatagramReceived(self, dg: DatagramReadMemo): # internal for tests
def fireDatagramReceived(self, dg: DatagramReceiveMemo): # internal for tests
"""Fire *datagram received* listeners."""
logger.debug(
f"FIRING listeners for datagram from {dg.srcID},"
Expand Down Expand Up @@ -236,15 +246,15 @@ def process(self, message: Message):

def handleDatagram(self, message: Message):
'''create a read memo and pass to listeners'''
memo = DatagramReadMemo(message.source, message.data)
memo = DatagramReceiveMemo(message.source, message.data)
self.fireDatagramReceived(memo)
# ^ destination listener calls back to
# positiveReplyToDatagram/negativeReplyToDatagram before returning

def handleDatagramReceivedOK(self, message: Message):
'''OK reply to write'''
# match to the memo and remove from queue
memo = self.matchToWriteMemo(message) # type: DatagramWriteMemo|None
memo = self.matchToWriteMemo(message) # type: DatagramSendMemo|None

# check for whether a match was found, indicating this was for us
if memo is None:
Expand Down Expand Up @@ -307,16 +317,16 @@ def handleLinkRestarted(self, message: Message):
return
else:
# are there any queued datagrams? If so, send first
if len(self.pendingWriteMemos) > 0:
if len(self.pendingSendMemos) > 0:
self.sendNextDatagramFromQueue()

def matchToWriteMemo(self, message: Message):
for memo in self.pendingWriteMemos:
for memo in self.pendingSendMemos:
if memo.destID != message.source:
continue # keep looking
# remove the found element - might need a try/except on this
index = self.pendingWriteMemos.index(memo)
del self.pendingWriteMemos[index]
index = self.pendingSendMemos.index(memo)
del self.pendingSendMemos[index]

return memo

Expand All @@ -327,28 +337,28 @@ def matchToWriteMemo(self, message: Message):

def sendNextDatagramFromQueue(self):
# is there a next datagram request?
if len(self.pendingWriteMemos) > 0:
if len(self.pendingSendMemos) > 0:
# yes, get it, process it
memo = self.pendingWriteMemos[0]
memo = self.pendingSendMemos[0]
self.sendDatagramMessage(memo)

def positiveReplyToDatagram(self, dg: DatagramReadMemo, flags: int = 0):
def positiveReplyToDatagram(self, dg: DatagramReceiveMemo, flags: int = 0):
"""Send a positive reply to a received datagram.

Args:
dg (DatagramReadMemo): Datagram memo being responded to.
dg (DatagramReceiveMemo): Datagram memo being responded to.
flags (Optional[int]): Flag byte to be returned to sender, see
Datagram Standard & Technical Note for meaning. Defaults to 0.
"""
message = Message(MTI.Datagram_Received_OK, self.linkLayer.localNodeID,
dg.srcID, bytearray([flags]))
self.linkLayer.sendMessage(message)

def negativeReplyToDatagram(self, dg: DatagramReadMemo, err: int):
def negativeReplyToDatagram(self, dg: DatagramReceiveMemo, err: int):
"""Send a negative reply to a received datagram.

Args:
dg (DatagramReadMemo): Datagram memo being responded to.
dg (DatagramReceiveMemo): Datagram memo being responded to.
err (int): Error code(s) to be returned to sender,
see Datagram Standard & Technical Note for meaning.
"""
Expand Down
Loading
Loading